mirror of
https://github.com/etesync/android
synced 2024-11-26 01:48:34 +00:00
support for reminders; better support for recurring events; bug fixes
* use DURATION instead of DTEND when required (fixes #60) * send VTIMEZONEs along with VEVENTs (fixes #66) * support for reminders (closes #70) * bug fixes (UID parsing)
This commit is contained in:
parent
55d9dc4357
commit
759e32b89b
@ -30,6 +30,7 @@ import net.fortuna.ical4j.model.Property;
|
||||
import net.fortuna.ical4j.model.PropertyList;
|
||||
import net.fortuna.ical4j.model.TimeZoneRegistry;
|
||||
import net.fortuna.ical4j.model.ValidationException;
|
||||
import net.fortuna.ical4j.model.component.VAlarm;
|
||||
import net.fortuna.ical4j.model.component.VEvent;
|
||||
import net.fortuna.ical4j.model.component.VTimeZone;
|
||||
import net.fortuna.ical4j.model.parameter.Value;
|
||||
@ -39,6 +40,7 @@ import net.fortuna.ical4j.model.property.DateProperty;
|
||||
import net.fortuna.ical4j.model.property.Description;
|
||||
import net.fortuna.ical4j.model.property.DtEnd;
|
||||
import net.fortuna.ical4j.model.property.DtStart;
|
||||
import net.fortuna.ical4j.model.property.Duration;
|
||||
import net.fortuna.ical4j.model.property.ExDate;
|
||||
import net.fortuna.ical4j.model.property.ExRule;
|
||||
import net.fortuna.ical4j.model.property.Location;
|
||||
@ -65,6 +67,7 @@ public class Event extends Resource {
|
||||
|
||||
@Getter private DtStart dtStart;
|
||||
@Getter private DtEnd dtEnd;
|
||||
@Getter @Setter private Duration duration;
|
||||
@Getter @Setter private RDate rdate;
|
||||
@Getter @Setter private RRule rrule;
|
||||
@Getter @Setter private ExDate exdate;
|
||||
@ -80,6 +83,11 @@ public class Event extends Resource {
|
||||
attendees.add(attendee);
|
||||
}
|
||||
|
||||
@Getter private List<VAlarm> alarms = new LinkedList<VAlarm>();
|
||||
public void addAlarm(VAlarm alarm) {
|
||||
alarms.add(alarm);
|
||||
}
|
||||
|
||||
|
||||
public Event(String name, String ETag) {
|
||||
super(name, ETag);
|
||||
@ -95,12 +103,15 @@ public class Event extends Resource {
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void parseEntity(@NonNull InputStream entity) throws IOException, ParserException {
|
||||
CalendarBuilder builder = new CalendarBuilder();
|
||||
net.fortuna.ical4j.model.Calendar ical = builder.build(entity);
|
||||
if (ical == null)
|
||||
return;
|
||||
|
||||
Log.d(TAG, "Parsing iCal: " + ical.toString());
|
||||
|
||||
// event
|
||||
ComponentList events = ical.getComponents(Component.VEVENT);
|
||||
if (events == null || events.isEmpty())
|
||||
@ -108,7 +119,7 @@ public class Event extends Resource {
|
||||
VEvent event = (VEvent)events.get(0);
|
||||
|
||||
if (event.getUid() != null)
|
||||
uid = event.getUid().toString();
|
||||
uid = event.getUid().getValue();
|
||||
else {
|
||||
Log.w(TAG, "Received VEVENT without UID, generating new one");
|
||||
UidGenerator uidGenerator = new UidGenerator(Integer.toString(android.os.Process.myPid()));
|
||||
@ -118,6 +129,7 @@ public class Event extends Resource {
|
||||
dtStart = event.getStartDate(); validateTimeZone(dtStart);
|
||||
dtEnd = event.getEndDate(); validateTimeZone(dtEnd);
|
||||
|
||||
duration = event.getDuration();
|
||||
rrule = (RRule)event.getProperty(Property.RRULE);
|
||||
rdate = (RDate)event.getProperty(Property.RDATE);
|
||||
exrule = (ExRule)event.getProperty(Property.EXRULE);
|
||||
@ -144,10 +156,11 @@ public class Event extends Resource {
|
||||
forPublic = false;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Parsed iCal: " + ical.toString());
|
||||
this.alarms = event.getAlarms();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public String toEntity() {
|
||||
net.fortuna.ical4j.model.Calendar ical = new net.fortuna.ical4j.model.Calendar();
|
||||
ical.getProperties().add(Version.VERSION_2_0);
|
||||
@ -160,7 +173,10 @@ public class Event extends Resource {
|
||||
props.add(new Uid(uid));
|
||||
|
||||
props.add(dtStart);
|
||||
if (dtEnd != null)
|
||||
props.add(dtEnd);
|
||||
if (duration != null)
|
||||
props.add(duration);
|
||||
|
||||
if (rrule != null)
|
||||
props.add(rrule);
|
||||
@ -183,18 +199,23 @@ public class Event extends Resource {
|
||||
|
||||
if (organizer != null)
|
||||
props.add(organizer);
|
||||
for (Attendee attendee : attendees)
|
||||
props.add(attendee);
|
||||
props.addAll(attendees);
|
||||
|
||||
if (forPublic != null)
|
||||
event.getProperties().add(forPublic ? Clazz.PUBLIC : Clazz.PRIVATE);
|
||||
|
||||
event.getAlarms().addAll(alarms);
|
||||
|
||||
ical.getComponents().add(event);
|
||||
|
||||
/*if (dtStart.getTimeZone() != null)
|
||||
ical.getComponents().add(dtStart.getTimeZone().getVTimeZone());
|
||||
if (dtEnd.getTimeZone() != null)
|
||||
ical.getComponents().add(dtEnd.getTimeZone().getVTimeZone());*/
|
||||
// add VTIMEZONE components
|
||||
net.fortuna.ical4j.model.TimeZone
|
||||
tzStart = (dtStart == null ? null : dtStart.getTimeZone()),
|
||||
tzEnd = (dtEnd == null ? null : dtEnd.getTimeZone());
|
||||
if (tzStart != null)
|
||||
ical.getComponents().add(tzStart.getVTimeZone());
|
||||
if (tzEnd != null && tzEnd != tzStart)
|
||||
ical.getComponents().add(tzEnd.getVTimeZone());
|
||||
|
||||
return ical.toString();
|
||||
}
|
||||
@ -280,7 +301,7 @@ public class Event extends Resource {
|
||||
|
||||
/* guess matching Android timezone ID */
|
||||
protected void validateTimeZone(DateProperty date) {
|
||||
if (date.isUtc() || hasNoTime(date))
|
||||
if (date == null || date.isUtc() || hasNoTime(date))
|
||||
return;
|
||||
|
||||
String tzID = getTzId(date);
|
||||
|
@ -15,13 +15,19 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.fortuna.ical4j.model.Dur;
|
||||
import net.fortuna.ical4j.model.Parameter;
|
||||
import net.fortuna.ical4j.model.ParameterList;
|
||||
import net.fortuna.ical4j.model.PropertyList;
|
||||
import net.fortuna.ical4j.model.component.VAlarm;
|
||||
import net.fortuna.ical4j.model.parameter.Cn;
|
||||
import net.fortuna.ical4j.model.parameter.CuType;
|
||||
import net.fortuna.ical4j.model.parameter.PartStat;
|
||||
import net.fortuna.ical4j.model.parameter.Role;
|
||||
import net.fortuna.ical4j.model.property.Action;
|
||||
import net.fortuna.ical4j.model.property.Attendee;
|
||||
import net.fortuna.ical4j.model.property.Description;
|
||||
import net.fortuna.ical4j.model.property.Duration;
|
||||
import net.fortuna.ical4j.model.property.ExDate;
|
||||
import net.fortuna.ical4j.model.property.ExRule;
|
||||
import net.fortuna.ical4j.model.property.Organizer;
|
||||
@ -49,6 +55,7 @@ import android.provider.CalendarContract;
|
||||
import android.provider.CalendarContract.Attendees;
|
||||
import android.provider.CalendarContract.Calendars;
|
||||
import android.provider.CalendarContract.Events;
|
||||
import android.provider.CalendarContract.Reminders;
|
||||
import android.provider.ContactsContract;
|
||||
import android.util.Log;
|
||||
import at.bitfire.davdroid.syncadapter.ServerInfo;
|
||||
@ -188,7 +195,7 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
/* 8 */ Events.STATUS, Events.ACCESS_LEVEL,
|
||||
/* 10 */ Events.RRULE, Events.RDATE, Events.EXRULE, Events.EXDATE,
|
||||
/* 14 */ Events.HAS_ATTENDEE_DATA, Events.ORGANIZER, Events.SELF_ATTENDEE_STATUS,
|
||||
/* 17 */ entryColumnUID()
|
||||
/* 17 */ entryColumnUID(), Events.DURATION
|
||||
}, null, null, null);
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
e.setUid(cursor.getString(17));
|
||||
@ -199,23 +206,27 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
|
||||
long tsStart = cursor.getLong(3),
|
||||
tsEnd = cursor.getLong(4);
|
||||
if (cursor.getInt(7) != 0) { // all-day, UTC
|
||||
e.setDtStart(tsStart, null);
|
||||
e.setDtEnd(tsEnd, null);
|
||||
|
||||
String tzId;
|
||||
if (cursor.getInt(7) != 0) { // ALL_DAY != 0
|
||||
tzId = null; // -> use UTC
|
||||
} else {
|
||||
// use the start time zone for the end time, too
|
||||
// because the Samsung Planner UI allows the user to change the time zone
|
||||
// but it will change the start time zone only
|
||||
|
||||
String tzIdStart = cursor.getString(5);
|
||||
tzId = cursor.getString(5);
|
||||
//tzIdEnd = cursor.getString(6);
|
||||
|
||||
e.setDtStart(tsStart, tzIdStart);
|
||||
e.setDtEnd(tsEnd, tzIdStart /*(tzIdEnd != null) ? tzIdEnd : tzIdStart*/);
|
||||
}
|
||||
e.setDtStart(tsStart, tzId);
|
||||
if (tsEnd != 0)
|
||||
e.setDtEnd(tsEnd, tzId);
|
||||
|
||||
// recurrence
|
||||
try {
|
||||
String duration = cursor.getString(18);
|
||||
if (duration != null)
|
||||
e.setDuration(new Duration(new Dur(duration)));
|
||||
|
||||
String strRRule = cursor.getString(10);
|
||||
if (strRRule != null)
|
||||
e.setRrule(new RRule(strRRule));
|
||||
@ -339,6 +350,28 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
case Events.ACCESS_PUBLIC:
|
||||
e.setForPublic(true);
|
||||
}
|
||||
|
||||
// reminders
|
||||
Uri remindersUri = Reminders.CONTENT_URI.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.build();
|
||||
Cursor c = providerClient.query(remindersUri, new String[] {
|
||||
/* 0 */ Reminders.MINUTES, Reminders.METHOD
|
||||
}, Reminders.EVENT_ID + "=?", new String[] { String.valueOf(e.getLocalID()) }, null);
|
||||
while (c != null && c.moveToNext()) {
|
||||
VAlarm alarm = new VAlarm(new Dur(0, 0, -c.getInt(0), 0));
|
||||
|
||||
PropertyList props = alarm.getProperties();
|
||||
switch (c.getInt(1)) {
|
||||
/*case Reminders.METHOD_EMAIL:
|
||||
props.add(Action.EMAIL);
|
||||
break;*/
|
||||
default:
|
||||
props.add(Action.DISPLAY);
|
||||
props.add(new Description(e.getSummary()));
|
||||
}
|
||||
e.addAlarm(alarm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -436,6 +469,8 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
protected void addDataRows(Event event, long localID, int backrefIdx) {
|
||||
for (Attendee attendee : event.getAttendees())
|
||||
pendingOperations.add(buildAttendee(newDataInsertBuilder(Attendees.CONTENT_URI, Attendees.EVENT_ID, localID, backrefIdx), attendee).build());
|
||||
for (VAlarm alarm : event.getAlarms())
|
||||
pendingOperations.add(buildReminder(newDataInsertBuilder(Reminders.CONTENT_URI, Reminders.EVENT_ID, localID, backrefIdx), alarm).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -443,6 +478,9 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
pendingOperations.add(ContentProviderOperation.newDelete(syncAdapterURI(Attendees.CONTENT_URI))
|
||||
.withSelection(Attendees.EVENT_ID + "=?",
|
||||
new String[] { String.valueOf(event.getLocalID()) }).build());
|
||||
pendingOperations.add(ContentProviderOperation.newDelete(syncAdapterURI(Reminders.CONTENT_URI))
|
||||
.withSelection(Reminders.EVENT_ID + "=?",
|
||||
new String[] { String.valueOf(event.getLocalID()) }).build());
|
||||
}
|
||||
|
||||
|
||||
@ -491,4 +529,18 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
.withValue(Attendees.ATTENDEE_TYPE, type)
|
||||
.withValue(Attendees.ATTENDEE_STATUS, status);
|
||||
}
|
||||
|
||||
protected Builder buildReminder(Builder builder, VAlarm alarm) {
|
||||
int minutes = 0;
|
||||
|
||||
Dur duration;
|
||||
if (alarm.getTrigger() != null && (duration = alarm.getTrigger().getDuration()) != null)
|
||||
minutes = duration.getDays() * 24*60 + duration.getHours()*60 + duration.getMinutes();
|
||||
|
||||
Log.i(TAG, "Adding alarm " + minutes + " min before");
|
||||
|
||||
return builder
|
||||
.withValue(Reminders.METHOD, Reminders.METHOD_ALERT)
|
||||
.withValue(Reminders.MINUTES, minutes);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import java.net.UnknownHostException;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
import org.apache.http.conn.scheme.LayeredSocketFactory;
|
||||
@ -66,9 +67,13 @@ public class TlsSniSocketFactory implements LayeredSocketFactory {
|
||||
Log.i(TAG, "No SNI support below Android 4.2!");
|
||||
|
||||
// verify hostname and certificate
|
||||
if (!hostnameVerifier.verify(host, ssl.getSession()))
|
||||
SSLSession session = ssl.getSession();
|
||||
if (!hostnameVerifier.verify(host, session))
|
||||
throw new SSLPeerUnverifiedException("Cannot verify hostname: " + host);
|
||||
|
||||
Log.i(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
|
||||
" using " + session.getCipherSuite());
|
||||
|
||||
return ssl;
|
||||
}
|
||||
}
|
||||
|
@ -96,13 +96,13 @@ public class WebDavResource {
|
||||
|
||||
if (trailingSlash && !location.getRawPath().endsWith("/"))
|
||||
location = new URI(location.getScheme(), location.getSchemeSpecificPart() + "/", null);
|
||||
|
||||
client = DavHttpClient.getDefault();
|
||||
}
|
||||
|
||||
public WebDavResource(URI baseURL, String username, String password, boolean preemptive, boolean trailingSlash) throws URISyntaxException {
|
||||
this(baseURL, trailingSlash);
|
||||
|
||||
client = DavHttpClient.getDefault();
|
||||
|
||||
// authenticate
|
||||
client.getCredentialsProvider().setCredentials(
|
||||
new AuthScope(location.getHost(), location.getPort()),
|
||||
@ -350,6 +350,7 @@ public class WebDavResource {
|
||||
|
||||
public void put(byte[] data, PutMode mode) throws IOException, HttpException {
|
||||
HttpPut put = new HttpPut(location);
|
||||
Log.d(TAG, "Sending PUT request: " + new String(data, "UTF-8"));
|
||||
put.setEntity(new ByteArrayEntity(data));
|
||||
|
||||
switch (mode) {
|
||||
|
@ -29,7 +29,7 @@ public class CalendarTest extends InstrumentationTestCase {
|
||||
Assert.assertEquals("Test-Ereignis im schönen Wien", e.getSummary());
|
||||
|
||||
//DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Europe/Vienna:20131009T170000
|
||||
//Assert.assertEquals(1381327200, e.getDtStartInMillis());
|
||||
Assert.assertEquals(1381327200, e.getDtStartInMillis());
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package at.bitfire.davdroid.test;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
import at.bitfire.davdroid.URIUtils;
|
||||
|
@ -21,7 +21,7 @@ import at.bitfire.davdroid.webdav.WebDavResource.PutMode;
|
||||
// tests require running robohydra!
|
||||
|
||||
public class WebDavResourceTest extends InstrumentationTestCase {
|
||||
static final String ROBOHYDRA_BASE = "http://10.0.0.119:3000/";
|
||||
static final String ROBOHYDRA_BASE = "http://10.0.0.11:3000/";
|
||||
static byte[] SAMPLE_CONTENT = new byte[] { 1, 2, 3, 4, 5 };
|
||||
|
||||
AssetManager assetMgr;
|
||||
|
Loading…
Reference in New Issue
Block a user