Additional work on new Event upload to EAS server
* Added support for reminders and recurrences * Note that Duration class is copied from CalendarProvider with only formatting changes Change-Id: Icf399df422f813ba8e7880646bfbc96a2156a204
This commit is contained in:
parent
d99dbf01fb
commit
dc6930c0b3
@ -21,6 +21,7 @@ import com.android.email.provider.EmailContent.Mailbox;
|
||||
import com.android.exchange.Eas;
|
||||
import com.android.exchange.EasSyncService;
|
||||
import com.android.exchange.utility.CalendarUtilities;
|
||||
import com.android.exchange.utility.Duration;
|
||||
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentProviderResult;
|
||||
@ -34,6 +35,7 @@ import android.content.Entity.NamedContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.pim.DateException;
|
||||
import android.provider.Calendar;
|
||||
import android.provider.Calendar.Attendees;
|
||||
import android.provider.Calendar.Calendars;
|
||||
@ -47,6 +49,7 @@ import android.util.Log;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
@ -69,6 +72,8 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
Calendars._SYNC_ACCOUNT + "=? AND " + Calendars._SYNC_ACCOUNT_TYPE + "=?";
|
||||
private static final int CALENDAR_SELECTION_ID = 0;
|
||||
|
||||
private static final String CATEGORY_TOKENIZER_DELIMITER = "\\";
|
||||
|
||||
private static final ContentProviderOperation PLACEHOLDER_OPERATION =
|
||||
ContentProviderOperation.newInsert(Uri.EMPTY).build();
|
||||
|
||||
@ -173,7 +178,10 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
|
||||
@Override
|
||||
public void wipe() {
|
||||
mContentResolver.delete(mAccountUri, null, null);
|
||||
// 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, Eas.ACCOUNT_MANAGER_TYPE});
|
||||
}
|
||||
|
||||
public void addEvent(CalendarOperations ops, String serverId, boolean update)
|
||||
@ -243,9 +251,6 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
case Tags.CALENDAR_BODY:
|
||||
cv.put(Events.DESCRIPTION, getValue());
|
||||
break;
|
||||
case Tags.CALENDAR_CATEGORIES:
|
||||
categoriesParser(ops);
|
||||
break;
|
||||
case Tags.CALENDAR_TIME_ZONE:
|
||||
TimeZone tz = CalendarUtilities.parseTimeZone(getValue());
|
||||
if (tz != null) {
|
||||
@ -255,12 +260,12 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
}
|
||||
break;
|
||||
case Tags.CALENDAR_START_TIME:
|
||||
startTime = CalendarUtilities.parseDateTime(getValue());
|
||||
startTime = CalendarUtilities.parseDateTimeToMillis(getValue());
|
||||
cv.put(Events.DTSTART, startTime);
|
||||
cv.put(Events.ORIGINAL_INSTANCE_TIME, startTime);
|
||||
break;
|
||||
case Tags.CALENDAR_END_TIME:
|
||||
endTime = CalendarUtilities.parseDateTime(getValue());
|
||||
endTime = CalendarUtilities.parseDateTimeToMillis(getValue());
|
||||
break;
|
||||
case Tags.CALENDAR_EXCEPTIONS:
|
||||
exceptionsParser(ops, cv);
|
||||
@ -284,26 +289,32 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
case Tags.CALENDAR_SENSITIVITY:
|
||||
cv.put(Events.VISIBILITY, encodeVisibility(getValueInt()));
|
||||
break;
|
||||
case Tags.CALENDAR_UID:
|
||||
ops.newExtendedProperty("uid", getValue());
|
||||
break;
|
||||
case Tags.CALENDAR_ORGANIZER_NAME:
|
||||
organizerName = getValue();
|
||||
break;
|
||||
case Tags.CALENDAR_REMINDER_MINS_BEFORE:
|
||||
ops.newReminder(getValueInt());
|
||||
cv.put(Events.HAS_ALARM, 1);
|
||||
break;
|
||||
// The following are fields we should save (for changes), though they don't
|
||||
// relate to data used by CalendarProvider at this point
|
||||
case Tags.CALENDAR_UID:
|
||||
ops.newExtendedProperty("uid", getValue());
|
||||
break;
|
||||
case Tags.CALENDAR_DTSTAMP:
|
||||
ops.newExtendedProperty("dtstamp", getValue());
|
||||
break;
|
||||
case Tags.CALENDAR_MEETING_STATUS:
|
||||
// TODO Try to fit this into Calendar scheme
|
||||
ops.newExtendedProperty("meeting_status", getValue());
|
||||
break;
|
||||
case Tags.CALENDAR_BUSY_STATUS:
|
||||
// TODO Try to fit this into Calendar scheme
|
||||
ops.newExtendedProperty("busy_status", getValue());
|
||||
break;
|
||||
case Tags.CALENDAR_REMINDER_MINS_BEFORE:
|
||||
ops.newReminder(getValueInt());
|
||||
cv.put(Events.HAS_ALARM, 1);
|
||||
case Tags.CALENDAR_CATEGORIES:
|
||||
String categories = categoriesParser(ops);
|
||||
if (categories.length() > 0) {
|
||||
ops.newExtendedProperty("categories", categories);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
skipTag();
|
||||
@ -399,7 +410,7 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
switch (tag) {
|
||||
case Tags.CALENDAR_EXCEPTION_START_TIME:
|
||||
cv.put(Events.ORIGINAL_INSTANCE_TIME,
|
||||
CalendarUtilities.parseDateTime(getValue()));
|
||||
CalendarUtilities.parseDateTimeToMillis(getValue()));
|
||||
break;
|
||||
case Tags.CALENDAR_EXCEPTION_IS_DELETED:
|
||||
if (getValueInt() == 1) {
|
||||
@ -416,10 +427,10 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
cv.put(Events.DESCRIPTION, getValue());
|
||||
break;
|
||||
case Tags.CALENDAR_START_TIME:
|
||||
cv.put(Events.DTSTART, CalendarUtilities.parseDateTime(getValue()));
|
||||
cv.put(Events.DTSTART, CalendarUtilities.parseDateTimeToMillis(getValue()));
|
||||
break;
|
||||
case Tags.CALENDAR_END_TIME:
|
||||
cv.put(Events.DTEND, CalendarUtilities.parseDateTime(getValue()));
|
||||
cv.put(Events.DTEND, CalendarUtilities.parseDateTimeToMillis(getValue()));
|
||||
break;
|
||||
case Tags.CALENDAR_LOCATION:
|
||||
cv.put(Events.EVENT_LOCATION, getValue());
|
||||
@ -500,15 +511,20 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
private void categoriesParser(CalendarOperations ops) throws IOException {
|
||||
private String categoriesParser(CalendarOperations ops) throws IOException {
|
||||
StringBuilder categories = new StringBuilder();
|
||||
while (nextTag(Tags.CALENDAR_CATEGORIES) != END) {
|
||||
switch (tag) {
|
||||
case Tags.CALENDAR_CATEGORY:
|
||||
// TODO Handle categories
|
||||
// TODO Handle categories (there's no similar concept for gdata AFAIK)
|
||||
// We need to save them and spit them back when we update the event
|
||||
categories.append(getValue());
|
||||
categories.append(CATEGORY_TOKENIZER_DELIMITER);
|
||||
default:
|
||||
skipTag();
|
||||
}
|
||||
}
|
||||
return categories.toString();
|
||||
}
|
||||
|
||||
private String attendeesParser(CalendarOperations ops, String organizerName,
|
||||
@ -918,11 +934,21 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
boolean first = true;
|
||||
while (ei.hasNext()) {
|
||||
Entity entity = ei.next();
|
||||
String clientId = null;
|
||||
String clientId = "uid_" + mMailbox.mId + '_' + System.currentTimeMillis();
|
||||
|
||||
// For each of these entities, create the change commands
|
||||
ContentValues entityValues = entity.getEntityValues();
|
||||
String serverId = entityValues.getAsString(Events._SYNC_ID);
|
||||
|
||||
// EAS 2.5 needs: BusyStatus DtStamp EndTime Sensitivity StartTime TimeZone UID
|
||||
// We can generate all but what we're testing for below
|
||||
if (!entityValues.containsKey(Events.DTSTART)
|
||||
|| !entityValues.containsKey(Events.DURATION)) {
|
||||
continue;
|
||||
}
|
||||
// TODO Handle BusyStatus for EAS 2.5
|
||||
// What should it be??
|
||||
|
||||
// Ignore exceptions (will have Events.ORIGINAL_EVENT)
|
||||
|
||||
if (first) {
|
||||
@ -932,7 +958,6 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
}
|
||||
if (serverId == null) {
|
||||
// This is a new event; create a clientId
|
||||
clientId = "new_" + mMailbox.mId + '_' + System.currentTimeMillis();
|
||||
userLog("Creating new event with clientId: ", clientId);
|
||||
s.start(Tags.SYNC_ADD).data(Tags.SYNC_CLIENT_ID, clientId);
|
||||
// And save it in the Event as the local id
|
||||
@ -963,29 +988,41 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
s.data(Tags.CALENDAR_ALL_DAY_EVENT,
|
||||
entityValues.getAsInteger(Events.ALL_DAY).toString());
|
||||
}
|
||||
if (entityValues.containsKey(Events.DTSTART)) {
|
||||
long startTime = entityValues.getAsLong(Events.DTSTART);
|
||||
s.data(Tags.CALENDAR_START_TIME,
|
||||
CalendarUtilities.millisToEasDateTime(startTime));
|
||||
|
||||
long startTime = entityValues.getAsLong(Events.DTSTART);
|
||||
s.data(Tags.CALENDAR_START_TIME,
|
||||
CalendarUtilities.millisToEasDateTime(startTime));
|
||||
// Convert this into millis and add it to DTSTART for DTEND
|
||||
// We'll use 1 hour as a default
|
||||
long durationMillis = HOURS;
|
||||
Duration duration = new Duration();
|
||||
try {
|
||||
duration.parse(entityValues.getAsString(Events.DURATION));
|
||||
} catch (DateException e) {
|
||||
// Can't do much about this; use the default (1 hour)
|
||||
}
|
||||
s.data(Tags.CALENDAR_END_TIME,
|
||||
CalendarUtilities.millisToEasDateTime(startTime + durationMillis));
|
||||
if (entityValues.containsKey(Events.DTEND)) {
|
||||
long endTime = entityValues.getAsLong(Events.DTEND);
|
||||
s.data(Tags.CALENDAR_END_TIME,
|
||||
CalendarUtilities.millisToEasDateTime(endTime));
|
||||
// TODO Use this to determine last date; it's NOT the same as EAS DTEND
|
||||
//long endTime = entityValues.getAsLong(Events.DTEND);
|
||||
//s.data(Tags.CALENDAR_END_TIME,
|
||||
// CalendarUtilities.millisToEasDateTime(endTime));
|
||||
}
|
||||
s.data(Tags.CALENDAR_DTSTAMP,
|
||||
CalendarUtilities.millisToEasDateTime(System.currentTimeMillis()));
|
||||
|
||||
// Our clientId (for new calendar items) is used for UID
|
||||
if (clientId != null) {
|
||||
s.data(Tags.CALENDAR_UID, clientId);
|
||||
}
|
||||
|
||||
// A time zone is required in all EAS events; we'll use the default if none
|
||||
// is set.
|
||||
String timeZoneName;
|
||||
if (entityValues.containsKey(Events.EVENT_TIMEZONE)) {
|
||||
String timeZoneName = entityValues.getAsString(Events.EVENT_TIMEZONE);
|
||||
String x = CalendarUtilities.timeZoneToTZIString(timeZoneName);
|
||||
s.data(Tags.CALENDAR_TIME_ZONE, x);
|
||||
timeZoneName = entityValues.getAsString(Events.EVENT_TIMEZONE);
|
||||
} else {
|
||||
timeZoneName = TimeZone.getDefault().getID();
|
||||
}
|
||||
String x = CalendarUtilities.timeZoneToTZIString(timeZoneName);
|
||||
s.data(Tags.CALENDAR_TIME_ZONE, x);
|
||||
|
||||
if (entityValues.containsKey(Events.EVENT_LOCATION)) {
|
||||
s.data(Tags.CALENDAR_LOCATION,
|
||||
entityValues.getAsString(Events.EVENT_LOCATION));
|
||||
@ -1011,6 +1048,13 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
if (entityValues.containsKey(Events.VISIBILITY)) {
|
||||
s.data(Tags.CALENDAR_SENSITIVITY,
|
||||
decodeVisibility(entityValues.getAsInteger(Events.VISIBILITY)));
|
||||
} else {
|
||||
// Private if not set
|
||||
s.data(Tags.CALENDAR_SENSITIVITY, "1");
|
||||
}
|
||||
if (entityValues.containsKey(Events.RRULE)) {
|
||||
CalendarUtilities.recurrenceFromRrule(
|
||||
entityValues.getAsString(Events.RRULE), startTime, s);
|
||||
}
|
||||
|
||||
// Handle associated data EXCEPT for attendees, which have to be grouped
|
||||
@ -1020,11 +1064,27 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
ContentValues ncvValues = ncv.values;
|
||||
if (ncvUri.equals(ExtendedProperties.CONTENT_URI)) {
|
||||
if (ncvValues.containsKey("uid")) {
|
||||
s.data(Tags.CALENDAR_UID, ncvValues.getAsString("uid"));
|
||||
clientId = ncvValues.getAsString("uid");
|
||||
s.data(Tags.CALENDAR_UID, clientId);
|
||||
}
|
||||
if (ncvValues.containsKey("dtstamp")) {
|
||||
s.data(Tags.CALENDAR_DTSTAMP, ncvValues.getAsString("dtstamp"));
|
||||
}
|
||||
if (ncvValues.containsKey("categories")) {
|
||||
// Send all the categories back to the server
|
||||
// We've saved them as a String of delimited tokens
|
||||
String categories = ncvValues.getAsString("categories");
|
||||
StringTokenizer st =
|
||||
new StringTokenizer(categories, CATEGORY_TOKENIZER_DELIMITER);
|
||||
if (st.countTokens() > 0) {
|
||||
s.start(Tags.CALENDAR_CATEGORIES);
|
||||
while (st.hasMoreTokens()) {
|
||||
String category = st.nextToken();
|
||||
s.data(Tags.CALENDAR_CATEGORY, category);
|
||||
}
|
||||
s.end();
|
||||
}
|
||||
}
|
||||
} else if (ncvUri.equals(Reminders.CONTENT_URI)) {
|
||||
if (ncvValues.containsKey(Reminders.MINUTES)) {
|
||||
s.data(Tags.CALENDAR_REMINDER_MINS_BEFORE,
|
||||
@ -1033,6 +1093,10 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// We've got to send a UID. If the event is new, we've generated one; if not,
|
||||
// we should have gotten one from extended properties.
|
||||
s.data(Tags.CALENDAR_UID, clientId);
|
||||
|
||||
// Handle attendee data here; keep track of organizer and stream it afterward
|
||||
boolean hasAttendees = false;
|
||||
String organizerName = null;
|
||||
@ -1078,27 +1142,9 @@ public class CalendarSyncAdapter extends AbstractSyncAdapter {
|
||||
if (organizerName != null) {
|
||||
s.data(Tags.CALENDAR_ORGANIZER_NAME, organizerName);
|
||||
}
|
||||
// case Tags.CALENDAR_CATEGORIES:
|
||||
// categoriesParser(ops);
|
||||
// break;
|
||||
// case Tags.CALENDAR_EXCEPTIONS:
|
||||
// exceptionsParser(ops, cv);
|
||||
// break;
|
||||
// case Tags.CALENDAR_RECURRENCE:
|
||||
// String rrule = recurrenceParser(ops);
|
||||
// if (rrule != null) {
|
||||
// cv.put(Events.RRULE, rrule);
|
||||
// }
|
||||
// break;
|
||||
// case Tags.CALENDAR_MEETING_STATUS:
|
||||
// // TODO Try to fit this into Calendar scheme
|
||||
// ops.newExtendedProperty("meeting_status", getValue());
|
||||
// break;
|
||||
// case Tags.CALENDAR_BUSY_STATUS:
|
||||
// // TODO Try to fit this into Calendar scheme
|
||||
// ops.newExtendedProperty("busy_status", getValue());
|
||||
// break;
|
||||
|
||||
s.end().end(); // ApplicationData & Change
|
||||
mUpdatedIdList.add(entityValues.getAsLong(Events._ID));
|
||||
}
|
||||
|
@ -254,7 +254,7 @@ public class FolderSyncParser extends AbstractSyncParser {
|
||||
cv.put(Calendars.SELECTED, 1);
|
||||
cv.put(Calendars.HIDDEN, 0);
|
||||
// TODO Find out how to set color!!
|
||||
cv.put(Calendars.COLOR, -14069085 /* blue */);
|
||||
cv.put(Calendars.COLOR, 0xFF228B22 /*green*/);
|
||||
cv.put(Calendars.TIMEZONE, Time.getCurrentTimezone());
|
||||
cv.put(Calendars.ACCESS_LEVEL, Calendars.OWNER_ACCESS);
|
||||
cv.put(Calendars.OWNER_ACCOUNT, mAccount.mEmailAddress);
|
||||
|
@ -17,11 +17,14 @@
|
||||
package com.android.exchange.utility;
|
||||
|
||||
import com.android.exchange.Eas;
|
||||
import com.android.exchange.adapter.Serializer;
|
||||
import com.android.exchange.adapter.Tags;
|
||||
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
@ -318,7 +321,7 @@ public class CalendarUtilities {
|
||||
* @param DateTime string from Exchange server
|
||||
* @return the time in milliseconds (since Jan 1, 1970)
|
||||
*/
|
||||
static public long parseDateTime(String date) {
|
||||
static public long parseDateTimeToMillis(String date) {
|
||||
// Format for calendar date strings is 20090211T180303Z
|
||||
GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)),
|
||||
Integer.parseInt(date.substring(4, 6)) - 1, Integer.parseInt(date.substring(6, 8)),
|
||||
@ -328,6 +331,21 @@ public class CalendarUtilities {
|
||||
return cal.getTimeInMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a GregorianCalendar from a date string that represents a date/time in GMT
|
||||
* @param DateTime string from Exchange server
|
||||
* @return the GregorianCalendar
|
||||
*/
|
||||
static public GregorianCalendar parseDateTimeToCalendar(String date) {
|
||||
// Format for calendar date strings is 20090211T180303Z
|
||||
GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)),
|
||||
Integer.parseInt(date.substring(4, 6)) - 1, Integer.parseInt(date.substring(6, 8)),
|
||||
Integer.parseInt(date.substring(9, 11)), Integer.parseInt(date.substring(11, 13)),
|
||||
Integer.parseInt(date.substring(13, 15)));
|
||||
cal.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
return cal;
|
||||
}
|
||||
|
||||
static String formatTwo(int num) {
|
||||
if (num <= 12) {
|
||||
return sTwoCharacterNumbers[num];
|
||||
@ -378,6 +396,116 @@ public class CalendarUtilities {
|
||||
rrule.append(";BYMONTHDAY=" + dom);
|
||||
}
|
||||
|
||||
static String generateEasDayOfWeek(String dow) {
|
||||
int bit = 1;
|
||||
for (String token: sDayTokens) {
|
||||
if (dow.equals(token)) {
|
||||
break;
|
||||
} else {
|
||||
bit <<= 1;
|
||||
}
|
||||
}
|
||||
return Integer.toString(bit);
|
||||
}
|
||||
|
||||
static String tokenFromRrule(String rrule, String token) {
|
||||
int start = rrule.indexOf(token);
|
||||
if (start < 0) return null;
|
||||
int len = rrule.length();
|
||||
start += token.length();
|
||||
int end = start;
|
||||
char c;
|
||||
do {
|
||||
c = rrule.charAt(end++);
|
||||
if (!Character.isLetterOrDigit(c) || (end == len)) {
|
||||
if (end == len) end++;
|
||||
return rrule.substring(start, end -1);
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write recurrence information to EAS based on the RRULE in CalendarProvider
|
||||
* @param rrule the RRULE, from CalendarProvider
|
||||
* @param startTime, the DTSTART of this Event
|
||||
* @param s the Serializer we're using to write WBXML data
|
||||
* @throws IOException
|
||||
*/
|
||||
// NOTE: For the moment, we're only parsing recurrence types that are supported by the
|
||||
// Calendar app UI, which is a small subset of possible recurrence types
|
||||
// This code must be updated when the Calendar adds new functionality
|
||||
static public void recurrenceFromRrule(String rrule, long startTime, Serializer s)
|
||||
throws IOException {
|
||||
Log.d("RRULE", "rule: " + rrule);
|
||||
String freq = tokenFromRrule(rrule, "FREQ=");
|
||||
// If there's no FREQ=X, then we don't write a recurrence
|
||||
// Note that we duplicate s.start(Tags.CALENDAR_RECURRENCE); s.end(); to prevent the
|
||||
// possibility of writing out a partial recurrence stanza
|
||||
if (freq != null) {
|
||||
if (freq.equals("DAILY")) {
|
||||
s.start(Tags.CALENDAR_RECURRENCE);
|
||||
s.data(Tags.CALENDAR_RECURRENCE_TYPE, "0");
|
||||
s.data(Tags.CALENDAR_RECURRENCE_INTERVAL, "1");
|
||||
s.end();
|
||||
} else if (freq.equals("WEEKLY")) {
|
||||
s.start(Tags.CALENDAR_RECURRENCE);
|
||||
s.data(Tags.CALENDAR_RECURRENCE_TYPE, "1");
|
||||
s.data(Tags.CALENDAR_RECURRENCE_INTERVAL, "1");
|
||||
// Requires a day of week (whereas RRULE does not)
|
||||
String byDay = tokenFromRrule(rrule, "BYDAY=");
|
||||
if (byDay != null) {
|
||||
s.data(Tags.CALENDAR_RECURRENCE_DAYOFWEEK, generateEasDayOfWeek(byDay));
|
||||
}
|
||||
s.end();
|
||||
} else if (freq.equals("MONTHLY")) {
|
||||
String byMonthDay = tokenFromRrule(rrule, "BYMONTHDAY=");
|
||||
if (byMonthDay != null) {
|
||||
// The nth day of the month
|
||||
s.start(Tags.CALENDAR_RECURRENCE);
|
||||
s.data(Tags.CALENDAR_RECURRENCE_TYPE, "2");
|
||||
s.data(Tags.CALENDAR_RECURRENCE_DAYOFMONTH, byMonthDay);
|
||||
s.end();
|
||||
} else {
|
||||
String byDay = tokenFromRrule(rrule, "BYDAY=");
|
||||
String bareByDay;
|
||||
if (byDay != null) {
|
||||
// This can be 1WE (1st Wednesday) or -1FR (last Friday)
|
||||
int wom = byDay.charAt(0);
|
||||
if (wom == '-') {
|
||||
// -1 is the only legal case (last week) Use "5" for EAS
|
||||
wom = 5;
|
||||
bareByDay = byDay.substring(2);
|
||||
} else {
|
||||
wom = wom - '0';
|
||||
bareByDay = byDay.substring(1);
|
||||
}
|
||||
s.start(Tags.CALENDAR_RECURRENCE);
|
||||
s.data(Tags.CALENDAR_RECURRENCE_TYPE, "3");
|
||||
s.data(Tags.CALENDAR_RECURRENCE_WEEKOFMONTH, Integer.toString(wom));
|
||||
s.data(Tags.CALENDAR_RECURRENCE_DAYOFWEEK, generateEasDayOfWeek(bareByDay));
|
||||
s.end();
|
||||
}
|
||||
}
|
||||
} else if (freq.equals("YEARLY")) {
|
||||
String byMonth = tokenFromRrule(rrule, "BYMONTH=");
|
||||
String byMonthDay = tokenFromRrule(rrule, "BYMONTHDAY=");
|
||||
if (byMonth == null || byMonthDay == null) {
|
||||
// Calculate the month and day from the startDate
|
||||
GregorianCalendar cal = new GregorianCalendar();
|
||||
cal.setTimeInMillis(startTime);
|
||||
cal.setTimeZone(TimeZone.getDefault());
|
||||
byMonth = Integer.toString(cal.get(Calendar.MONTH) + 1);
|
||||
byMonthDay = Integer.toString(cal.get(Calendar.DAY_OF_MONTH));
|
||||
}
|
||||
s.start(Tags.CALENDAR_RECURRENCE);
|
||||
s.data(Tags.CALENDAR_RECURRENCE_TYPE, "5");
|
||||
s.data(Tags.CALENDAR_RECURRENCE_DAYOFMONTH, byMonthDay);
|
||||
s.data(Tags.CALENDAR_RECURRENCE_MONTHOFYEAR, byMonth);
|
||||
s.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static public String rruleFromRecurrence(int type, int occurrences, int interval, int dow,
|
||||
int dom, int wom, int moy, String until) {
|
||||
StringBuilder rrule = new StringBuilder("FREQ=" + sTypeToFreq[type]);
|
||||
@ -426,4 +554,4 @@ public class CalendarUtilities {
|
||||
|
||||
return rrule.toString();
|
||||
}
|
||||
}
|
||||
}
|
128
src/com/android/exchange/utility/Duration.java
Normal file
128
src/com/android/exchange/utility/Duration.java
Normal file
@ -0,0 +1,128 @@
|
||||
/* Copyright 2010, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.exchange.utility;
|
||||
|
||||
import android.pim.DateException;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* Note: This class was simply copied from the class in CalendarProvider, since we don't have access
|
||||
* to it from the Email app. I reformated some lines, but otherwise haven't altered the code.
|
||||
*/
|
||||
public class Duration {
|
||||
public int sign; // 1 or -1
|
||||
public int weeks;
|
||||
public int days;
|
||||
public int hours;
|
||||
public int minutes;
|
||||
public int seconds;
|
||||
|
||||
public Duration() {
|
||||
sign = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse according to RFC2445 ss4.3.6. (It's actually a little loose with
|
||||
* its parsing, for better or for worse)
|
||||
*/
|
||||
public void parse(String str) throws DateException {
|
||||
sign = 1;
|
||||
weeks = 0;
|
||||
days = 0;
|
||||
hours = 0;
|
||||
minutes = 0;
|
||||
seconds = 0;
|
||||
|
||||
int len = str.length();
|
||||
int index = 0;
|
||||
char c;
|
||||
|
||||
if (len < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
c = str.charAt(0);
|
||||
if (c == '-') {
|
||||
sign = -1;
|
||||
index++;
|
||||
} else if (c == '+') {
|
||||
index++;
|
||||
}
|
||||
|
||||
if (len < index) {
|
||||
return;
|
||||
}
|
||||
|
||||
c = str.charAt(index);
|
||||
if (c != 'P') {
|
||||
throw new DateException (
|
||||
"Duration.parse(str='" + str + "') expected 'P' at index="
|
||||
+ index);
|
||||
}
|
||||
index++;
|
||||
|
||||
int n = 0;
|
||||
for (; index < len; index++) {
|
||||
c = str.charAt(index);
|
||||
if (c >= '0' && c <= '9') {
|
||||
n *= 10;
|
||||
n += (c - '0');
|
||||
} else if (c == 'W') {
|
||||
weeks = n;
|
||||
n = 0;
|
||||
} else if (c == 'H') {
|
||||
hours = n;
|
||||
n = 0;
|
||||
} else if (c == 'M') {
|
||||
minutes = n;
|
||||
n = 0;
|
||||
} else if (c == 'S') {
|
||||
seconds = n;
|
||||
n = 0;
|
||||
} else if (c == 'D') {
|
||||
days = n;
|
||||
n = 0;
|
||||
} else if (c == 'T') {
|
||||
} else {
|
||||
throw new DateException (
|
||||
"Duration.parse(str='" + str + "') unexpected char '"
|
||||
+ c + "' at index=" + index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this to the calendar provided, in place, in the calendar.
|
||||
*/
|
||||
public void addTo(Calendar cal) {
|
||||
cal.add(Calendar.DAY_OF_MONTH, sign*weeks*7);
|
||||
cal.add(Calendar.DAY_OF_MONTH, sign*days);
|
||||
cal.add(Calendar.HOUR, sign*hours);
|
||||
cal.add(Calendar.MINUTE, sign*minutes);
|
||||
cal.add(Calendar.SECOND, sign*seconds);
|
||||
}
|
||||
|
||||
public long addTo(long dt) {
|
||||
return dt + getMillis();
|
||||
}
|
||||
|
||||
public long getMillis() {
|
||||
long factor = 1000 * sign;
|
||||
return factor * ((7*24*60*60*weeks) + (24*60*60*days) + (60*60*hours) + (60*minutes) +
|
||||
seconds);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user