diff --git a/src/com/android/exchange/adapter/CalendarSyncAdapter.java b/src/com/android/exchange/adapter/CalendarSyncAdapter.java index 4ab071cc9..3732423cb 100644 --- a/src/com/android/exchange/adapter/CalendarSyncAdapter.java +++ b/src/com/android/exchange/adapter/CalendarSyncAdapter.java @@ -90,6 +90,13 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { private static final String[] ORIGINAL_EVENT_PROJECTION = new String[] {Events.ORIGINAL_EVENT, Events._ID}; + // Note that we use LIKE below for its case insensitivity + private static final String EVENT_AND_EMAIL = + Attendees.EVENT_ID + "=? AND "+ Attendees.ATTENDEE_EMAIL + " LIKE ?"; + private static final int ATTENDEE_STATUS_COLUMN_STATUS = 0; + private static final String[] ATTENDEE_STATUS_PROJECTION = + new String[] {Attendees.ATTENDEE_STATUS}; + public static final String CALENDAR_SELECTION = Calendars._SYNC_ACCOUNT + "=? AND " + Calendars._SYNC_ACCOUNT_TYPE + "=?"; private static final int CALENDAR_SELECTION_ID = 0; @@ -113,6 +120,7 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { private long mCalendarId = -1; private String mCalendarIdString; private String[] mCalendarIdArgument; + private String mEmailAddress; private ArrayList mDeletedIdList = new ArrayList(); private ArrayList mUploadedIdList = new ArrayList(); @@ -121,10 +129,10 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { public CalendarSyncAdapter(Mailbox mailbox, EasSyncService service) { super(mailbox, service); - + mEmailAddress = mAccount.mEmailAddress; Cursor c = mService.mContentResolver.query(Calendars.CONTENT_URI, new String[] {Calendars._ID}, CALENDAR_SELECTION, - new String[] {mAccount.mEmailAddress, Email.EXCHANGE_ACCOUNT_MANAGER_TYPE}, null); + new String[] {mEmailAddress, Email.EXCHANGE_ACCOUNT_MANAGER_TYPE}, null); try { if (c.moveToFirst()) { mCalendarId = c.getLong(CALENDAR_SELECTION_ID); @@ -241,7 +249,7 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { // Delete the calendar associated with this account // TODO Make sure the Events, etc. are also deleted mContentResolver.delete(Calendars.CONTENT_URI, CALENDAR_SELECTION, - new String[] {mAccount.mEmailAddress, Email.EXCHANGE_ACCOUNT_MANAGER_TYPE}); + new String[] {mEmailAddress, Email.EXCHANGE_ACCOUNT_MANAGER_TYPE}); } private void addOrganizerToAttendees(CalendarOperations ops, long eventId, @@ -257,6 +265,7 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { } attendeeCv.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER); attendeeCv.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); + attendeeCv.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED); if (eventId < 0) { ops.newAttendee(attendeeCv); } else { @@ -269,7 +278,7 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { throws IOException { ContentValues cv = new ContentValues(); cv.put(Events.CALENDAR_ID, mCalendarId); - cv.put(Events._SYNC_ACCOUNT, mAccount.mEmailAddress); + cv.put(Events._SYNC_ACCOUNT, mEmailAddress); cv.put(Events._SYNC_ACCOUNT_TYPE, Email.EXCHANGE_ACCOUNT_MANAGER_TYPE); cv.put(Events._SYNC_ID, serverId); cv.put(Events.HAS_ATTENDEE_DATA, 1); @@ -280,6 +289,7 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { String organizerEmail = null; int eventOffset = -1; int deleteOffset = -1; + int busyStatus = CalendarUtilities.BUSY_STATUS_TENTATIVE; boolean firstTag = true; long eventId = -1; @@ -370,7 +380,7 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { // we call exceptionsParser addOrganizerToAttendees(ops, eventId, organizerName, organizerEmail); organizerAdded = true; - exceptionsParser(ops, cv, attendeeValues, reminderMins); + exceptionsParser(ops, cv, attendeeValues, reminderMins, busyStatus); break; case Tags.CALENDAR_LOCATION: cv.put(Events.EVENT_LOCATION, getValue()); @@ -411,9 +421,10 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { ops.newExtendedProperty("meeting_status", getValue()); break; case Tags.CALENDAR_BUSY_STATUS: - int busyStatus = getValueInt(); - cv.put(Events.SELF_ATTENDEE_STATUS, - CalendarUtilities.selfAttendeeStatusFromBusyStatus(busyStatus)); + // We'll set the user's status in the Attendees table below + // Don't set selfAttendeeStatus or CalendarProvider will create a duplicate + // attendee! + busyStatus = getValueInt(); break; case Tags.CALENDAR_CATEGORIES: String categories = categoriesParser(ops); @@ -432,11 +443,24 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { } // Store email addresses of attendees (in a tokenizable string) in ExtendedProperties + // If the user is an attendee, set the attendee status using busyStatus (note that the + // busyStatus is inherited from the parent unless it's specified in the exception) + // Add the insert/update operation for each attendee (based on whether it's add/change) if (attendeeValues.size() > 0) { StringBuilder sb = new StringBuilder(); for (ContentValues attendee: attendeeValues) { - sb.append(attendee.getAsString(Attendees.ATTENDEE_EMAIL)); + String attendeeEmail = attendee.getAsString(Attendees.ATTENDEE_EMAIL); + sb.append(attendeeEmail); sb.append(ATTENDEE_TOKENIZER_DELIMITER); + if (mEmailAddress.equalsIgnoreCase(attendeeEmail)) { + attendee.put(Attendees.ATTENDEE_STATUS, + CalendarUtilities.attendeeStatusFromBusyStatus(busyStatus)); + } + if (eventId < 0) { + ops.newAttendee(attendee); + } else { + ops.updatedAttendee(attendee, eventId); + } } ops.newExtendedProperty("attendees", sb.toString()); } @@ -568,10 +592,11 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { } private void exceptionParser(CalendarOperations ops, ContentValues parentCv, - ArrayList attendeeValues, int reminderMins) throws IOException { + ArrayList attendeeValues, int reminderMins, int busyStatus) + throws IOException { ContentValues cv = new ContentValues(); cv.put(Events.CALENDAR_ID, mCalendarId); - cv.put(Events._SYNC_ACCOUNT, mAccount.mEmailAddress); + cv.put(Events._SYNC_ACCOUNT, mEmailAddress); cv.put(Events._SYNC_ACCOUNT_TYPE, Email.EXCHANGE_ACCOUNT_MANAGER_TYPE); // It appears that these values have to be copied from the parent if they are to appear @@ -585,7 +610,6 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { cv.put(Events.EVENT_TIMEZONE, parentCv.getAsString(Events.EVENT_TIMEZONE)); // This column is the key that links the exception to the serverId - // TODO Make sure calendar knows this isn't globally unique!! cv.put(Events.ORIGINAL_EVENT, parentCv.getAsString(Events._SYNC_ID)); String exceptionStartTime = "_noStartTime"; @@ -632,11 +656,10 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { cv.put(Events.VISIBILITY, encodeVisibility(getValueInt())); break; case Tags.CALENDAR_BUSY_STATUS: - int busyStatus = getValueInt(); - cv.put(Events.SELF_ATTENDEE_STATUS, - CalendarUtilities.selfAttendeeStatusFromBusyStatus(busyStatus)); + busyStatus = getValueInt(); + // Don't set selfAttendeeStatus or CalendarProvider will create a duplicate + // attendee! break; - // TODO How to handle these items that are linked to event id! // case Tags.CALENDAR_DTSTAMP: // ops.newExtendedProperty("dtstamp", getValue()); @@ -666,6 +689,12 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { // Also add the attendees, because they need to be copied over from the parent event if (attendeeValues != null) { for (ContentValues attValues: attendeeValues) { + // If this is the user, use his busy status for attendee status + String attendeeEmail = attValues.getAsString(Attendees.ATTENDEE_EMAIL); + if (mEmailAddress.equalsIgnoreCase(attendeeEmail)) { + attValues.put(Attendees.ATTENDEE_STATUS, + CalendarUtilities.attendeeStatusFromBusyStatus(busyStatus)); + } ops.newAttendee(attValues, exceptionStart); } } @@ -695,11 +724,12 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { } private void exceptionsParser(CalendarOperations ops, ContentValues cv, - ArrayList attendeeValues, int reminderMins) throws IOException { + ArrayList attendeeValues, int reminderMins, int busyStatus) + throws IOException { while (nextTag(Tags.CALENDAR_EXCEPTIONS) != END) { switch (tag) { case Tags.CALENDAR_EXCEPTION: - exceptionParser(ops, cv, attendeeValues, reminderMins); + exceptionParser(ops, cv, attendeeValues, reminderMins, busyStatus); break; default: skipTag(); @@ -782,11 +812,6 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { } } cv.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); - if (eventId < 0) { - ops.newAttendee(cv); - } else { - ops.updatedAttendee(cv, eventId); - } return cv; } @@ -1182,13 +1207,6 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { s.data(Tags.CALENDAR_TIME_ZONE, timeZone); } - // Busy status is only required for 2.5, but we need to send it with later - // versions as well, because if we don't, the server will clear it. - int selfAttendeeStatus = entityValues.getAsInteger(Events.SELF_ATTENDEE_STATUS); - s.data(Tags.CALENDAR_BUSY_STATUS, - Integer.toString(CalendarUtilities - .busyStatusFromSelfAttendeeStatus(selfAttendeeStatus))); - boolean allDay = false; if (entityValues.containsKey(Events.ALL_DAY)) { Integer ade = entityValues.getAsInteger(Events.ALL_DAY); @@ -1365,8 +1383,26 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { s.end(); // Attendees } + // Get busy status from Attendees table + long eventId = entityValues.getAsLong(Events._ID); + int busyStatus = CalendarUtilities.BUSY_STATUS_TENTATIVE; + Cursor c = mService.mContentResolver.query(ATTENDEES_URI, + ATTENDEE_STATUS_PROJECTION, EVENT_AND_EMAIL, + new String[] {Long.toString(eventId), mEmailAddress}, null); + if (c != null) { + try { + if (c.moveToFirst()) { + busyStatus = CalendarUtilities.busyStatusFromAttendeeStatus( + c.getInt(ATTENDEE_STATUS_COLUMN_STATUS)); + } + } finally { + c.close(); + } + } + s.data(Tags.CALENDAR_BUSY_STATUS, Integer.toString(busyStatus)); + // Meeting status, 0 = appointment, 1 = meeting, 3 = attendee - if (organizerEmail.equalsIgnoreCase(mAccount.mEmailAddress)) { + if (mEmailAddress.equalsIgnoreCase(organizerEmail)) { s.data(Tags.CALENDAR_MEETING_STATUS, hasAttendees ? "1" : "0"); } else { s.data(Tags.CALENDAR_MEETING_STATUS, "3"); @@ -1388,9 +1424,21 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { } // Send exception deleted flag if necessary + Integer deleted = entityValues.getAsInteger(Events.DELETED); + boolean isDeleted = deleted != null && deleted == 1; Integer eventStatus = entityValues.getAsInteger(Events.STATUS); - if (eventStatus != null && eventStatus.equals(Events.STATUS_CANCELED)) { + boolean isCanceled = eventStatus != null && eventStatus.equals(Events.STATUS_CANCELED); + if (isDeleted || isCanceled) { s.data(Tags.CALENDAR_EXCEPTION_IS_DELETED, "1"); + // If we're deleted, the UI will continue to show this exception until we mark + // it canceled, so we'll do that here... + if (isDeleted && !isCanceled) { + long eventId = entityValues.getAsLong(Events._ID); + ContentValues cv = new ContentValues(); + cv.put(Events.STATUS, Events.STATUS_CANCELED); + mService.mContentResolver.update( + ContentUris.withAppendedId(EVENTS_URI, eventId), cv, null, null); + } } } } @@ -1455,7 +1503,6 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { cr.query(EVENTS_URI, null, DIRTY_OR_MARKED_TOP_LEVEL_IN_CALENDAR, mCalendarIdArgument, null), cr); ContentValues cidValues = new ContentValues(); - String ourEmailAddress = mAccount.mEmailAddress; try { boolean first = true; @@ -1475,7 +1522,7 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { // EAS 2.5 needs: BusyStatus DtStamp EndTime Sensitivity StartTime TimeZone UID // We can generate all but what we're testing for below String organizerEmail = entityValues.getAsString(Events.ORGANIZER); - boolean selfOrganizer = organizerEmail.equalsIgnoreCase(ourEmailAddress); + boolean selfOrganizer = organizerEmail.equalsIgnoreCase(mEmailAddress); if (!entityValues.containsKey(Events.DTSTART) || (!entityValues.containsKey(Events.DURATION) && @@ -1707,6 +1754,9 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter { } } else if (!selfOrganizer) { // If we're not the organizer, see if we've changed our attendee status + // Note: Since we "own" our own calendar, selfAttendeeStatus will be + // correct when we set it from the UI. We NEVER set selfAttendeeStatus + // on downsync, however. int currentStatus = entityValues.getAsInteger(Events.SELF_ATTENDEE_STATUS); String adapterData = entityValues.getAsString(Events.SYNC_ADAPTER_DATA); int syncStatus = Attendees.ATTENDEE_STATUS_NONE; diff --git a/src/com/android/exchange/utility/CalendarUtilities.java b/src/com/android/exchange/utility/CalendarUtilities.java index a91628828..78c719f45 100644 --- a/src/com/android/exchange/utility/CalendarUtilities.java +++ b/src/com/android/exchange/utility/CalendarUtilities.java @@ -1228,21 +1228,21 @@ public class CalendarUtilities { * @param busyStatus the busy status, from EAS * @return the corresponding value for selfAttendeeStatus */ - static public int selfAttendeeStatusFromBusyStatus(int busyStatus) { - int selfAttendeeStatus; + static public int attendeeStatusFromBusyStatus(int busyStatus) { + int attendeeStatus; switch (busyStatus) { case BUSY_STATUS_BUSY: - selfAttendeeStatus = Attendees.ATTENDEE_STATUS_ACCEPTED; + attendeeStatus = Attendees.ATTENDEE_STATUS_ACCEPTED; break; case BUSY_STATUS_TENTATIVE: - selfAttendeeStatus = Attendees.ATTENDEE_STATUS_TENTATIVE; + attendeeStatus = Attendees.ATTENDEE_STATUS_TENTATIVE; break; case BUSY_STATUS_FREE: case BUSY_STATUS_OUT_OF_OFFICE: default: - selfAttendeeStatus = Attendees.ATTENDEE_STATUS_NONE; + attendeeStatus = Attendees.ATTENDEE_STATUS_NONE; } - return selfAttendeeStatus; + return attendeeStatus; } /** Get a busy status from a selfAttendeeStatus @@ -1250,7 +1250,7 @@ public class CalendarUtilities { * @param selfAttendeeStatus from CalendarProvider2 * @return the corresponding value of busy status */ - static public int busyStatusFromSelfAttendeeStatus(int selfAttendeeStatus) { + static public int busyStatusFromAttendeeStatus(int selfAttendeeStatus) { int busyStatus; switch (selfAttendeeStatus) { case Attendees.ATTENDEE_STATUS_DECLINED: diff --git a/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java b/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java index 2e3c02d27..f0bc4533d 100644 --- a/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java +++ b/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java @@ -717,34 +717,34 @@ public class CalendarUtilitiesTests extends AndroidTestCase { public void testSelfAttendeeStatusFromBusyStatus() { assertEquals(Attendees.ATTENDEE_STATUS_ACCEPTED, - CalendarUtilities.selfAttendeeStatusFromBusyStatus( + CalendarUtilities.attendeeStatusFromBusyStatus( CalendarUtilities.BUSY_STATUS_BUSY)); assertEquals(Attendees.ATTENDEE_STATUS_TENTATIVE, - CalendarUtilities.selfAttendeeStatusFromBusyStatus( + CalendarUtilities.attendeeStatusFromBusyStatus( CalendarUtilities.BUSY_STATUS_TENTATIVE)); assertEquals(Attendees.ATTENDEE_STATUS_NONE, - CalendarUtilities.selfAttendeeStatusFromBusyStatus( + CalendarUtilities.attendeeStatusFromBusyStatus( CalendarUtilities.BUSY_STATUS_FREE)); assertEquals(Attendees.ATTENDEE_STATUS_NONE, - CalendarUtilities.selfAttendeeStatusFromBusyStatus( + CalendarUtilities.attendeeStatusFromBusyStatus( CalendarUtilities.BUSY_STATUS_OUT_OF_OFFICE)); } public void testBusyStatusFromSelfStatus() { assertEquals(CalendarUtilities.BUSY_STATUS_FREE, - CalendarUtilities.busyStatusFromSelfAttendeeStatus( + CalendarUtilities.busyStatusFromAttendeeStatus( Attendees.ATTENDEE_STATUS_DECLINED)); assertEquals(CalendarUtilities.BUSY_STATUS_FREE, - CalendarUtilities.busyStatusFromSelfAttendeeStatus( + CalendarUtilities.busyStatusFromAttendeeStatus( Attendees.ATTENDEE_STATUS_NONE)); assertEquals(CalendarUtilities.BUSY_STATUS_FREE, - CalendarUtilities.busyStatusFromSelfAttendeeStatus( + CalendarUtilities.busyStatusFromAttendeeStatus( Attendees.ATTENDEE_STATUS_INVITED)); assertEquals(CalendarUtilities.BUSY_STATUS_TENTATIVE, - CalendarUtilities.busyStatusFromSelfAttendeeStatus( + CalendarUtilities.busyStatusFromAttendeeStatus( Attendees.ATTENDEE_STATUS_TENTATIVE)); assertEquals(CalendarUtilities.BUSY_STATUS_BUSY, - CalendarUtilities.busyStatusFromSelfAttendeeStatus( + CalendarUtilities.busyStatusFromAttendeeStatus( Attendees.ATTENDEE_STATUS_ACCEPTED)); } }