Fix the VCALENDAR we send with all day events
* We need to send date only (without time) in the VCALENDAR file for all-day events * Add unit test for this case Bug: 2561789 Change-Id: I33a43c7a248059c97482ca147a23af083744118a
This commit is contained in:
parent
1b3166e84a
commit
1c48450c02
@ -795,28 +795,32 @@ public class CalendarUtilities {
|
||||
* Generate an EAS formatted date/time string based on GMT. See below for details.
|
||||
*/
|
||||
static public String millisToEasDateTime(long millis) {
|
||||
return millisToEasDateTime(millis, sGmtTimeZone);
|
||||
return millisToEasDateTime(millis, sGmtTimeZone, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an EAS formatted local date/time string from a time and a time zone
|
||||
* Generate an EAS formatted local date/time string from a time and a time zone. If the final
|
||||
* argument is false, only a date will be returned (e.g. 20100331)
|
||||
* @param millis a time in milliseconds
|
||||
* @param tz a time zone
|
||||
* @return an EAS formatted string indicating the date/time in the given time zone
|
||||
* @param withTime if the time is to be included in the string
|
||||
* @return an EAS formatted string indicating the date (and time) in the given time zone
|
||||
*/
|
||||
static public String millisToEasDateTime(long millis, TimeZone tz) {
|
||||
static public String millisToEasDateTime(long millis, TimeZone tz, boolean withTime) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
GregorianCalendar cal = new GregorianCalendar(tz);
|
||||
cal.setTimeInMillis(millis);
|
||||
sb.append(cal.get(Calendar.YEAR));
|
||||
sb.append(formatTwo(cal.get(Calendar.MONTH) + 1));
|
||||
sb.append(formatTwo(cal.get(Calendar.DAY_OF_MONTH)));
|
||||
sb.append('T');
|
||||
sb.append(formatTwo(cal.get(Calendar.HOUR_OF_DAY)));
|
||||
sb.append(formatTwo(cal.get(Calendar.MINUTE)));
|
||||
sb.append(formatTwo(cal.get(Calendar.SECOND)));
|
||||
if (tz == sGmtTimeZone) {
|
||||
sb.append('Z');
|
||||
if (withTime) {
|
||||
sb.append('T');
|
||||
sb.append(formatTwo(cal.get(Calendar.HOUR_OF_DAY)));
|
||||
sb.append(formatTwo(cal.get(Calendar.MINUTE)));
|
||||
sb.append(formatTwo(cal.get(Calendar.SECOND)));
|
||||
if (tz == sGmtTimeZone) {
|
||||
sb.append('Z');
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
@ -1245,15 +1249,28 @@ public class CalendarUtilities {
|
||||
// Our default vcalendar time zone is UTC, but this will change (below) if we're
|
||||
// sending a recurring event, in which case we use local time
|
||||
TimeZone vCalendarTimeZone = sGmtTimeZone;
|
||||
String vCalendarTimeZoneSuffix = "";
|
||||
String vCalendarDateSuffix = "";
|
||||
|
||||
// Check for all day event
|
||||
boolean allDayEvent = false;
|
||||
if (entityValues.containsKey(Events.ALL_DAY)) {
|
||||
Integer ade = entityValues.getAsInteger(Events.ALL_DAY);
|
||||
allDayEvent = (ade != null) && (ade == 1);
|
||||
if (allDayEvent) {
|
||||
// Example: DTSTART;VALUE=DATE:20100331 (all day event)
|
||||
vCalendarDateSuffix = ";VALUE=DATE";
|
||||
}
|
||||
}
|
||||
|
||||
// If we're inviting people and the meeting is recurring, we need to send our time zone
|
||||
// information and make sure to send DTSTART/DTEND in local time
|
||||
if (!isReply && entityValues.containsKey(Events.RRULE)) {
|
||||
// information and make sure to send DTSTART/DTEND in local time (unless, of course,
|
||||
// this is an all-day event)
|
||||
if (!isReply && entityValues.containsKey(Events.RRULE) && !allDayEvent) {
|
||||
vCalendarTimeZone = TimeZone.getDefault();
|
||||
// Write the VTIMEZONE block to the writer
|
||||
timeZoneToVTimezone(vCalendarTimeZone, ics);
|
||||
vCalendarTimeZoneSuffix = ";TZID=" + vCalendarTimeZone.getID();
|
||||
// Example: DTSTART;TZID=US/Pacific:20100331T124500
|
||||
vCalendarDateSuffix = ";TZID=" + vCalendarTimeZone.getID();
|
||||
}
|
||||
|
||||
ics.writeTag("BEGIN", "VEVENT");
|
||||
@ -1272,23 +1289,24 @@ public class CalendarUtilities {
|
||||
|
||||
long startTime = entityValues.getAsLong(Events.DTSTART);
|
||||
if (startTime != 0) {
|
||||
ics.writeTag("DTSTART" + vCalendarTimeZoneSuffix,
|
||||
millisToEasDateTime(startTime, vCalendarTimeZone));
|
||||
ics.writeTag("DTSTART" + vCalendarDateSuffix,
|
||||
millisToEasDateTime(startTime, vCalendarTimeZone, !allDayEvent));
|
||||
}
|
||||
|
||||
// If this is an Exception, we send the recurrence-id, which is just the original
|
||||
// instance time
|
||||
if (isException) {
|
||||
long originalTime = entityValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
|
||||
ics.writeTag("RECURRENCE-ID" + vCalendarTimeZoneSuffix,
|
||||
millisToEasDateTime(originalTime, vCalendarTimeZone));
|
||||
ics.writeTag("RECURRENCE-ID" + vCalendarDateSuffix,
|
||||
millisToEasDateTime(originalTime, vCalendarTimeZone, !allDayEvent));
|
||||
}
|
||||
|
||||
if (!entityValues.containsKey(Events.DURATION)) {
|
||||
if (entityValues.containsKey(Events.DTEND)) {
|
||||
ics.writeTag("DTEND" + vCalendarTimeZoneSuffix,
|
||||
ics.writeTag("DTEND" + vCalendarDateSuffix,
|
||||
millisToEasDateTime(
|
||||
entityValues.getAsLong(Events.DTEND), vCalendarTimeZone));
|
||||
entityValues.getAsLong(Events.DTEND), vCalendarTimeZone,
|
||||
!allDayEvent));
|
||||
}
|
||||
} else {
|
||||
// Convert this into millis and add it to DTSTART for DTEND
|
||||
@ -1300,9 +1318,9 @@ public class CalendarUtilities {
|
||||
} catch (ParseException e) {
|
||||
// We'll use the default in this case
|
||||
}
|
||||
ics.writeTag("DTEND" + vCalendarTimeZoneSuffix,
|
||||
ics.writeTag("DTEND" + vCalendarDateSuffix,
|
||||
millisToEasDateTime(
|
||||
startTime + durationMillis, vCalendarTimeZone));
|
||||
startTime + durationMillis, vCalendarTimeZone, !allDayEvent));
|
||||
}
|
||||
|
||||
String location = null;
|
||||
|
@ -265,6 +265,70 @@ public class CalendarUtilitiesTests extends AndroidTestCase {
|
||||
//TODO Check the contents of the attachment using an iCalendar parser
|
||||
}
|
||||
|
||||
public void testCreateMessageForEntity_Invite_AllDay() throws IOException {
|
||||
// Set up the "event"
|
||||
String title = "Discuss Unit Tests";
|
||||
Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title);
|
||||
entity.getEntityValues().put(Events.ALL_DAY, 1);
|
||||
|
||||
// Create a dummy account for the attendee
|
||||
Account account = new Account();
|
||||
account.mEmailAddress = ORGANIZER;
|
||||
|
||||
// The uid is required, but can be anything
|
||||
String uid = "31415926535";
|
||||
|
||||
// Create the outgoing message
|
||||
Message msg = CalendarUtilities.createMessageForEntity(mContext, entity,
|
||||
Message.FLAG_OUTGOING_MEETING_INVITE, uid, account);
|
||||
|
||||
// First, we should have a message
|
||||
assertNotNull(msg);
|
||||
|
||||
// Now check some of the fields of the message
|
||||
assertEquals(Address.pack(new Address[] {new Address(ATTENDEE)}), msg.mTo);
|
||||
String accept = getContext().getResources().getString(R.string.meeting_invitation, title);
|
||||
assertEquals(accept, msg.mSubject);
|
||||
|
||||
// And make sure we have an attachment
|
||||
assertNotNull(msg.mAttachments);
|
||||
assertEquals(1, msg.mAttachments.size());
|
||||
Attachment att = msg.mAttachments.get(0);
|
||||
// And that the attachment has the correct elements
|
||||
assertEquals("invite.ics", att.mFileName);
|
||||
assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART,
|
||||
att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART);
|
||||
assertEquals("text/calendar; method=REQUEST", att.mMimeType);
|
||||
assertNotNull(att.mContentBytes);
|
||||
assertEquals(att.mSize, att.mContentBytes.length);
|
||||
|
||||
// We'll check the contents of the ics file here
|
||||
BlockHash vcalendar = parseIcsContent(att.mContentBytes);
|
||||
assertNotNull(vcalendar);
|
||||
|
||||
// We should have a VCALENDAR with a REQUEST method
|
||||
assertEquals("VCALENDAR", vcalendar.name);
|
||||
assertEquals("REQUEST", vcalendar.get("METHOD"));
|
||||
|
||||
// We should have one block under VCALENDAR
|
||||
assertEquals(1, vcalendar.blocks.size());
|
||||
BlockHash vevent = vcalendar.blocks.get(0);
|
||||
// It's a VEVENT with the following fields
|
||||
assertEquals("VEVENT", vevent.name);
|
||||
assertEquals("Meeting Location", vevent.get("LOCATION"));
|
||||
assertEquals("0", vevent.get("SEQUENCE"));
|
||||
assertEquals("Discuss Unit Tests", vevent.get("SUMMARY"));
|
||||
assertEquals(uid, vevent.get("UID"));
|
||||
assertEquals("MAILTO:" + ATTENDEE,
|
||||
vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE"));
|
||||
|
||||
// These next two fields should have a date only
|
||||
assertEquals("20100412", vevent.get("DTSTART;VALUE=DATE"));
|
||||
assertEquals("20100412", vevent.get("DTEND;VALUE=DATE"));
|
||||
// This should be set to TRUE for all-day events
|
||||
assertEquals("TRUE", vevent.get("X-MICROSOFT-CDO-ALLDAYEVENT"));
|
||||
}
|
||||
|
||||
public void testCreateMessageForEntity_Invite() throws IOException {
|
||||
// Set up the "event"
|
||||
String title = "Discuss Unit Tests";
|
||||
@ -320,6 +384,14 @@ public class CalendarUtilitiesTests extends AndroidTestCase {
|
||||
assertEquals(uid, vevent.get("UID"));
|
||||
assertEquals("MAILTO:" + ATTENDEE,
|
||||
vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE"));
|
||||
|
||||
// These next two fields should exist (without the VALUE=DATE suffix)
|
||||
assertNotNull(vevent.get("DTSTART"));
|
||||
assertNotNull(vevent.get("DTEND"));
|
||||
assertNull(vevent.get("DTSTART;VALUE=DATE"));
|
||||
assertNull(vevent.get("DTEND;VALUE=DATE"));
|
||||
// This shouldn't exist for this event
|
||||
assertNull(vevent.get("X-MICROSOFT-CDO-ALLDAYEVENT"));
|
||||
}
|
||||
|
||||
public void testCreateMessageForEntity_Exception_Cancel() throws IOException {
|
||||
@ -396,8 +468,8 @@ public class CalendarUtilitiesTests extends AndroidTestCase {
|
||||
long originalTime = entityValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
|
||||
assertNotSame(0, originalTime);
|
||||
// For an exception, RECURRENCE-ID is critical
|
||||
assertEquals(CalendarUtilities.millisToEasDateTime(originalTime, timeZone),
|
||||
vevent.get("RECURRENCE-ID" + ";TZID=" + timeZone.getID()));
|
||||
assertEquals(CalendarUtilities.millisToEasDateTime(originalTime, timeZone,
|
||||
true /*withTime*/), vevent.get("RECURRENCE-ID" + ";TZID=" + timeZone.getID()));
|
||||
}
|
||||
|
||||
public void testUtcOffsetString() {
|
||||
|
Loading…
Reference in New Issue
Block a user