Sync notes and tasks

This commit is contained in:
Ricki Hirner 2015-05-22 03:06:30 +02:00
parent c1671b7d62
commit e29920504e
28 changed files with 2561 additions and 75 deletions

View file

@ -10,7 +10,7 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion '21.1.2'
buildToolsVersion '22.0.1'
defaultConfig {
applicationId "at.bitfire.davdroid"

View file

@ -26,6 +26,12 @@
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="at.bitfire.notebooks.provider.READ_WRITE_NOTES" />
<uses-permission android:name="de.azapps.mirakel.provider.READ_WRITE_DATA" />
<uses-permission android:name="de.azapps.mirakel.provider.READ_DATA" />
<uses-permission android:name="de.azapps.mirakel.provider.WRITE_DATA" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
@ -50,7 +56,6 @@
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_contacts" />
@ -64,11 +69,30 @@
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_calendars" />
</service>
<service
android:name=".syncadapter.NotesSyncAdapterService"
android:exported="true" >
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_notes" />
</service>
<service
android:name=".syncadapter.TasksSyncAdapterService"
android:exported="true" >
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_tasks" />
</service>
<activity
android:name=".ui.MainActivity"

View file

@ -7,10 +7,14 @@
*/
package at.bitfire.davdroid;
import net.fortuna.ical4j.model.property.ProdId;
public class Constants {
public static final String
APP_VERSION = "0.7.7",
ACCOUNT_TYPE = "bitfire.at.davdroid",
WEB_URL_HELP = "https://davdroid.bitfire.at/configuration?pk_campaign=davdroid-app",
WEB_URL_VIEW_LOGS = "https://github.com/bitfireAT/davdroid/wiki/How-to-view-the-logs";
public static final ProdId ICAL_PRODID = new ProdId("-//bitfire web engineering//DAVdroid " + Constants.APP_VERSION + " (ical4j 1.0.x)//EN");
}

View file

@ -29,10 +29,8 @@ public class CalDavCalendar extends RemoteCollection<Event> {
public CalDavCalendar(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
super(httpClient, baseURL, user, password, preemptiveAuth);
}
@Override
protected String memberAcceptedMimeTypes()
{

View file

@ -0,0 +1,72 @@
package at.bitfire.davdroid.resource;
import android.util.Log;
import org.apache.http.impl.client.CloseableHttpClient;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;
import java.io.StringWriter;
import java.net.URISyntaxException;
import at.bitfire.davdroid.webdav.DavCalendarQuery;
import at.bitfire.davdroid.webdav.DavCompFilter;
import at.bitfire.davdroid.webdav.DavFilter;
import at.bitfire.davdroid.webdav.DavMultiget;
import at.bitfire.davdroid.webdav.DavProp;
public class CalDavNotebook extends RemoteCollection<Note> {
private final static String TAG = "davdroid.CalDAVNotebook";
public CalDavNotebook(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
super(httpClient, baseURL, user, password, preemptiveAuth);
}
@Override
protected String memberAcceptedMimeTypes()
{
return "text/calendar";
}
@Override
protected DavMultiget.Type multiGetType() {
return DavMultiget.Type.CALENDAR;
}
@Override
protected Note newResourceSkeleton(String name, String ETag) {
return new Note(name, ETag);
}
@Override
public String getMemberETagsQuery() {
DavCalendarQuery query = new DavCalendarQuery();
// prop
DavProp prop = new DavProp();
prop.setGetetag(new DavProp.GetETag());
query.setProp(prop);
// filter
DavFilter filter = new DavFilter();
query.setFilter(filter);
DavCompFilter compFilter = new DavCompFilter("VCALENDAR");
filter.setCompFilter(compFilter);
compFilter.setCompFilter(new DavCompFilter("VJOURNAL"));
Serializer serializer = new Persister();
StringWriter writer = new StringWriter();
try {
serializer.write(query, writer);
} catch (Exception e) {
Log.e(TAG, "Couldn't prepare REPORT query", e);
return null;
}
return writer.toString();
}
}

View file

@ -0,0 +1,72 @@
package at.bitfire.davdroid.resource;
import android.util.Log;
import org.apache.http.impl.client.CloseableHttpClient;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;
import java.io.StringWriter;
import java.net.URISyntaxException;
import at.bitfire.davdroid.webdav.DavCalendarQuery;
import at.bitfire.davdroid.webdav.DavCompFilter;
import at.bitfire.davdroid.webdav.DavFilter;
import at.bitfire.davdroid.webdav.DavMultiget;
import at.bitfire.davdroid.webdav.DavProp;
public class CalDavTaskList extends RemoteCollection<Task> {
private final static String TAG = "davdroid.CalDAVTaskList";
public CalDavTaskList(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
super(httpClient, baseURL, user, password, preemptiveAuth);
}
@Override
protected String memberAcceptedMimeTypes()
{
return "text/calendar";
}
@Override
protected DavMultiget.Type multiGetType() {
return DavMultiget.Type.CALENDAR;
}
@Override
protected Task newResourceSkeleton(String name, String ETag) {
return new Task(name, ETag);
}
@Override
public String getMemberETagsQuery() {
DavCalendarQuery query = new DavCalendarQuery();
// prop
DavProp prop = new DavProp();
prop.setGetetag(new DavProp.GetETag());
query.setProp(prop);
// filter
DavFilter filter = new DavFilter();
query.setFilter(filter);
DavCompFilter compFilter = new DavCompFilter("VCALENDAR");
filter.setCompFilter(compFilter);
compFilter.setCompFilter(new DavCompFilter("VTODO"));
Serializer serializer = new Persister();
StringWriter writer = new StringWriter();
try {
serializer.write(query, writer);
} catch (Exception e) {
Log.e(TAG, "Couldn't prepare REPORT query", e);
return null;
}
return writer.toString();
}
}

View file

@ -131,25 +131,35 @@ public class DavResourceFinder implements Closeable {
for (WebDavResource resource : possibleCalendars)
if (resource.isCalendar()) {
Log.i(TAG, "Found calendar: " + resource.getLocation().getPath());
if (resource.getSupportedComponents() != null) {
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
ServerInfo.ResourceInfo.Type.CALENDAR,
resource.isReadOnly(),
resource.getLocation().toString(),
resource.getDisplayName(),
resource.getDescription(), resource.getColor()
);
info.setTimezone(resource.getTimezone());
if (resource.getSupportedComponents() == null) {
// no info about supported components, assuming all components are supported
info.setSupportingEvents(true);
info.setSupportingNotes(true);
} else {
// CALDAV:supported-calendar-component-set available
boolean supportsEvents = false;
for (String supportedComponent : resource.getSupportedComponents())
if (supportedComponent.equalsIgnoreCase("VEVENT"))
supportsEvents = true;
if (!supportsEvents) { // ignore collections without VEVENT support
Log.i(TAG, "Ignoring this calendar because of missing VEVENT support");
if ("VEVENT".equalsIgnoreCase(supportedComponent))
info.setSupportingEvents(true);
else if ("VJOURNAL".equalsIgnoreCase(supportedComponent))
info.setSupportingNotes(true);
else if ("VTODO".equalsIgnoreCase(supportedComponent))
info.setSupportingTasks(true);
if (!info.isSupportingEvents() && !info.isSupportingNotes() && !info.isSupportingTasks()) {
Log.i(TAG, "Ignoring this calendar because it supports neither VEVENT nor VJOURNAL nor VTODO");
continue;
}
}
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
ServerInfo.ResourceInfo.Type.CALENDAR,
resource.isReadOnly(),
resource.getLocation().toString(),
resource.getDisplayName(),
resource.getDescription(), resource.getColor()
);
info.setTimezone(resource.getTimezone());
calendars.add(info);
}
serverInfo.setCalendars(calendars);

View file

@ -244,7 +244,7 @@ public class Event extends Resource {
public ByteArrayOutputStream toEntity() throws IOException {
net.fortuna.ical4j.model.Calendar ical = new net.fortuna.ical4j.model.Calendar();
ical.getProperties().add(Version.VERSION_2_0);
ical.getProperties().add(new ProdId("-//bitfire web engineering//DAVdroid " + Constants.APP_VERSION + " (ical4j 1.0.x)//EN"));
ical.getProperties().add(Constants.ICAL_PRODID);
// "master event" (without exceptions)
ComponentList components = ical.getComponents();

View file

@ -654,12 +654,15 @@ public class LocalAddressBook extends LocalCollection<Contact> {
/* content builder methods */
@Override
protected Builder buildEntry(Builder builder, Resource resource) {
protected Builder buildEntry(Builder builder, Resource resource, boolean update) {
Contact contact = (Contact)resource;
if (!update)
builder = builder
.withValue(RawContacts.ACCOUNT_NAME, account.name)
.withValue(RawContacts.ACCOUNT_TYPE, account.type);
return builder
.withValue(RawContacts.ACCOUNT_NAME, account.name)
.withValue(RawContacts.ACCOUNT_TYPE, account.type)
.withValue(entryColumnRemoteName(), contact.getName())
.withValue(entryColumnUID(), contact.getUid())
.withValue(entryColumnETag(), contact.getETag())

View file

@ -87,30 +87,25 @@ import lombok.Getter;
public class LocalCalendar extends LocalCollection<Event> {
private static final String TAG = "davdroid.LocalCalendar";
@Getter protected long id;
@Getter protected String url;
@Getter protected long id;
protected static String COLLECTION_COLUMN_CTAG = Calendars.CAL_SYNC1;
/* database fields */
@Override protected Uri entriesURI() { return syncAdapterURI(Events.CONTENT_URI); }
@Override protected String entryColumnAccountType() { return Events.ACCOUNT_TYPE; }
@Override protected String entryColumnAccountName() { return Events.ACCOUNT_NAME; }
@Override protected String entryColumnParentID() { return Events.CALENDAR_ID; }
@Override protected String entryColumnID() { return Events._ID; }
@Override protected String entryColumnRemoteName() { return Events._SYNC_ID; }
@Override protected String entryColumnETag() { return Events.SYNC_DATA1; }
@Override protected String entryColumnDirty() { return Events.DIRTY; }
@Override protected String entryColumnDeleted() { return Events.DELETED; }
@Override
protected Uri entriesURI() {
return syncAdapterURI(Events.CONTENT_URI);
}
protected String entryColumnAccountType() { return Events.ACCOUNT_TYPE; }
protected String entryColumnAccountName() { return Events.ACCOUNT_NAME; }
protected String entryColumnParentID() { return Events.CALENDAR_ID; }
protected String entryColumnID() { return Events._ID; }
protected String entryColumnRemoteName() { return Events._SYNC_ID; }
protected String entryColumnETag() { return Events.SYNC_DATA1; }
protected String entryColumnDirty() { return Events.DIRTY; }
protected String entryColumnDeleted() { return Events.DELETED; }
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
protected String entryColumnUID() {
return (android.os.Build.VERSION.SDK_INT >= 17) ?
@ -164,7 +159,7 @@ public class LocalCalendar extends LocalCollection<Event> {
if (info.getTimezone() != null)
values.put(Calendars.CALENDAR_TIME_ZONE, info.getTimezone());
Log.i(TAG, "Inserting calendar: " + values.toString() + " -> " + calendarsURI(account).toString());
Log.i(TAG, "Inserting calendar: " + values.toString());
try {
return client.insert(calendarsURI(account), values);
} catch (RemoteException e) {
@ -176,8 +171,8 @@ public class LocalCalendar extends LocalCollection<Event> {
@Cleanup Cursor cursor = providerClient.query(calendarsURI(account),
new String[] { Calendars._ID, Calendars.NAME },
Calendars.DELETED + "=0 AND " + Calendars.SYNC_EVENTS + "=1", null, null);
LinkedList<LocalCalendar> calendars = new LinkedList<LocalCalendar>();
LinkedList<LocalCalendar> calendars = new LinkedList<>();
while (cursor != null && cursor.moveToNext())
calendars.add(new LocalCalendar(account, providerClient, cursor.getInt(0), cursor.getString(1)));
return calendars.toArray(new LocalCalendar[0]);
@ -198,9 +193,9 @@ public class LocalCalendar extends LocalCollection<Event> {
try {
@Cleanup Cursor c = providerClient.query(ContentUris.withAppendedId(calendarsURI(), id),
new String[] { COLLECTION_COLUMN_CTAG }, null, null, null);
if (c.moveToFirst()) {
if (c != null && c.moveToFirst())
return c.getString(0);
} else
else
throw new LocalStorageException("Couldn't query calendar CTag");
} catch(RemoteException e) {
throw new LocalStorageException(e);
@ -528,9 +523,14 @@ public class LocalCalendar extends LocalCollection<Event> {
/* content builder methods */
@Override
protected Builder buildEntry(Builder builder, Resource resource) {
protected Builder buildEntry(Builder builder, Resource resource, boolean update) {
final Event event = (Event)resource;
if (!update)
builder = builder
.withValue(Events.ACCOUNT_TYPE, account.type)
.withValue(Events.ACCOUNT_NAME, account.name);
builder = builder
.withValue(Events.CALENDAR_ID, id)
.withValue(Events.ALL_DAY, event.isAllDay() ? 1 : 0)
@ -648,7 +648,7 @@ public class LocalCalendar extends LocalCollection<Event> {
protected Builder buildException(Builder builder, Event master, Event exception) {
buildEntry(builder, exception);
buildEntry(builder, exception, false);
builder.withValue(Events.ORIGINAL_SYNC_ID, exception.getName());
// Some servers (iCloud, for instance) return RECURRENCE-ID with DATE-TIME even if
@ -747,9 +747,11 @@ public class LocalCalendar extends LocalCollection<Event> {
/* private helper methods */
protected static Uri calendarsURI(Account account) {
return Calendars.CONTENT_URI.buildUpon().appendQueryParameter(Calendars.ACCOUNT_NAME, account.name)
return Calendars.CONTENT_URI.buildUpon()
.appendQueryParameter(Calendars.ACCOUNT_NAME, account.name)
.appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type)
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true").build();
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.build();
}
protected Uri calendarsURI() {

View file

@ -15,14 +15,20 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.CalendarContract;
import android.util.Log;
import org.apache.commons.lang.StringUtils;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import lombok.Cleanup;
import lombok.Getter;
/**
* Represents a locally-stored synchronizable collection (for instance, the
@ -67,6 +73,10 @@ public abstract class LocalCollection<T extends Resource> {
/** column name of an entry's UID */
abstract protected String entryColumnUID();
/** ID of the collection (for instance, CalendarContract.Calendars._ID) */
// protected long id;
/** SQL filter expression */
String sqlFilter;
@ -114,7 +124,7 @@ public abstract class LocalCollection<T extends Resource> {
for (int idx = 0; cursor.moveToNext(); idx++) {
long id = cursor.getLong(0);
// new record: generate UID + remote file name so that we can upload
// new record: we have to generate UID + remote file name for uploading
T resource = findById(id, false);
resource.initialize();
// write generated UID + remote file name into database
@ -218,9 +228,9 @@ public abstract class LocalCollection<T extends Resource> {
/**
* Finds a specific resource by remote file name. Only records matching sqlFilter are taken into account.
* @param localID remote file name of the resource
* @param populate true: populates all data fields (for instance, contact or event details);
* false: only remote file name and ETag are populated
* @param remoteName remote file name of the resource
* @param populate true: populates all data fields (for instance, contact or event details);
* false: only remote file name and ETag are populated
* @return resource with either ID/remote file/name/ETag or all fields populated
* @throws RecordNotFoundException when the resource couldn't be found
* @throws LocalStorageException when the content provider couldn't be queried
@ -255,7 +265,7 @@ public abstract class LocalCollection<T extends Resource> {
* Creates a new resource object in memory. No content provider operations involved.
* @param localID the ID of the resource
* @param resourceName the (remote) file name of the resource
* @param ETag of the resource
* @param eTag ETag of the resource
* @return the new resource object */
abstract public T newResource(long localID, String resourceName, String eTag);
@ -263,9 +273,9 @@ public abstract class LocalCollection<T extends Resource> {
public void add(Resource resource) {
int idx = pendingOperations.size();
pendingOperations.add(
buildEntry(ContentProviderOperation.newInsert(entriesURI()), resource)
.withYieldAllowed(true)
.build());
buildEntry(ContentProviderOperation.newInsert(entriesURI()), resource, false)
.withYieldAllowed(true)
.build());
addDataRows(resource, -1, idx);
}
@ -275,7 +285,7 @@ public abstract class LocalCollection<T extends Resource> {
public void updateByRemoteName(Resource remoteResource) throws LocalStorageException {
T localResource = findByRemoteName(remoteResource.getName(), false);
pendingOperations.add(
buildEntry(ContentProviderOperation.newUpdate(ContentUris.withAppendedId(entriesURI(), localResource.getLocalID())), remoteResource)
buildEntry(ContentProviderOperation.newUpdate(ContentUris.withAppendedId(entriesURI(), localResource.getLocalID())), remoteResource, true)
.withValue(entryColumnETag(), remoteResource.getETag())
.withYieldAllowed(true)
.build());
@ -296,8 +306,28 @@ public abstract class LocalCollection<T extends Resource> {
* Enqueues deleting all resources except the give ones from the local collection. Requires commit().
* @param remoteResources resources with these remote file names will be kept
*/
public abstract void deleteAllExceptRemoteNames(Resource[] remoteResources);
public void deleteAllExceptRemoteNames(Resource[] remoteResources) {
final String where;
if (remoteResources.length != 0) {
// delete all except certain entries
final List<String> sqlFileNames = new LinkedList<>();
for (final Resource res : remoteResources)
sqlFileNames.add(DatabaseUtils.sqlEscapeString(res.getName()));
where = entryColumnRemoteName() + " NOT IN (" + StringUtils.join(sqlFileNames, ",") + ')';
} else
// delete all entries
where = entryColumnRemoteName() + " IS NOT NULL";
ContentProviderOperation.Builder builder = ContentProviderOperation.newDelete(entriesURI())
.withSelection( // restrict deletion to parent collection
entryColumnParentID() + "=? AND (" + where + ')',
new String[] { String.valueOf(getId()) }
);
pendingOperations.add(builder.withYieldAllowed(true).build());
}
/** Updates the locally-known ETag of a resource. */
public void updateETag(Resource res, String eTag) throws LocalStorageException {
Log.d(TAG, "Setting ETag of local resource " + res.getName() + " to " + eTag);
@ -365,9 +395,11 @@ public abstract class LocalCollection<T extends Resource> {
* Builds the main entry (for instance, a ContactsContract.RawContacts row) from a resource.
* The entry is built for insertion to the location identified by entriesURI().
*
* @param builder Builder to be extended by all resource data that can be stored without extra data rows.
* @param builder Builder to be extended by all resource data that can be stored without extra data rows.
* @param resource Event, task or note resource whose contents shall be inserted/updated
* @param update false when the entry is built the first time (when creating the row), true if it's an update
*/
protected abstract Builder buildEntry(Builder builder, Resource resource);
protected abstract Builder buildEntry(Builder builder, Resource resource, boolean update);
/** Enqueues adding extra data rows of the resource to the local collection. */
protected abstract void addDataRows(Resource resource, long localID, int backrefIdx);

View file

@ -0,0 +1,179 @@
package at.bitfire.davdroid.resource;
import android.accounts.Account;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.CalendarContract;
import android.util.Log;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.property.Created;
import net.fortuna.ical4j.model.property.DtStamp;
import org.apache.commons.lang.StringUtils;
import java.util.LinkedList;
import java.util.List;
import at.bitfire.davdroid.Constants;
import at.bitfire.notebooks.provider.NoteContract;
import lombok.Cleanup;
import lombok.Getter;
public class LocalNotebook extends LocalCollection<Note> {
private final static String TAG = "davdroid.LocalNotebook";
@Getter protected final String url;
@Getter protected final long id;
protected static String COLLECTION_COLUMN_CTAG = NoteContract.Notebooks.SYNC1;
@Override protected Uri entriesURI() { return syncAdapterURI(NoteContract.Notes.CONTENT_URI); }
@Override protected String entryColumnAccountType() { return NoteContract.Notes.ACCOUNT_TYPE; }
@Override protected String entryColumnAccountName() { return NoteContract.Notes.ACCOUNT_NAME; }
@Override protected String entryColumnParentID() { return NoteContract.Notes.NOTEBOOK_ID; }
@Override protected String entryColumnID() { return NoteContract.Notes._ID; }
@Override protected String entryColumnRemoteName() { return NoteContract.Notes._SYNC_ID; }
@Override protected String entryColumnETag() { return NoteContract.Notes.SYNC1; }
@Override protected String entryColumnDirty() { return NoteContract.Notes.DIRTY; }
@Override protected String entryColumnDeleted() { return NoteContract.Notes.DELETED; }
@Override protected String entryColumnUID() { return NoteContract.Notes.UID; }
public static Uri create(Account account, ContentResolver resolver, ServerInfo.ResourceInfo info) throws LocalStorageException {
final ContentProviderClient client = resolver.acquireContentProviderClient(NoteContract.AUTHORITY);
if (client == null)
throw new LocalStorageException("No notes provider found");
ContentValues values = new ContentValues();
values.put(NoteContract.Notebooks._SYNC_ID, info.getURL());
values.put(NoteContract.Notebooks.NAME, info.getTitle());
Log.i(TAG, "Inserting notebook: " + values.toString());
try {
return client.insert(notebooksURI(account), values);
} catch (RemoteException e) {
throw new LocalStorageException(e);
}
}
public static LocalNotebook[] findAll(Account account, ContentProviderClient providerClient) throws RemoteException {
@Cleanup Cursor cursor = providerClient.query(notebooksURI(account),
new String[] { NoteContract.Notebooks._ID, NoteContract.Notebooks._SYNC_ID },
NoteContract.Notebooks.DELETED + "=0", null, null);
LinkedList<LocalNotebook> notebooks = new LinkedList<>();
while (cursor != null && cursor.moveToNext())
notebooks.add(new LocalNotebook(account, providerClient, cursor.getInt(0), cursor.getString(1)));
return notebooks.toArray(new LocalNotebook[0]);
}
public LocalNotebook(Account account, ContentProviderClient providerClient, long id, String url) throws RemoteException {
super(account, providerClient);
this.id = id;
this.url = url;
}
@Override
public String getCTag() throws LocalStorageException {
try {
@Cleanup Cursor c = providerClient.query(ContentUris.withAppendedId(notebooksURI(account), id),
new String[] { COLLECTION_COLUMN_CTAG }, null, null, null);
if (c != null && c.moveToFirst())
return c.getString(0);
else
throw new LocalStorageException("Couldn't query notebook CTag");
} catch(RemoteException e) {
throw new LocalStorageException(e);
}
}
@Override
public void setCTag(String cTag) throws LocalStorageException {
ContentValues values = new ContentValues(1);
values.put(COLLECTION_COLUMN_CTAG, cTag);
try {
providerClient.update(ContentUris.withAppendedId(notebooksURI(account), id), values, null, null);
} catch(RemoteException e) {
throw new LocalStorageException(e);
}
}
@Override
public Note newResource(long localID, String resourceName, String eTag) {
return new Note(localID, resourceName, eTag);
}
@Override
public void populate(Resource record) throws LocalStorageException {
try {
@Cleanup final Cursor cursor = providerClient.query(entriesURI(),
new String[] {
/* 0 */ entryColumnUID(), NoteContract.Notes.CREATED_AT, NoteContract.Notes.UPDATED_AT, NoteContract.Notes.DTSTART,
/* 4 */ NoteContract.Notes.SUMMARY, NoteContract.Notes.DESCRIPTION, NoteContract.Notes.COMMENT,
/* 7 */ NoteContract.Notes.ORGANIZER, NoteContract.Notes.STATUS, NoteContract.Notes.CLASSIFICATION,
/* 10 */ NoteContract.Notes.CONTACT, NoteContract.Notes.URL
}, entryColumnID() + "=?", new String[]{ String.valueOf(record.getLocalID()) }, null);
Note note = (Note)record;
if (cursor != null && cursor.moveToFirst()) {
note.setUid(cursor.getString(0));
if (!cursor.isNull(1))
note.setCreated(new Created(new DateTime(cursor.getLong(1))));
note.setSummary(cursor.getString(4));
note.setDescription(cursor.getString(5));
}
} catch (RemoteException e) {
throw new LocalStorageException("Couldn't process locally stored note", e);
}
}
@Override
protected ContentProviderOperation.Builder buildEntry(ContentProviderOperation.Builder builder, Resource resource, boolean update) {
final Note note = (Note)resource;
builder = builder
.withValue(entryColumnParentID(), id)
.withValue(entryColumnRemoteName(), note.getName())
.withValue(entryColumnUID(), note.getUid())
.withValue(entryColumnETag(), note.getETag())
.withValue(NoteContract.Notes.SUMMARY, note.getSummary())
.withValue(NoteContract.Notes.DESCRIPTION, note.getDescription());
if (note.getCreated() != null)
builder = builder.withValue(NoteContract.Notes.CREATED_AT, note.getCreated().getDateTime().getTime());
return builder;
}
@Override
protected void addDataRows(Resource resource, long localID, int backrefIdx) {
}
@Override
protected void removeDataRows(Resource resource) {
}
// helpers
protected static Uri notebooksURI(Account account) {
return NoteContract.Notebooks.CONTENT_URI.buildUpon()
.appendQueryParameter(NoteContract.Notebooks.ACCOUNT_TYPE, account.type)
.appendQueryParameter(NoteContract.Notebooks.ACCOUNT_NAME, account.name)
.appendQueryParameter(NoteContract.CALLER_IS_SYNCADAPTER, "true")
.build();
}
}

View file

@ -0,0 +1,288 @@
package at.bitfire.davdroid.resource;
import android.accounts.Account;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.util.Log;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.property.Clazz;
import net.fortuna.ical4j.model.property.Completed;
import net.fortuna.ical4j.model.property.DtStart;
import net.fortuna.ical4j.model.property.Status;
import org.dmfs.provider.tasks.TaskContract;
import java.util.LinkedList;
import lombok.Cleanup;
import lombok.Getter;
public class LocalTaskList extends LocalCollection<Task> {
private static final String TAG = "davdroid.LocalTaskList";
@Getter protected String url;
@Getter protected long id;
protected static String COLLECTION_COLUMN_CTAG = TaskContract.TaskLists.SYNC1;
@Override protected Uri entriesURI() { return syncAdapterURI(TaskContract.Tasks.CONTENT_URI); }
@Override protected String entryColumnAccountType() { return TaskContract.Tasks.ACCOUNT_TYPE; }
@Override protected String entryColumnAccountName() { return TaskContract.Tasks.ACCOUNT_NAME; }
@Override protected String entryColumnParentID() { return TaskContract.Tasks.LIST_ID; }
@Override protected String entryColumnID() { return TaskContract.Tasks._ID; }
@Override protected String entryColumnRemoteName() { return TaskContract.Tasks._SYNC_ID; }
@Override protected String entryColumnETag() { return TaskContract.Tasks.SYNC1; }
@Override protected String entryColumnDirty() { return TaskContract.Tasks._DIRTY; }
@Override protected String entryColumnDeleted() { return TaskContract.Tasks._DELETED; }
@Override protected String entryColumnUID() { return TaskContract.Tasks.SYNC2; }
public static Uri create(Account account, ContentResolver resolver, ServerInfo.ResourceInfo info) throws LocalStorageException {
final ContentProviderClient client = resolver.acquireContentProviderClient(TaskContract.AUTHORITY);
if (client == null)
throw new LocalStorageException("No tasks provider found");
ContentValues values = new ContentValues();
values.put(TaskContract.TaskLists.ACCOUNT_NAME, account.name);
values.put(TaskContract.TaskLists.ACCOUNT_TYPE, /*account.type*/"davdroid.new");
values.put(TaskContract.TaskLists._SYNC_ID, info.getURL());
values.put(TaskContract.TaskLists.LIST_NAME, info.getTitle());
values.put(TaskContract.TaskLists.OWNER, account.name);
values.put(TaskContract.TaskLists.ACCESS_LEVEL, 0);
values.put(TaskContract.TaskLists.SYNC_ENABLED, 1);
values.put(TaskContract.TaskLists.VISIBLE, 1);
Log.i(TAG, "Inserting task list: " + values.toString());
try {
return client.insert(taskListsURI(account), values);
} catch (RemoteException e) {
throw new LocalStorageException(e);
}
}
public static LocalTaskList[] findAll(Account account, ContentProviderClient providerClient) throws RemoteException {
@Cleanup Cursor cursor = providerClient.query(taskListsURI(account),
new String[] { TaskContract.TaskLists._ID, TaskContract.TaskLists._SYNC_ID },
null, null, null);
LinkedList<LocalTaskList> taskList = new LinkedList<>();
while (cursor != null && cursor.moveToNext())
taskList.add(new LocalTaskList(account, providerClient, cursor.getInt(0), cursor.getString(1)));
return taskList.toArray(new LocalTaskList[0]);
}
public LocalTaskList(Account account, ContentProviderClient providerClient, long id, String url) throws RemoteException {
super(account, providerClient);
this.id = id;
this.url = url;
}
@Override
public String getCTag() throws LocalStorageException {
try {
@Cleanup Cursor c = providerClient.query(ContentUris.withAppendedId(taskListsURI(account), id),
new String[] { COLLECTION_COLUMN_CTAG }, null, null, null);
if (c != null && c.moveToFirst())
return c.getString(0);
else
throw new LocalStorageException("Couldn't query task list CTag");
} catch(RemoteException e) {
throw new LocalStorageException(e);
}
}
@Override
public void setCTag(String cTag) throws LocalStorageException {
ContentValues values = new ContentValues(1);
values.put(COLLECTION_COLUMN_CTAG, cTag);
try {
providerClient.update(ContentUris.withAppendedId(taskListsURI(account), id), values, null, null);
} catch(RemoteException e) {
throw new LocalStorageException(e);
}
}
@Override
public Task newResource(long localID, String resourceName, String eTag) {
return new Task(localID, resourceName, eTag);
}
@Override
public void populate(Resource record) throws LocalStorageException {
try {
@Cleanup final Cursor cursor = providerClient.query(entriesURI(),
new String[] {
/* 0 */ entryColumnUID(), TaskContract.Tasks.TITLE, TaskContract.Tasks.LOCATION, TaskContract.Tasks.DESCRIPTION, TaskContract.Tasks.URL,
/* 5 */ TaskContract.Tasks.CLASSIFICATION, TaskContract.Tasks.STATUS, TaskContract.Tasks.PERCENT_COMPLETE,
/* 8 */ TaskContract.Tasks.DTSTART, TaskContract.Tasks.IS_ALLDAY, /*TaskContract.Tasks.COMPLETED, TaskContract.Tasks.COMPLETED_IS_ALLDAY*/
}, entryColumnID() + "=?", new String[]{ String.valueOf(record.getLocalID()) }, null);
Task task = (Task)record;
if (cursor != null && cursor.moveToFirst()) {
task.setUid(cursor.getString(0));
task.setSummary(cursor.getString(1));
task.setLocation(cursor.getString(2));
task.setDescription(cursor.getString(3));
task.setUrl(cursor.getString(4));
if (!cursor.isNull(5))
switch (cursor.getInt(5)) {
case TaskContract.Tasks.CLASSIFICATION_PUBLIC:
task.setClassification(Clazz.PUBLIC);
break;
case TaskContract.Tasks.CLASSIFICATION_CONFIDENTIAL:
task.setClassification(Clazz.CONFIDENTIAL);
break;
default:
task.setClassification(Clazz.PRIVATE);
}
if (!cursor.isNull(6))
switch (cursor.getInt(6)) {
case TaskContract.Tasks.STATUS_IN_PROCESS:
task.setStatus(Status.VTODO_IN_PROCESS);
break;
case TaskContract.Tasks.STATUS_COMPLETED:
task.setStatus(Status.VTODO_COMPLETED);
break;
case TaskContract.Tasks.STATUS_CANCELLED:
task.setStatus(Status.VTODO_CANCELLED);
break;
default:
task.setStatus(Status.VTODO_NEEDS_ACTION);
}
if (!cursor.isNull(7))
task.setPercentComplete(cursor.getInt(7));
if (!cursor.isNull(8) && !cursor.isNull(9)) {
long ts = cursor.getLong(8);
boolean allDay = cursor.getInt(9) != 0;
task.setDtStart(new DtStart(allDay ? new Date(ts) : new DateTime(ts)));
}
/*if (!cursor.isNull(10) && !cursor.isNull(11)) {
long ts = cursor.getLong(10);
// boolean allDay = cursor.getInt(11) != 0;
task.setCompletedAt(new Completed(allDay ? new Date(ts) : new DateTime(ts)));
}*/
}
} catch (RemoteException e) {
throw new LocalStorageException("Couldn't process locally stored task", e);
}
}
@Override
protected ContentProviderOperation.Builder buildEntry(ContentProviderOperation.Builder builder, Resource resource, boolean update) {
final Task task = (Task)resource;
if (!update)
builder = builder
.withValue(entryColumnParentID(), id)
.withValue(entryColumnRemoteName(), task.getName());
builder = builder
.withValue(entryColumnUID(), task.getUid())
.withValue(entryColumnETag(), task.getETag())
.withValue(TaskContract.Tasks.TITLE, task.getSummary())
.withValue(TaskContract.Tasks.LOCATION, task.getLocation())
.withValue(TaskContract.Tasks.DESCRIPTION, task.getDescription())
.withValue(TaskContract.Tasks.URL, task.getUrl());
if (task.getClassification() != null) {
int classCode = TaskContract.Tasks.CLASSIFICATION_PRIVATE;
if (task.getClassification() == Clazz.PUBLIC)
classCode = TaskContract.Tasks.CLASSIFICATION_PUBLIC;
else if (task.getClassification() == Clazz.CONFIDENTIAL)
classCode = TaskContract.Tasks.CLASSIFICATION_CONFIDENTIAL;
builder = builder.withValue(TaskContract.Tasks.CLASSIFICATION, classCode);
}
int statusCode = TaskContract.Tasks.STATUS_DEFAULT;
if (task.getStatus() != null) {
if (task.getStatus() == Status.VTODO_NEEDS_ACTION)
statusCode = TaskContract.Tasks.STATUS_NEEDS_ACTION;
else if (task.getStatus() == Status.VTODO_IN_PROCESS)
statusCode = TaskContract.Tasks.STATUS_IN_PROCESS;
else if (task.getStatus() == Status.VTODO_COMPLETED)
statusCode = TaskContract.Tasks.STATUS_COMPLETED;
else if (task.getStatus() == Status.VTODO_CANCELLED)
statusCode = TaskContract.Tasks.STATUS_CANCELLED;
}
builder = builder
.withValue(TaskContract.Tasks.STATUS, statusCode)
.withValue(TaskContract.Tasks.PERCENT_COMPLETE, task.getPercentComplete());
/*if (task.getCreatedAt() != null)
builder = builder.withValue(TaskContract.Tasks.CREATED, task.getCreatedAt().getDate().getTime());/*
if (task.getDtStart() != null) {
Date start = task.getDtStart().getDate();
boolean allDay;
if (start instanceof DateTime)
allDay = false;
else {
task.getDtStart().setUtc(true);
allDay = true;
}
long ts = start.getTime();
builder = builder.withValue(TaskContract.Tasks.DTSTART, ts);
builder = builder.withValue(TaskContract.Tasks.IS_ALLDAY, allDay ? 1 : 0);
}
/*if (task.getCompletedAt() != null) {
Date completed = task.getCompletedAt().getDate();
boolean allDay;
if (completed instanceof DateTime)
allDay = false;
else {
task.getCompletedAt().setUtc(true);
allDay = true;
}
long ts = completed.getTime();
builder = builder.withValue(TaskContract.Tasks.COMPLETED, ts);
builder = builder.withValue(TaskContract.Tasks.COMPLETED_IS_ALLDAY, allDay ? 1 : 0);
}*/
return builder;
}
@Override
protected void addDataRows(Resource resource, long localID, int backrefIdx) {
}
@Override
protected void removeDataRows(Resource resource) {
}
// helpers
@Override
protected Uri syncAdapterURI(Uri baseURI) {
return baseURI.buildUpon()
.appendQueryParameter(entryColumnAccountType(), /*account.type*/"davdroid.new")
.appendQueryParameter(entryColumnAccountName(), account.name)
.appendQueryParameter(TaskContract.CALLER_IS_SYNCADAPTER, "true")
.build();
}
protected static Uri taskListsURI(Account account) {
return TaskContract.TaskLists.CONTENT_URI.buildUpon()
.appendQueryParameter(TaskContract.TaskLists.ACCOUNT_TYPE, /*account.type*/"davdroid.new")
.appendQueryParameter(TaskContract.TaskLists.ACCOUNT_NAME, account.name)
.appendQueryParameter(TaskContract.CALLER_IS_SYNCADAPTER, "true")
.build();
}
}

View file

@ -0,0 +1,124 @@
package at.bitfire.davdroid.resource;
import android.util.Log;
import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.data.CalendarOutputter;
import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.ComponentList;
import net.fortuna.ical4j.model.PropertyList;
import net.fortuna.ical4j.model.ValidationException;
import net.fortuna.ical4j.model.component.VJournal;
import net.fortuna.ical4j.model.component.VToDo;
import net.fortuna.ical4j.model.property.Created;
import net.fortuna.ical4j.model.property.Description;
import net.fortuna.ical4j.model.property.DtStamp;
import net.fortuna.ical4j.model.property.Location;
import net.fortuna.ical4j.model.property.ProdId;
import net.fortuna.ical4j.model.property.Summary;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Version;
import net.fortuna.ical4j.util.SimpleHostInfo;
import net.fortuna.ical4j.util.UidGenerator;
import org.apache.commons.lang.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.syncadapter.DavSyncAdapter;
import lombok.Getter;
import lombok.Setter;
public class Note extends Resource {
private final static String TAG = "davdroid.Note";
@Getter @Setter Created created;
@Getter @Setter String summary, description;
public Note(String name, String ETag) {
super(name, ETag);
}
public Note(long localId, String name, String ETag)
{
super(localId, name, ETag);
}
@Override
public void initialize() {
UidGenerator generator = new UidGenerator(new SimpleHostInfo(DavSyncAdapter.getAndroidID()), String.valueOf(android.os.Process.myPid()));
uid = generator.generateUid().getValue();
name = uid + ".ics";
}
@Override
public void parseEntity(InputStream entity, AssetDownloader downloader) throws IOException, InvalidResourceException {
net.fortuna.ical4j.model.Calendar ical;
try {
CalendarBuilder builder = new CalendarBuilder();
ical = builder.build(entity);
if (ical == null)
throw new InvalidResourceException("No iCalendar found");
} catch (ParserException e) {
throw new InvalidResourceException(e);
}
ComponentList notes = ical.getComponents(Component.VJOURNAL);
if (notes == null || notes.isEmpty())
throw new InvalidResourceException("No VJOURNAL found");
VJournal note = (VJournal)notes.get(0);
if (note.getUid() != null)
uid = note.getUid().getValue();
if (note.getCreated() != null)
created = note.getCreated();
if (note.getSummary() != null)
summary = note.getSummary().getValue();
if (note.getDescription() != null)
description = note.getDescription().getValue();
}
@Override
public String getMimeType() {
return "text/calendar";
}
@Override
public ByteArrayOutputStream toEntity() throws IOException {
final net.fortuna.ical4j.model.Calendar ical = new net.fortuna.ical4j.model.Calendar();
ical.getProperties().add(Version.VERSION_2_0);
ical.getProperties().add(Constants.ICAL_PRODID);
final VJournal note = new VJournal();
ical.getComponents().add(note);
final PropertyList props = note.getProperties();
if (uid != null)
props.add(new Uid(uid));
if (summary != null)
props.add(new Summary(summary));
if (description != null)
props.add(new Description(description));
CalendarOutputter output = new CalendarOutputter(false);
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
output.output(ical, os);
} catch (ValidationException e) {
Log.e(TAG, "Generated invalid iCalendar");
}
return os;
}
}

View file

@ -69,7 +69,9 @@ public class ServerInfo implements Serializable {
VCardVersion vCardVersion;
String timezone;
boolean supportingEvents = false,
supportingNotes = false,
supportingTasks = false;
public String getTitle() {
if (title == null) {

View file

@ -0,0 +1,175 @@
package at.bitfire.davdroid.resource;
import android.util.Log;
import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.data.CalendarOutputter;
import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.ComponentList;
import net.fortuna.ical4j.model.PropertyList;
import net.fortuna.ical4j.model.ValidationException;
import net.fortuna.ical4j.model.component.VToDo;
import net.fortuna.ical4j.model.property.Clazz;
import net.fortuna.ical4j.model.property.Completed;
import net.fortuna.ical4j.model.property.Created;
import net.fortuna.ical4j.model.property.Description;
import net.fortuna.ical4j.model.property.DtStart;
import net.fortuna.ical4j.model.property.Location;
import net.fortuna.ical4j.model.property.PercentComplete;
import net.fortuna.ical4j.model.property.Priority;
import net.fortuna.ical4j.model.property.Status;
import net.fortuna.ical4j.model.property.Summary;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Url;
import net.fortuna.ical4j.model.property.Version;
import net.fortuna.ical4j.util.SimpleHostInfo;
import net.fortuna.ical4j.util.UidGenerator;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.syncadapter.DavSyncAdapter;
import lombok.Getter;
import lombok.Setter;
public class Task extends Resource {
private final static String TAG = "davdroid.Task";
@Getter @Setter String summary, location, description, url;
@Getter @Setter int priority;
@Getter @Setter Clazz classification;
@Getter @Setter Status status;
@Getter @Setter Created createdAt;
@Getter @Setter DtStart dtStart;
@Getter @Setter Completed completedAt;
@Getter @Setter Integer percentComplete;
public Task(String name, String ETag) {
super(name, ETag);
}
public Task(long localId, String name, String ETag)
{
super(localId, name, ETag);
}
@Override
public void initialize() {
UidGenerator generator = new UidGenerator(new SimpleHostInfo(DavSyncAdapter.getAndroidID()), String.valueOf(android.os.Process.myPid()));
uid = generator.generateUid().getValue();
name = uid + ".ics";
}
@Override
public void parseEntity(InputStream entity, AssetDownloader downloader) throws IOException, InvalidResourceException {
net.fortuna.ical4j.model.Calendar ical;
try {
CalendarBuilder builder = new CalendarBuilder();
ical = builder.build(entity);
if (ical == null)
throw new InvalidResourceException("No iCalendar found");
} catch (ParserException e) {
throw new InvalidResourceException(e);
}
ComponentList notes = ical.getComponents(Component.VTODO);
if (notes == null || notes.isEmpty())
throw new InvalidResourceException("No VTODO found");
VToDo todo = (VToDo)notes.get(0);
if (todo.getUid() != null)
uid = todo.getUid().getValue();
if (todo.getSummary() != null)
summary = todo.getSummary().getValue();
if (todo.getLocation() != null)
location = todo.getLocation().getValue();
if (todo.getDescription() != null)
description = todo.getDescription().getValue();
if (todo.getUrl() != null)
url = todo.getUrl().getValue();
priority = (todo.getPriority() != null) ? todo.getPriority().getLevel() : 0;
if (todo.getClassification() != null)
classification = todo.getClassification();
if (todo.getStatus() != null)
status = todo.getStatus();
if (todo.getCreated() != null)
createdAt = todo.getCreated();
if (todo.getStartDate() != null)
dtStart = todo.getStartDate();
if (todo.getDateCompleted() != null)
completedAt = todo.getDateCompleted();
if (todo.getPercentComplete() != null)
percentComplete = todo.getPercentComplete().getPercentage();
}
@Override
public String getMimeType() {
return "text/calendar";
}
@Override
public ByteArrayOutputStream toEntity() throws IOException {
final net.fortuna.ical4j.model.Calendar ical = new net.fortuna.ical4j.model.Calendar();
ical.getProperties().add(Version.VERSION_2_0);
ical.getProperties().add(Constants.ICAL_PRODID);
final VToDo todo = new VToDo();
ical.getComponents().add(todo);
final PropertyList props = todo.getProperties();
if (uid != null)
props.add(new Uid(uid));
if (summary != null)
props.add(new Summary(summary));
if (location != null)
props.add(new Location(location));
if (description != null)
props.add(new Description(description));
if (url != null)
try {
props.add(new Url(new URI(url)));
} catch (URISyntaxException e) {
Log.e(TAG, "Ignoring invalid task URL: " + url, e);
}
if (priority != 0)
props.add(new Priority(priority));
if (classification != null)
props.add(classification);
if (status != null)
props.add(status);
if (createdAt != null)
props.add(createdAt);
if (dtStart != null)
props.add(dtStart);
if (completedAt != null)
props.add(completedAt);
if (percentComplete != null)
props.add(new PercentComplete(percentComplete));
CalendarOutputter output = new CalendarOutputter(false);
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
output.output(ical, os);
} catch (ValidationException e) {
Log.e(TAG, "Generated invalid iCalendar");
}
return os;
}
}

View file

@ -28,7 +28,6 @@ import at.bitfire.davdroid.resource.RemoteCollection;
public class CalendarsSyncAdapterService extends Service {
private static SyncAdapter syncAdapter;
@Override
public void onCreate() {
if (syncAdapter == null)
@ -48,9 +47,8 @@ public class CalendarsSyncAdapterService extends Service {
private static class SyncAdapter extends DavSyncAdapter {
private final static String TAG = "davdroid.CalDAVSync";
private final static String TAG = "davdroid.CalendarsSync";
private SyncAdapter(Context context) {
super(context);
}

View file

@ -27,7 +27,6 @@ import at.bitfire.davdroid.resource.RemoteCollection;
public class ContactsSyncAdapterService extends Service {
private static ContactsSyncAdapter syncAdapter;
@Override
public void onCreate() {
if (syncAdapter == null)
@ -47,9 +46,8 @@ public class ContactsSyncAdapterService extends Service {
private static class ContactsSyncAdapter extends DavSyncAdapter {
private final static String TAG = "davdroid.CardDAVSync";
private final static String TAG = "davdroid.ContactsSync";
private ContactsSyncAdapter(Context context) {
super(context);
}

View file

@ -0,0 +1,73 @@
package at.bitfire.davdroid.syncadapter;
import android.accounts.Account;
import android.app.Service;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import at.bitfire.davdroid.resource.CalDavNotebook;
import at.bitfire.davdroid.resource.LocalCollection;
import at.bitfire.davdroid.resource.LocalNotebook;
import at.bitfire.davdroid.resource.RemoteCollection;
public class NotesSyncAdapterService extends Service {
private static NotesSyncAdapter syncAdapter;
@Override
public void onCreate() {
if (syncAdapter == null)
syncAdapter = new NotesSyncAdapter(getApplicationContext());
}
@Override
public void onDestroy() {
syncAdapter.close();
syncAdapter = null;
}
@Override
public IBinder onBind(Intent intent) {
return syncAdapter.getSyncAdapterBinder();
}
private static class NotesSyncAdapter extends DavSyncAdapter {
private final static String TAG = "davdroid.NotesSync";
private NotesSyncAdapter(Context context) {
super(context);
}
@Override
protected Map<LocalCollection<?>, RemoteCollection<?>> getSyncPairs(Account account, ContentProviderClient provider) {
AccountSettings settings = new AccountSettings(getContext(), account);
String userName = settings.getUserName(),
password = settings.getPassword();
boolean preemptive = settings.getPreemptiveAuth();
try {
Map<LocalCollection<?>, RemoteCollection<?>> map = new HashMap<LocalCollection<?>, RemoteCollection<?>>();
for (LocalNotebook noteList : LocalNotebook.findAll(account, provider)) {
RemoteCollection<?> dav = new CalDavNotebook(httpClient, noteList.getUrl(), userName, password, preemptive);
map.put(noteList, dav);
}
return map;
} catch (RemoteException ex) {
Log.e(TAG, "Couldn't find local notebooks", ex);
} catch (URISyntaxException ex) {
Log.e(TAG, "Couldn't build calendar URI", ex);
}
return null;
}
}
}

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2013 2015 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.davdroid.syncadapter;
import android.accounts.Account;
import android.app.Service;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import at.bitfire.davdroid.resource.CalDavCalendar;
import at.bitfire.davdroid.resource.CalDavTaskList;
import at.bitfire.davdroid.resource.LocalCalendar;
import at.bitfire.davdroid.resource.LocalCollection;
import at.bitfire.davdroid.resource.LocalTaskList;
import at.bitfire.davdroid.resource.RemoteCollection;
public class TasksSyncAdapterService extends Service {
private static SyncAdapter syncAdapter;
@Override
public void onCreate() {
if (syncAdapter == null)
syncAdapter = new SyncAdapter(getApplicationContext());
}
@Override
public void onDestroy() {
syncAdapter.close();
syncAdapter = null;
}
@Override
public IBinder onBind(Intent intent) {
return syncAdapter.getSyncAdapterBinder();
}
private static class SyncAdapter extends DavSyncAdapter {
private final static String TAG = "davdroid.TasksSync";
private SyncAdapter(Context context) {
super(context);
}
@Override
protected Map<LocalCollection<?>, RemoteCollection<?>> getSyncPairs(Account account, ContentProviderClient provider) {
AccountSettings settings = new AccountSettings(getContext(), account);
String userName = settings.getUserName(),
password = settings.getPassword();
boolean preemptive = settings.getPreemptiveAuth();
try {
Map<LocalCollection<?>, RemoteCollection<?>> map = new HashMap<LocalCollection<?>, RemoteCollection<?>>();
for (LocalTaskList calendar : LocalTaskList.findAll(account, provider)) {
RemoteCollection<?> dav = new CalDavTaskList(httpClient, calendar.getUrl(), userName, password, preemptive);
map.put(calendar, dav);
}
return map;
} catch (RemoteException ex) {
Log.e(TAG, "Couldn't find local task lists", ex);
} catch (URISyntaxException ex) {
Log.e(TAG, "Couldn't build task list URI", ex);
}
return null;
}
}
}

View file

@ -26,12 +26,18 @@ import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import org.dmfs.provider.tasks.TaskContract;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.LocalCalendar;
import at.bitfire.davdroid.resource.LocalNotebook;
import at.bitfire.davdroid.resource.LocalStorageException;
import at.bitfire.davdroid.resource.LocalTaskList;
import at.bitfire.davdroid.resource.ServerInfo;
import at.bitfire.davdroid.resource.Task;
import at.bitfire.davdroid.syncadapter.AccountSettings;
import at.bitfire.notebooks.provider.NoteContract;
public class AccountDetailsFragment extends Fragment implements TextWatcher {
public static final String KEY_SERVER_INFO = "server_info";
@ -102,22 +108,54 @@ public class AccountDetailsFragment extends Fragment implements TextWatcher {
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0);
if (accountManager.addAccountExplicitly(account, serverInfo.getPassword(), userData)) {
// account created, now create calendars
// account created, now create calendars ...
boolean syncCalendars = false;
for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars())
if (calendar.isEnabled())
if (calendar.isEnabled() && calendar.isSupportingEvents())
try {
LocalCalendar.create(account, getActivity().getContentResolver(), calendar);
syncCalendars = true;
} catch (LocalStorageException e) {
Toast.makeText(getActivity(), "Couldn't create calendar(s): " + e.getMessage(), Toast.LENGTH_LONG).show();
Toast.makeText(getActivity(), "Couldn't create calendar: " + e.getMessage(), Toast.LENGTH_LONG).show();
}
if (syncCalendars) {
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, CalendarContract.AUTHORITY, true);
} else
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 0);
// ... and notes
boolean syncNotes = false;
for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars())
if (calendar.isEnabled() && calendar.isSupportingNotes())
try {
LocalNotebook.create(account, getActivity().getContentResolver(), calendar);
syncNotes = true;
} catch (LocalStorageException e) {
Toast.makeText(getActivity(), "Couldn't create notebook: " + e.getMessage(), Toast.LENGTH_LONG).show();
}
if (syncNotes) {
ContentResolver.setIsSyncable(account, NoteContract.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, NoteContract.AUTHORITY, true);
} else
ContentResolver.setIsSyncable(account, NoteContract.AUTHORITY, 0);
// ... and tasks
boolean syncTasks = false;
for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars())
if (calendar.isEnabled() && calendar.isSupportingTasks())
try {
LocalTaskList.create(account, getActivity().getContentResolver(), calendar);
syncTasks = true;
} catch (LocalStorageException e) {
Toast.makeText(getActivity(), "Couldn't create task list: " + e.getMessage(), Toast.LENGTH_LONG).show();
}
if (syncTasks) {
ContentResolver.setIsSyncable(account, TaskContract.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, TaskContract.AUTHORITY, true);
} else
ContentResolver.setIsSyncable(account, TaskContract.AUTHORITY, 0);
getActivity().finish();
} else
Toast.makeText(getActivity(), "Couldn't create account (account with this name already existing?)", Toast.LENGTH_LONG).show();

View file

@ -19,5 +19,4 @@ public class DavCompFilter {
@Element(required=false,name="comp-filter")
@Getter @Setter DavCompFilter compFilter;
}

View file

@ -8,6 +8,6 @@ import lombok.Setter;
@Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav")
public class DavFilter {
@Element(required=false)
@Element(required=false,name="comp-filter")
@Getter @Setter DavCompFilter compFilter;
}

View file

@ -453,7 +453,7 @@ public class WebDavResource {
}
if (multiStatus.response == null) // empty response
throw new DavNoContentException();
return;
// member list will be built from response
List<WebDavResource> members = new LinkedList<WebDavResource>();

View file

@ -0,0 +1 @@
../../../../../../../../../notebooks/app/src/main/java/at/bitfire/notebooks/provider/NoteContract.java

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
<!--
~ Copyright (c) 2013 2015 Ricki Hirner (bitfire web engineering).
~ All rights reserved. This program and the accompanying materials
~ are made available under the terms of the GNU Public License v3.0
~ which accompanies this distribution, and is available at
~ http://www.gnu.org/licenses/gpl.html
-->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="bitfire.at.davdroid"
android:contentAuthority="at.bitfire.notebooks.provider"
android:allowParallelSyncs="true"
android:supportsUploading="true"
android:isAlwaysSyncable="true"
android:userVisible="true" />

View file

@ -0,0 +1,15 @@
<!--
~ Copyright (c) 2013 2015 Ricki Hirner (bitfire web engineering).
~ All rights reserved. This program and the accompanying materials
~ are made available under the terms of the GNU Public License v3.0
~ which accompanies this distribution, and is available at
~ http://www.gnu.org/licenses/gpl.html
-->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="bitfire.at.davdroid"
android:contentAuthority="de.azapps.mirakel.provider"
android:allowParallelSyncs="true"
android:supportsUploading="true"
android:isAlwaysSyncable="true"
android:userVisible="true" />