CalendarSyncAdapterpublic final class CalendarSyncAdapter extends com.google.android.providers.AbstractGDataSyncAdapter SyncAdapter for Google Calendar. Fetches the list of the user's calendars,
and for each calendar that is marked as "selected" in the web
interface, syncs that calendar. |
Fields Summary |
---|
static final String | USER_AGENT_APP_VERSION | private static final String | SELECT_BY_ACCOUNT | private static final String | SELECT_BY_ACCOUNT_AND_FEED | private static final String[] | CALENDAR_KEY_COLUMNS | private static final String | CALENDAR_KEY_SORT_ORDER | private static final String[] | FEEDS_KEY_COLUMNS | private static final String | FEEDS_KEY_SORT_ORDER | private static final String | TAG | private static final Integer | sTentativeStatus | private static final Integer | sConfirmedStatus | private static final Integer | sCanceledStatus | private final com.google.wireless.gdata.calendar.client.CalendarClient | mCalendarClient | private android.content.ContentResolver | mContentResolver | private static final String[] | CALENDARS_PROJECTION | private static int | mServerDiffs | private static int | mRefresh |
Constructors Summary |
---|
protected CalendarSyncAdapter(android.content.Context context, android.content.SyncableContentProvider provider)
super(context, provider);
mCalendarClient = new CalendarClient(
new AndroidGDataClient(context, USER_AGENT_APP_VERSION),
new XmlCalendarGDataParserFactory(new AndroidXmlParserFactory()));
|
Methods Summary |
---|
private void | addAttendeesToEntry(long eventId, com.google.wireless.gdata.calendar.data.EventEntry event)
Cursor c = getContext().getContentResolver().query(
Calendar.Attendees.CONTENT_URI, null, "event_id=" + eventId, null, null);
try {
int nameIndex = c.getColumnIndexOrThrow(Calendar.Attendees.ATTENDEE_NAME);
int emailIndex = c.getColumnIndexOrThrow(Calendar.Attendees.ATTENDEE_EMAIL);
int statusIndex = c.getColumnIndexOrThrow(Calendar.Attendees.ATTENDEE_STATUS);
int typeIndex = c.getColumnIndexOrThrow(Calendar.Attendees.ATTENDEE_TYPE);
int relIndex = c.getColumnIndexOrThrow(Calendar.Attendees.ATTENDEE_RELATIONSHIP);
while (c.moveToNext()) {
Who who = new Who();
who.setValue(c.getString(nameIndex));
who.setEmail(c.getString(emailIndex));
int status = c.getInt(statusIndex);
switch (status) {
case Calendar.Attendees.ATTENDEE_STATUS_NONE:
who.setStatus(Who.STATUS_NONE);
break;
case Calendar.Attendees.ATTENDEE_STATUS_ACCEPTED:
who.setStatus(Who.STATUS_ACCEPTED);
break;
case Calendar.Attendees.ATTENDEE_STATUS_DECLINED:
who.setStatus(Who.STATUS_DECLINED);
break;
case Calendar.Attendees.ATTENDEE_STATUS_INVITED:
who.setStatus(Who.STATUS_INVITED);
break;
case Calendar.Attendees.ATTENDEE_STATUS_TENTATIVE:
who.setStatus(Who.STATUS_TENTATIVE);
break;
default:
Log.e(TAG, "Unknown attendee status: " + status);
who.setStatus(Who.STATUS_NONE);
break;
}
int type = c.getInt(typeIndex);
switch (type) {
case Calendar.Attendees.TYPE_NONE:
who.setType(Who.TYPE_NONE);
break;
case Calendar.Attendees.TYPE_REQUIRED:
who.setType(Who.TYPE_REQUIRED);
break;
case Calendar.Attendees.TYPE_OPTIONAL:
who.setType(Who.TYPE_OPTIONAL);
break;
default:
Log.e(TAG, "Unknown attendee type: " + type);
who.setType(Who.TYPE_NONE);
break;
}
int rel = c.getInt(relIndex);
switch (rel) {
case Calendar.Attendees.RELATIONSHIP_NONE:
who.setRelationship(Who.RELATIONSHIP_NONE);
break;
case Calendar.Attendees.RELATIONSHIP_ATTENDEE:
who.setRelationship(Who.RELATIONSHIP_ATTENDEE);
break;
case Calendar.Attendees.RELATIONSHIP_ORGANIZER:
who.setRelationship(Who.RELATIONSHIP_ORGANIZER);
break;
case Calendar.Attendees.RELATIONSHIP_SPEAKER:
who.setRelationship(Who.RELATIONSHIP_SPEAKER);
break;
case Calendar.Attendees.RELATIONSHIP_PERFORMER:
who.setRelationship(Who.RELATIONSHIP_PERFORMER);
break;
default:
Log.e(TAG, "Unknown attendee relationship: " + rel);
who.setRelationship(Who.RELATIONSHIP_NONE);
break;
}
event.addAttendee(who);
}
} finally {
c.close();
}
| private void | addExtendedPropertiesToEntry(long eventId, com.google.wireless.gdata.calendar.data.EventEntry event)
Cursor c = getContext().getContentResolver().query(
Calendar.ExtendedProperties.CONTENT_URI, null,
"event_id=" + eventId, null, null);
try {
int nameIndex = c.getColumnIndex(Calendar.ExtendedProperties.NAME);
int valueIndex = c.getColumnIndex(Calendar.ExtendedProperties.VALUE);
while (c.moveToNext()) {
String name = c.getString(nameIndex);
String value = c.getString(valueIndex);
event.addExtendedProperty(name, value);
}
} finally {
c.close();
}
| private void | addRecurrenceToEntry(ICalendar.Component component, com.google.wireless.gdata.calendar.data.EventEntry event)
// serialize the component into a Google Calendar recurrence string
// we don't serialize the entire component, since we have a dummy
// wrapper (BEGIN:DUMMY, END:DUMMY).
StringBuilder sb = new StringBuilder();
// append the properties
boolean first = true;
for (String propertyName : component.getPropertyNames()) {
for (ICalendar.Property property :
component.getProperties(propertyName)) {
if (first) {
first = false;
} else {
sb.append("\n");
}
property.toString(sb);
}
}
// append the sub-components
List<ICalendar.Component> children = component.getComponents();
if (children != null) {
for (ICalendar.Component child : children) {
if (first) {
first = false;
} else {
sb.append("\n");
}
child.toString(sb);
}
}
event.setRecurrence(sb.toString());
| private void | addRemindersToEntry(long eventId, com.google.wireless.gdata.calendar.data.EventEntry event)
Cursor c = getContext().getContentResolver().query(
Calendar.Reminders.CONTENT_URI, null,
"event_id=" + eventId, null, null);
try {
int methodIndex = c.getColumnIndex(Calendar.Reminders.METHOD);
int minutesIndex = c.getColumnIndex(Calendar.Reminders.MINUTES);
while (c.moveToNext()) {
Reminder reminder = new Reminder();
reminder.setMinutes(c.getInt(minutesIndex));
int method = c.getInt(methodIndex);
switch(method) {
case Calendar.Reminders.METHOD_DEFAULT:
reminder.setMethod(Reminder.METHOD_DEFAULT);
break;
case Calendar.Reminders.METHOD_ALERT:
reminder.setMethod(Reminder.METHOD_ALERT);
break;
case Calendar.Reminders.METHOD_EMAIL:
reminder.setMethod(Reminder.METHOD_EMAIL);
break;
case Calendar.Reminders.METHOD_SMS:
reminder.setMethod(Reminder.METHOD_SMS);
break;
default:
throw new ParseException("illegal method, " + method);
}
event.addReminder(reminder);
}
} finally {
c.close();
}
| protected java.lang.Object | createSyncInfo()
return new SyncInfo();
| protected java.lang.String | cursorToEntry(android.content.SyncContext context, android.database.Cursor c, com.google.wireless.gdata.data.Entry entry, java.lang.Object info)
EventEntry event = (EventEntry) entry;
SyncInfo syncInfo = (SyncInfo) info;
String feedUrl = c.getString(c.getColumnIndex(Calendars.URL));
// update the sync info. this will be used later when we update the
// provider with the results of sending this entry to the calendar
// server.
syncInfo.calendarId = c.getLong(c.getColumnIndex(Events.CALENDAR_ID));
syncInfo.calendarTimezone =
c.getString(c.getColumnIndex(Events.EVENT_TIMEZONE));
if (TextUtils.isEmpty(syncInfo.calendarTimezone)) {
// if the event timezone is not set -- e.g., when we're creating an
// event on the device -- we will use the timezone for the calendar.
syncInfo.calendarTimezone =
c.getString(c.getColumnIndex(Events.TIMEZONE));
}
// id
event.setId(c.getString(c.getColumnIndex(Events._SYNC_ID)));
event.setEditUri(c.getString(c.getColumnIndex(Events._SYNC_VERSION)));
// status
byte status;
int localStatus = c.getInt(c.getColumnIndex(Events.STATUS));
switch (localStatus) {
case Events.STATUS_CANCELED:
status = EventEntry.STATUS_CANCELED;
break;
case Events.STATUS_CONFIRMED:
status = EventEntry.STATUS_CONFIRMED;
break;
case Events.STATUS_TENTATIVE:
status = EventEntry.STATUS_TENTATIVE;
break;
default:
// should not happen
status = EventEntry.STATUS_TENTATIVE;
break;
}
event.setStatus(status);
// visibility
byte visibility;
int localVisibility = c.getInt(c.getColumnIndex(Events.VISIBILITY));
switch (localVisibility) {
case Events.VISIBILITY_DEFAULT:
visibility = EventEntry.VISIBILITY_DEFAULT;
break;
case Events.VISIBILITY_CONFIDENTIAL:
visibility = EventEntry.VISIBILITY_CONFIDENTIAL;
break;
case Events.VISIBILITY_PRIVATE:
visibility = EventEntry.VISIBILITY_PRIVATE;
break;
case Events.VISIBILITY_PUBLIC:
visibility = EventEntry.VISIBILITY_PUBLIC;
break;
default:
// should not happen
Log.e(TAG, "Unexpected value for visibility: " + localVisibility
+ "; using default visibility.");
visibility = EventEntry.VISIBILITY_DEFAULT;
break;
}
event.setVisibility(visibility);
byte transparency;
int localTransparency = c.getInt(c.getColumnIndex(Events.TRANSPARENCY));
switch (localTransparency) {
case Events.TRANSPARENCY_OPAQUE:
transparency = EventEntry.TRANSPARENCY_OPAQUE;
break;
case Events.TRANSPARENCY_TRANSPARENT:
transparency = EventEntry.TRANSPARENCY_TRANSPARENT;
break;
default:
// should not happen
Log.e(TAG, "Unexpected value for transparency: " + localTransparency
+ "; using opaque transparency.");
transparency = EventEntry.TRANSPARENCY_OPAQUE;
break;
}
event.setTransparency(transparency);
// could set the html uri, but there's no need to, since it should not be edited.
// title
event.setTitle(c.getString(c.getColumnIndex(Events.TITLE)));
// description
event.setContent(c.getString(c.getColumnIndex(Events.DESCRIPTION)));
// where
event.setWhere(c.getString(c.getColumnIndex(Events.EVENT_LOCATION)));
// attendees
long eventId = c.getInt(c.getColumnIndex(Events._SYNC_LOCAL_ID));
addAttendeesToEntry(eventId, event);
// comment uri
event.setCommentsUri(c.getString(c.getColumnIndexOrThrow(Events.COMMENTS_URI)));
Time utc = new Time(Time.TIMEZONE_UTC);
boolean allDay = c.getInt(c.getColumnIndex(Events.ALL_DAY)) != 0;
String startTime = null;
String endTime = null;
// start time
int dtstartColumn = c.getColumnIndex(Events.DTSTART);
if (!c.isNull(dtstartColumn)) {
long dtstart = c.getLong(dtstartColumn);
utc.set(dtstart);
startTime = utc.format3339(allDay);
}
// end time
int dtendColumn = c.getColumnIndex(Events.DTEND);
if (!c.isNull(dtendColumn)) {
long dtend = c.getLong(dtendColumn);
utc.set(dtend);
endTime = utc.format3339(allDay);
}
When when = new When(startTime, endTime);
event.addWhen(when);
// reminders
Integer hasReminder = c.getInt(c.getColumnIndex(Events.HAS_ALARM));
if (hasReminder != null && hasReminder.intValue() != 0) {
addRemindersToEntry(eventId, event);
}
// extendedProperties
Integer hasExtendedProperties = c.getInt(c.getColumnIndex(Events.HAS_EXTENDED_PROPERTIES));
if (hasExtendedProperties != null && hasExtendedProperties.intValue() != 0) {
addExtendedPropertiesToEntry(eventId, event);
}
long originalStartTime = -1;
String originalId = c.getString(c.getColumnIndex(Events.ORIGINAL_EVENT));
int originalStartTimeIndex = c.getColumnIndex(Events.ORIGINAL_INSTANCE_TIME);
if (!c.isNull(originalStartTimeIndex)) {
originalStartTime = c.getLong(originalStartTimeIndex);
}
if ((originalStartTime != -1) && !TextUtils.isEmpty(originalId)) {
// We need to use the "originalAllDay" field for the original event
// in order to format the "originalStartTime" correctly.
boolean originalAllDay = c.getInt(c.getColumnIndex(Events.ORIGINAL_ALL_DAY)) != 0;
Time originalTime = new Time(c.getString(c.getColumnIndex(Events.EVENT_TIMEZONE)));
originalTime.set(originalStartTime);
utc.set(originalStartTime);
event.setOriginalEventStartTime(utc.format3339(originalAllDay));
event.setOriginalEventId(originalId);
}
// recurrences.
ICalendar.Component component = new ICalendar.Component("DUMMY",
null /* parent */);
if (RecurrenceSet.populateComponent(c, component)) {
addRecurrenceToEntry(component, event);
}
// if this is a new entry, return the feed url. otherwise, return null; the edit url is
// already in the entry.
if (event.getEditUri() == null) {
return feedUrl;
} else {
return null;
}
| protected void | deletedCursorToEntry(android.content.SyncContext context, android.database.Cursor c, com.google.wireless.gdata.data.Entry entry)
EventEntry event = (EventEntry) entry;
event.setId(c.getString(c.getColumnIndex(Events._SYNC_ID)));
event.setEditUri(c.getString(c.getColumnIndex(Events._SYNC_VERSION)));
event.setStatus(EventEntry.STATUS_CANCELED);
| private void | deletedEntryToContentValues(java.lang.Long syncLocalId, com.google.wireless.gdata.calendar.data.EventEntry event, android.content.ContentValues values)
// see #deletedCursorToEntry. this deletion cannot be an exception to a recurrence (e.g.,
// deleting an instance of a repeating event) -- new recurrence exceptions would be
// insertions.
values.clear();
// Base sync info
values.put(Events._SYNC_LOCAL_ID, syncLocalId);
values.put(Events._SYNC_ID, event.getId());
values.put(Events._SYNC_VERSION, event.getEditUri());
| private int | entryToContentValues(com.google.wireless.gdata.calendar.data.EventEntry event, java.lang.Long syncLocalId, android.content.ContentValues map, java.lang.Object info)Clear out the map and stuff an Entry into it in a format that can
be inserted into a content provider.
If a date is before 1970 or past 2038, ENTRY_INVALID is returned, and DTSTART
is set to -1. This is due to the current 32-bit time restriction and
will be fixed in a future release.
SyncInfo syncInfo = (SyncInfo) info;
// There are 3 cases for parsing a date-time string:
//
// 1. The date-time string specifies a date and time with a time offset.
// (The "normal" format.)
// 2. The date-time string is just a date, used for all-day events,
// with no time or time offset fields. (The "all-day" format.)
// 3. The date-time string specifies a date and time, but no time
// offset. (The "floating" format, not supported yet.)
//
// Case 1: Time.parse3339() converts the date-time string to UTC and
// sets the Time.timezone to UTC. It does not matter what the initial
// Time.timezone field was set to. The initial timezone is ignored.
//
// Case 2: The date-time string doesn't specify the time.
// Time.parse3339() just sets the date but not the time (hour, minute,
// second) fields. (The time fields should be zero, meaning midnight.)
// This code then sets the timezone to UTC (because this is an all-day
// event). It does not matter in this case either what the initial
// Time.timezone field was set to.
//
// Case 3: This is a "floating time" (which we do not support yet).
// In this case, it will matter what the initial Time.timezone is set
// to. It should use UTC. If I specify a floating time of 1pm then I
// want that event displayed at 1pm in every timezone. The easiest way
// to support this would be store it as 1pm in UTC and mark the event
// as "isFloating" (with a new database column). Then when displaying
// the event, the code checks "isFloating" and just leaves the time at
// 1pm without doing any conversion to the local timezone.
//
// So in all cases, it is correct to set the Time.timezone to UTC.
Time time = new Time(Time.TIMEZONE_UTC);
map.clear();
// Base sync info
map.put(Events._SYNC_ID, event.getId());
String version = event.getEditUri();
if (!StringUtils.isEmpty(version)) {
// Always rewrite the edit URL to https for dasher account to avoid
// redirection.
map.put(Events._SYNC_VERSION, rewriteUrlforAccount(getAccount(), version));
}
// see if this is an exception to an existing event/recurrence.
String originalId = event.getOriginalEventId();
String originalStartTime = event.getOriginalEventStartTime();
boolean isRecurrenceException = false;
if (!StringUtils.isEmpty(originalId) && !StringUtils.isEmpty(originalStartTime)) {
isRecurrenceException = true;
time.parse3339(originalStartTime);
map.put(Events.ORIGINAL_EVENT, originalId);
map.put(Events.ORIGINAL_INSTANCE_TIME, time.toMillis(false /* use isDst */));
map.put(Events.ORIGINAL_ALL_DAY, time.allDay ? 1 : 0);
}
// Event status
byte status = event.getStatus();
switch (status) {
case EventEntry.STATUS_CANCELED:
if (!isRecurrenceException) {
return ENTRY_DELETED;
}
map.put(Events.STATUS, sCanceledStatus);
break;
case EventEntry.STATUS_TENTATIVE:
map.put(Events.STATUS, sTentativeStatus);
break;
case EventEntry.STATUS_CONFIRMED:
map.put(Events.STATUS, sConfirmedStatus);
break;
default:
// should not happen
return ENTRY_INVALID;
}
map.put(Events._SYNC_LOCAL_ID, syncLocalId);
// Updated time, only needed for non-deleted items
String updated = event.getUpdateDate();
map.put(Events._SYNC_TIME, updated);
map.put(Events._SYNC_DIRTY, 0);
// visibility
switch (event.getVisibility()) {
case EventEntry.VISIBILITY_DEFAULT:
map.put(Events.VISIBILITY, Events.VISIBILITY_DEFAULT);
break;
case EventEntry.VISIBILITY_CONFIDENTIAL:
map.put(Events.VISIBILITY, Events.VISIBILITY_CONFIDENTIAL);
break;
case EventEntry.VISIBILITY_PRIVATE:
map.put(Events.VISIBILITY, Events.VISIBILITY_PRIVATE);
break;
case EventEntry.VISIBILITY_PUBLIC:
map.put(Events.VISIBILITY, Events.VISIBILITY_PUBLIC);
break;
default:
// should not happen
Log.e(TAG, "Unexpected visibility " + event.getVisibility());
return ENTRY_INVALID;
}
// transparency
switch (event.getTransparency()) {
case EventEntry.TRANSPARENCY_OPAQUE:
map.put(Events.TRANSPARENCY, Events.TRANSPARENCY_OPAQUE);
break;
case EventEntry.TRANSPARENCY_TRANSPARENT:
map.put(Events.TRANSPARENCY, Events.TRANSPARENCY_TRANSPARENT);
break;
default:
// should not happen
Log.e(TAG, "Unexpected transparency " + event.getTransparency());
return ENTRY_INVALID;
}
// html uri
String htmlUri = event.getHtmlUri();
if (!StringUtils.isEmpty(htmlUri)) {
// TODO: convert this desktop url into a mobile one?
// htmlUri = htmlUri.replace("/event?", "/mevent?"); // but a little more robust
map.put(Events.HTML_URI, htmlUri);
}
// title
String title = event.getTitle();
if (!StringUtils.isEmpty(title)) {
map.put(Events.TITLE, title);
}
// content
String content = event.getContent();
if (!StringUtils.isEmpty(content)) {
map.put(Events.DESCRIPTION, content);
}
// where
String where = event.getWhere();
if (!StringUtils.isEmpty(where)) {
map.put(Events.EVENT_LOCATION, where);
}
// Calendar ID
map.put(Events.CALENDAR_ID, syncInfo.calendarId);
// comments uri
String commentsUri = event.getCommentsUri();
if (commentsUri != null) {
map.put(Events.COMMENTS_URI, commentsUri);
}
boolean timesSet = false;
// see if there are any reminders for this event
if (event.getReminders() != null) {
// just store that we have reminders. the caller will have
// to update the reminders table separately.
map.put(Events.HAS_ALARM, 1);
}
// see if there are any extended properties for this event
if (event.getExtendedProperties() != null) {
// just store that we have extended properties. the caller will have
// to update the extendedproperties table separately.
map.put(Events.HAS_EXTENDED_PROPERTIES, 1);
}
// dtstart & dtend
When when = event.getFirstWhen();
if (when != null) {
String startTime = when.getStartTime();
if (!StringUtils.isEmpty(startTime)) {
time.parse3339(startTime);
// we also stash away the event's timezone.
// this timezone might get overwritten below, if this event is
// a recurrence (recurrences are defined in terms of the
// timezone of the creator of the event).
// note that we treat all day events as occurring in the UTC timezone, so
// an event on 05/08/2007 occurs on 05/08/2007, no matter what timezone the device
// is in.
// TODO: handle the "floating" timezone.
if (time.allDay) {
map.put(Events.ALL_DAY, 1);
map.put(Events.EVENT_TIMEZONE, Time.TIMEZONE_UTC);
} else {
map.put(Events.EVENT_TIMEZONE, syncInfo.calendarTimezone);
}
long dtstart = time.toMillis(false /* use isDst */);
if (dtstart < 0) {
if (Config.LOGD) {
Log.d(TAG, "dtstart out of range: " + startTime);
}
map.put(Events.DTSTART, -1); // Flag to caller that date is out of range
return ENTRY_INVALID;
}
map.put(Events.DTSTART, dtstart);
timesSet = true;
}
String endTime = when.getEndTime();
if (!StringUtils.isEmpty(endTime)) {
time.parse3339(endTime);
long dtend = time.toMillis(false /* use isDst */);
if (dtend < 0) {
if (Config.LOGD) {
Log.d(TAG, "dtend out of range: " + endTime);
}
map.put(Events.DTSTART, -1); // Flag to caller that date is out of range
return ENTRY_INVALID;
}
map.put(Events.DTEND, dtend);
}
}
// rrule
String recurrence = event.getRecurrence();
if (!TextUtils.isEmpty(recurrence)) {
ICalendar.Component recurrenceComponent =
new ICalendar.Component("DUMMY", null /* parent */);
ICalendar ical = null;
try {
ICalendar.parseComponent(recurrenceComponent, recurrence);
} catch (ICalendar.FormatException fe) {
if (Config.LOGD) {
Log.d(TAG, "Unable to parse recurrence: " + recurrence);
}
return ENTRY_INVALID;
}
if (!RecurrenceSet.populateContentValues(recurrenceComponent, map)) {
return ENTRY_INVALID;
}
timesSet = true;
}
if (!timesSet) {
return ENTRY_INVALID;
}
map.put(SyncConstValue._SYNC_ACCOUNT, getAccount());
return ENTRY_OK;
| protected android.database.Cursor | getCursorForDeletedTable(android.content.ContentProvider cp, java.lang.Class entryClass)
if (entryClass != EventEntry.class) {
throw new IllegalArgumentException("unexpected entry class, " + entryClass.getName());
}
return cp.query(Calendar.Events.DELETED_CONTENT_URI, null, null, null, null);
| protected android.database.Cursor | getCursorForTable(android.content.ContentProvider cp, java.lang.Class entryClass)
if (entryClass != EventEntry.class) {
throw new IllegalArgumentException("unexpected entry class, " + entryClass.getName());
}
return cp.query(Calendar.Events.CONTENT_URI, null, null, null, null);
| protected java.lang.Class | getFeedEntryClass()
return EventEntry.class;
| protected java.lang.String | getFeedUrl(java.lang.String account)Should not get called. The feed url changes depending on which calendar is being sync'd
to/from the device, and thus is determined and passed around as a local variable, where
appropriate.
throw new UnsupportedOperationException("getFeedUrl() should not get called.");
| protected com.google.wireless.gdata.client.GDataServiceClient | getGDataServiceClient()
return mCalendarClient;
| public void | getServerDiffs(android.content.SyncContext context, SyncData baseSyncData, android.content.SyncableContentProvider tempProvider, android.os.Bundle extras, java.lang.Object baseSyncInfo, android.content.SyncResult syncResult)
final ContentResolver cr = getContext().getContentResolver();
mServerDiffs++;
final boolean syncingSingleFeed = (extras != null) && extras.containsKey("feed");
if (syncingSingleFeed) {
String feedUrl = extras.getString("feed");
getServerDiffsForFeed(context, baseSyncData, tempProvider, feedUrl,
baseSyncInfo, syncResult);
return;
}
// select the set of calendars for this account.
Cursor cursor = cr.query(Calendar.Calendars.CONTENT_URI,
CALENDARS_PROJECTION, SELECT_BY_ACCOUNT,
new String[] { getAccount() }, null /* sort order */);
Bundle syncExtras = new Bundle();
boolean refreshCalendars = true;
try {
while (cursor.moveToNext()) {
boolean syncEvents = (cursor.getInt(6) == 1);
String feedUrl = cursor.getString(3);
if (!syncEvents) {
continue;
}
// since this is a poll (no specific feed selected), refresh the list of calendars.
// we can move away from this when we move to the new allcalendars feed, which is
// syncable. until then, we'll rely on the daily poll to keep the list of calendars
// up to date.
if (refreshCalendars) {
mRefresh++;
context.setStatusText("Fetching list of calendars");
// get rid of the current cursor and fetch from the server.
cursor.close();
final String[] accountSelectionArgs = new String[]{getAccount()};
cursor = cr.query(
Calendar.Calendars.LIVE_CONTENT_URI, CALENDARS_PROJECTION,
SELECT_BY_ACCOUNT, accountSelectionArgs, null /* sort order */);
// start over with the loop
refreshCalendars = false;
continue;
}
// schedule syncs for each of these feeds.
syncExtras.clear();
syncExtras.putAll(extras);
syncExtras.putString("feed", feedUrl);
cr.startSync(Calendar.CONTENT_URI, syncExtras);
}
} finally {
cursor.close();
}
| private void | getServerDiffsForFeed(android.content.SyncContext context, SyncData baseSyncData, android.content.SyncableContentProvider tempProvider, java.lang.String feed, java.lang.Object baseSyncInfo, android.content.SyncResult syncResult)
final SyncInfo syncInfo = (SyncInfo) baseSyncInfo;
final GDataSyncData syncData = (GDataSyncData) baseSyncData;
Cursor cursor = getContext().getContentResolver().query(Calendar.Calendars.CONTENT_URI,
CALENDARS_PROJECTION, SELECT_BY_ACCOUNT_AND_FEED,
new String[] { getAccount(), feed }, null /* sort order */);
ContentValues map = new ContentValues();
int maxResults = getMaxEntriesPerSync();
try {
if (!cursor.moveToFirst()) {
return;
}
// TODO: refactor all of this, so we don't have to rely on
// member variables getting updated here in order for the
// base class hooks to work.
syncInfo.calendarId = cursor.getLong(0);
boolean syncEvents = (cursor.getInt(6) == 1);
long syncTime = cursor.getLong(2);
String feedUrl = cursor.getString(3);
String name = cursor.getString(4);
String origCalendarTimezone =
syncInfo.calendarTimezone = cursor.getString(5);
if (!syncEvents) {
// should not happen. non-syncable feeds should not be scheduled for syncs nor
// should they get tickled.
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Ignoring sync request for non-syncable feed.");
}
return;
}
context.setStatusText("Syncing " + name);
// call the superclass implementation to sync the current
// calendar from the server.
getServerDiffsImpl(context, tempProvider, getFeedEntryClass(), feedUrl, syncInfo,
maxResults, syncData, syncResult);
if (mSyncCanceled || syncResult.hasError()) {
return;
}
// update the timezone for this calendar if it changed
if (!TextUtils.equals(syncInfo.calendarTimezone,
origCalendarTimezone)) {
map.clear();
map.put(Calendars.TIMEZONE, syncInfo.calendarTimezone);
mContentResolver.update(
ContentUris.withAppendedId(Calendars.CONTENT_URI, syncInfo.calendarId),
map, null, null);
}
} finally {
cursor.close();
}
| protected void | getStatsString(java.lang.StringBuffer sb, android.content.SyncResult result)
super.getStatsString(sb, result);
if (mRefresh > 0) {
sb.append("F").append(mRefresh);
}
if (mServerDiffs > 0) {
sb.append("s").append(mServerDiffs);
}
| protected boolean | handleAllDeletedUnavailable(GDataSyncData syncData, java.lang.String feed)
syncData.feedData.remove(feed);
getContext().getContentResolver().delete(Calendar.Calendars.CONTENT_URI,
Calendar.Calendars._SYNC_ACCOUNT + "=? AND " + Calendar.Calendars.URL + "=?",
new String[]{getAccount(), feed});
return true;
| protected void | initTempProvider(android.content.SyncableContentProvider cp)
// TODO: don't use the real db's calendar id's. create new ones locally and translate
// during CalendarProvider's merge.
// populate temp provider with calendar ids, so joins work.
ContentValues map = new ContentValues();
Cursor c = getContext().getContentResolver().query(
Calendar.Calendars.CONTENT_URI,
CALENDARS_PROJECTION,
SELECT_BY_ACCOUNT, new String[]{getAccount()}, null /* sort order */);
final int idIndex = c.getColumnIndexOrThrow(Calendars._ID);
final int urlIndex = c.getColumnIndexOrThrow(Calendars.URL);
final int timezoneIndex = c.getColumnIndexOrThrow(Calendars.TIMEZONE);
while (c.moveToNext()) {
map.clear();
map.put(Calendars._ID, c.getLong(idIndex));
map.put(Calendars.URL, c.getString(urlIndex));
map.put(Calendars.TIMEZONE, c.getString(timezoneIndex));
cp.insert(Calendar.Calendars.CONTENT_URI, map);
}
c.close();
| protected com.google.wireless.gdata.data.Entry | newEntry()
return new EventEntry();
| public void | onAccountsChanged(java.lang.String[] accountsArray)
if (!"yes".equals(SystemProperties.get("ro.config.sync"))) {
return;
}
// - Get a cursor (A) over all selected calendars over all accounts
// - Get a cursor (B) over all subscribed feeds for calendar
// - If an item is in A but not B then add a subscription
// - If an item is in B but not A then remove the subscription
ContentResolver cr = getContext().getContentResolver();
Cursor cursorA = null;
Cursor cursorB = null;
try {
cursorA = Calendar.Calendars.query(cr, null /* projection */,
Calendar.Calendars.SELECTED + "=1", CALENDAR_KEY_SORT_ORDER);
int urlIndexA = cursorA.getColumnIndexOrThrow(Calendar.Calendars.URL);
int accountIndexA = cursorA.getColumnIndexOrThrow(Calendar.Calendars._SYNC_ACCOUNT);
cursorB = SubscribedFeeds.Feeds.query(cr, FEEDS_KEY_COLUMNS,
SubscribedFeeds.Feeds.AUTHORITY + "=?", new String[]{Calendar.AUTHORITY},
FEEDS_KEY_SORT_ORDER);
int urlIndexB = cursorB.getColumnIndexOrThrow(SubscribedFeeds.Feeds.FEED);
int accountIndexB = cursorB.getColumnIndexOrThrow(SubscribedFeeds.Feeds._SYNC_ACCOUNT);
for (CursorJoiner.Result joinerResult :
new CursorJoiner(cursorA, CALENDAR_KEY_COLUMNS, cursorB, FEEDS_KEY_COLUMNS)) {
switch (joinerResult) {
case LEFT:
SubscribedFeeds.addFeed(
cr,
cursorA.getString(urlIndexA),
cursorA.getString(accountIndexA),
Calendar.AUTHORITY,
CalendarClient.SERVICE);
break;
case RIGHT:
SubscribedFeeds.deleteFeed(
cr,
cursorB.getString(urlIndexB),
cursorB.getString(accountIndexB),
Calendar.AUTHORITY);
break;
case BOTH:
// do nothing, since the subscription already exists
break;
}
}
} finally {
// check for null in case an exception occurred before the cursors got created
if (cursorA != null) cursorA.close();
if (cursorB != null) cursorB.close();
}
| public void | onSyncStarting(android.content.SyncContext context, java.lang.String account, boolean forced, android.content.SyncResult result)
mContentResolver = getContext().getContentResolver();
mServerDiffs = 0;
mRefresh = 0;
super.onSyncStarting(context, account, forced, result);
| public void | updateProvider(com.google.wireless.gdata.data.Feed feed, java.lang.Long syncLocalId, com.google.wireless.gdata.data.Entry entry, android.content.ContentProvider provider, java.lang.Object info)
SyncInfo syncInfo = (SyncInfo) info;
EventEntry event = (EventEntry) entry;
ContentValues map = new ContentValues();
// use the calendar's timezone, if provided in the feed.
// this overwrites whatever was in the db.
if ((feed != null) && (feed instanceof EventsFeed)) {
EventsFeed eventsFeed = (EventsFeed) feed;
syncInfo.calendarTimezone = eventsFeed.getTimezone();
}
if (entry.isDeleted()) {
deletedEntryToContentValues(syncLocalId, event, map);
if (Config.LOGV) {
Log.v(TAG, "Deleting entry: " + map);
}
provider.insert(Events.DELETED_CONTENT_URI, map);
return;
}
int entryState = entryToContentValues(event, syncLocalId, map, syncInfo);
if (entryState == ENTRY_DELETED) {
if (Config.LOGV) {
Log.v(TAG, "Got deleted entry from server: "
+ map);
}
provider.insert(Events.DELETED_CONTENT_URI, map);
} else if (entryState == ENTRY_OK) {
if (Config.LOGV) {
Log.v(TAG, "Got entry from server: " + map);
}
Uri result = provider.insert(Events.CONTENT_URI, map);
long rowId = ContentUris.parseId(result);
// handle the reminders for the event
Integer hasAlarm = map.getAsInteger(Events.HAS_ALARM);
if (hasAlarm != null && hasAlarm == 1) {
// reminders should not be null
Vector alarms = event.getReminders();
if (alarms == null) {
Log.e(TAG, "Have an alarm but do not have any reminders "
+ "-- should not happen.");
throw new IllegalStateException("Have an alarm but do not have any reminders");
}
Enumeration reminders = alarms.elements();
while (reminders.hasMoreElements()) {
ContentValues reminderValues = new ContentValues();
reminderValues.put(Calendar.Reminders.EVENT_ID, rowId);
Reminder reminder = (Reminder) reminders.nextElement();
byte method = reminder.getMethod();
switch (method) {
case Reminder.METHOD_DEFAULT:
reminderValues.put(Calendar.Reminders.METHOD,
Calendar.Reminders.METHOD_DEFAULT);
break;
case Reminder.METHOD_ALERT:
reminderValues.put(Calendar.Reminders.METHOD,
Calendar.Reminders.METHOD_ALERT);
break;
case Reminder.METHOD_EMAIL:
reminderValues.put(Calendar.Reminders.METHOD,
Calendar.Reminders.METHOD_EMAIL);
break;
case Reminder.METHOD_SMS:
reminderValues.put(Calendar.Reminders.METHOD,
Calendar.Reminders.METHOD_SMS);
break;
default:
// should not happen. return false? we'd have to
// roll back the event.
Log.e(TAG, "Unknown reminder method: " + method
+ " should not happen!");
}
int minutes = reminder.getMinutes();
reminderValues.put(Calendar.Reminders.MINUTES,
minutes == Reminder.MINUTES_DEFAULT ?
Calendar.Reminders.MINUTES_DEFAULT :
minutes);
if (provider.insert(Calendar.Reminders.CONTENT_URI,
reminderValues) == null) {
throw new ParseException("Unable to insert reminders.");
}
}
}
// handle attendees for the event
Vector attendees = event.getAttendees();
Enumeration attendeesEnum = attendees.elements();
while (attendeesEnum.hasMoreElements()) {
Who who = (Who) attendeesEnum.nextElement();
ContentValues attendeesValues = new ContentValues();
attendeesValues.put(Calendar.Attendees.EVENT_ID, rowId);
attendeesValues.put(Calendar.Attendees.ATTENDEE_NAME, who.getValue());
attendeesValues.put(Calendar.Attendees.ATTENDEE_EMAIL, who.getEmail());
byte status;
switch (who.getStatus()) {
case Who.STATUS_NONE:
status = Calendar.Attendees.ATTENDEE_STATUS_NONE;
break;
case Who.STATUS_INVITED:
status = Calendar.Attendees.ATTENDEE_STATUS_INVITED;
break;
case Who.STATUS_ACCEPTED:
status = Calendar.Attendees.ATTENDEE_STATUS_ACCEPTED;
break;
case Who.STATUS_TENTATIVE:
status = Calendar.Attendees.ATTENDEE_STATUS_TENTATIVE;
break;
case Who.STATUS_DECLINED:
status = Calendar.Attendees.ATTENDEE_STATUS_DECLINED;
break;
default:
Log.w(TAG, "Unknown attendee status " + who.getStatus());
status = Calendar.Attendees.ATTENDEE_STATUS_NONE;
}
attendeesValues.put(Calendar.Attendees.ATTENDEE_STATUS, status);
byte rel;
switch (who.getRelationship()) {
case Who.RELATIONSHIP_NONE:
rel = Calendar.Attendees.RELATIONSHIP_NONE;
break;
case Who.RELATIONSHIP_ORGANIZER:
rel = Calendar.Attendees.RELATIONSHIP_ORGANIZER;
break;
case Who.RELATIONSHIP_ATTENDEE:
rel = Calendar.Attendees.RELATIONSHIP_ATTENDEE;
break;
case Who.RELATIONSHIP_PERFORMER:
rel = Calendar.Attendees.RELATIONSHIP_PERFORMER;
break;
case Who.RELATIONSHIP_SPEAKER:
rel = Calendar.Attendees.RELATIONSHIP_SPEAKER;
break;
default:
Log.w(TAG, "Unknown attendee relationship " + who.getRelationship());
rel = Calendar.Attendees.RELATIONSHIP_NONE;
}
attendeesValues.put(Calendar.Attendees.ATTENDEE_RELATIONSHIP, rel);
byte type;
switch (who.getType()) {
case Who.TYPE_NONE:
type = Calendar.Attendees.TYPE_NONE;
break;
case Who.TYPE_REQUIRED:
type = Calendar.Attendees.TYPE_REQUIRED;
break;
case Who.TYPE_OPTIONAL:
type = Calendar.Attendees.TYPE_OPTIONAL;
break;
default:
Log.w(TAG, "Unknown attendee type " + who.getType());
type = Calendar.Attendees.TYPE_NONE;
}
attendeesValues.put(Calendar.Attendees.ATTENDEE_TYPE, type);
if (provider.insert(Calendar.Attendees.CONTENT_URI, attendeesValues) == null) {
throw new ParseException("Unable to insert attendees.");
}
}
// handle the extended properties for the event
Integer hasExtendedProperties = map.getAsInteger(Events.HAS_EXTENDED_PROPERTIES);
if (hasExtendedProperties != null && hasExtendedProperties.intValue() != 0) {
// extended properties should not be null
// TODO: make the extended properties a bit more OO?
Hashtable extendedProperties = event.getExtendedProperties();
if (extendedProperties == null) {
Log.e(TAG, "Have extendedProperties but do not have any properties"
+ "-- should not happen.");
throw new IllegalStateException(
"Have extendedProperties but do not have any properties");
}
Enumeration propertyNames = extendedProperties.keys();
while (propertyNames.hasMoreElements()) {
String propertyName = (String) propertyNames.nextElement();
String propertyValue = (String) extendedProperties.get(propertyName);
ContentValues extendedPropertyValues = new ContentValues();
extendedPropertyValues.put(Calendar.ExtendedProperties.EVENT_ID, rowId);
extendedPropertyValues.put(Calendar.ExtendedProperties.NAME,
propertyName);
extendedPropertyValues.put(Calendar.ExtendedProperties.VALUE,
propertyValue);
if (provider.insert(Calendar.ExtendedProperties.CONTENT_URI,
extendedPropertyValues) == null) {
throw new ParseException("Unable to insert extended properties.");
}
}
}
} else {
// If the DTSTART == -1, then the date was out of range. We don't
// need to throw a ParseException because the user can create
// dates on the web that we can't handle on the phone. For
// example, events with dates before Dec 13, 1901 can be created
// on the web but cannot be handled on the phone.
Long dtstart = map.getAsLong(Events.DTSTART);
if (dtstart != null && dtstart == -1) {
return;
}
if (Config.LOGV) {
Log.v(TAG, "Got invalid entry from server: " + map);
}
throw new ParseException("Got invalid entry from server: " + map);
}
| protected void | updateQueryParameters(com.google.wireless.gdata.client.QueryParams params)
if (params.getUpdatedMin() == null) {
// if this is the first sync, only bother syncing starting from
// one month ago.
// TODO: remove this restriction -- we may want all of
// historical calendar events.
Time lastMonth = new Time(Time.TIMEZONE_UTC);
lastMonth.setToNow();
--lastMonth.month;
lastMonth.normalize(true /* ignore isDst */);
String startMin = lastMonth.format("%Y-%m-%dT%H:%M:%S.000Z");
// TODO: move start-min to CalendarClient?
// or create CalendarQueryParams subclass (extra class)?
params.setParamValue("start-min", startMin);
// HACK: specify that we want to expand recurrences ijn the past,
// so the server does not expand any recurrences. we do this to
// avoid a large number of gd:when elements that we do not need,
// since we process gd:recurrence elements instead.
params.setParamValue("recurrence-expansion-start", "1970-01-01");
params.setParamValue("recurrence-expansion-end", "1970-01-01");
}
// we want to get the events ordered by last modified, so we can
// recover in case we cannot process the entire feed.
params.setParamValue("orderby", "lastmodified");
params.setParamValue("sortorder", "ascending");
|
|