@Override
  protected String cursorToEntry(SyncContext context, Cursor c, Entry entry, Object info)
      throws ParseException {
    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;
    }
  }
  private void getServerDiffsForFeed(
      SyncContext context,
      SyncData baseSyncData,
      SyncableContentProvider tempProvider,
      String feed,
      Object baseSyncInfo,
      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();
    }
  }