1
0
mirror of https://github.com/etesync/android synced 2024-11-25 17:38:13 +00:00

Sync tasks

* remove VJOURNAL/notes sync (will be implemented later)
* setup: add "install Tasks app" fragment
* version bump to 0.8.0-beta1
* use Tasks instead of Mirakel
* handle task list colors
* allow independent selection of calendar/task sync for the same CalDAV calendar
* minor refactoring (don't use return value of Builder)
* handle more task fields and time zones
* sync interval setting for tasks
This commit is contained in:
Ricki Hirner 2015-05-25 19:54:16 +02:00
parent aa7e582bc9
commit af011a65db
37 changed files with 714 additions and 1929 deletions

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
style="@style/TextView.Heading"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/setup_task_lists" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp"
android:text="@string/setup_select_task_lists" />
</LinearLayout>

View File

@ -9,7 +9,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="at.bitfire.davdroid"
android:versionCode="63" android:versionName="0.7.7"
android:versionCode="64" android:versionName="0.8.0-beta1"
android:installLocation="internalOnly">
<uses-sdk
@ -26,11 +26,8 @@
<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" />
<uses-permission android:name="org.dmfs.permission.READ_TASKS" />
<uses-permission android:name="org.dmfs.permission.WRITE_TASKS" />
<application
android:allowBackup="true"
@ -73,16 +70,6 @@
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" >

View File

@ -11,7 +11,7 @@ import net.fortuna.ical4j.model.property.ProdId;
public class Constants {
public static final String
APP_VERSION = "0.7.7",
APP_VERSION = "0.8.0-beta1",
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";

View File

@ -0,0 +1,20 @@
package at.bitfire.davdroid;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DAVUtils {
public static int CalDAVtoARGBColor(String davColor) {
int color = 0xFFC3EA6E; // fallback: "DAVdroid green"
if (davColor != null) {
Pattern p = Pattern.compile("#?(\\p{XDigit}{6})(\\p{XDigit}{2})?");
Matcher m = p.matcher(davColor);
if (m.find()) {
int color_rgb = Integer.parseInt(m.group(1), 16);
int color_alpha = m.group(2) != null ? (Integer.parseInt(m.group(2), 16) & 0xFF) : 0xFF;
color = (color_alpha << 24) | color_rgb;
}
}
return color;
}
}

View File

@ -11,6 +11,10 @@ package at.bitfire.davdroid;
import android.text.format.Time;
import android.util.Log;
import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory;
import net.fortuna.ical4j.model.TimeZone;
import net.fortuna.ical4j.model.TimeZoneRegistry;
import org.apache.commons.lang.StringUtils;
import java.util.SimpleTimeZone;
@ -18,13 +22,16 @@ import java.util.SimpleTimeZone;
public class DateUtils {
private final static String TAG = "davdroid.DateUtils";
private final static TimeZoneRegistry tzRegistry = new DefaultTimeZoneRegistryFactory().createRegistry();
public static String findAndroidTimezoneID(String tzID) {
String localTZ = null;
String availableTZs[] = SimpleTimeZone.getAvailableIDs();
// first, try to find an exact match (case insensitive)
for (String availableTZ : availableTZs)
if (tzID.equalsIgnoreCase(availableTZ)) {
if (availableTZ.equalsIgnoreCase(tzID)) {
localTZ = availableTZ;
break;
}
@ -48,4 +55,9 @@ public class DateUtils {
Log.d(TAG, "Assuming time zone " + localTZ + " for " + tzID);
return localTZ;
}
public static TimeZone getTimeZone(String tzID) {
return tzRegistry.getTimeZone(tzID);
}
}

View File

@ -1,72 +0,0 @@
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

@ -9,6 +9,7 @@ package at.bitfire.davdroid.resource;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
import org.apache.http.HttpException;
import org.apache.http.impl.client.CloseableHttpClient;
@ -127,7 +128,9 @@ public class DavResourceFinder implements Closeable {
if (homeSetCalendars.getMembers() != null)
possibleCalendars.addAll(homeSetCalendars.getMembers());
List<ServerInfo.ResourceInfo> calendars = new LinkedList<>();
List<ServerInfo.ResourceInfo>
calendars = new LinkedList<>(),
todoLists = new LinkedList<>();
for (WebDavResource resource : possibleCalendars)
if (resource.isCalendar()) {
Log.i(TAG, "Found calendar: " + resource.getLocation().getPath());
@ -140,36 +143,41 @@ public class DavResourceFinder implements Closeable {
);
info.setTimezone(resource.getTimezone());
boolean isCalendar = false,
isTodoList = false;
if (resource.getSupportedComponents() == null) {
// no info about supported components, assuming all components are supported
info.setSupportingEvents(true);
info.setSupportingNotes(true);
isCalendar = true;
isTodoList = true;
} else {
// CALDAV:supported-calendar-component-set available
for (String supportedComponent : resource.getSupportedComponents())
if ("VEVENT".equalsIgnoreCase(supportedComponent))
info.setSupportingEvents(true);
else if ("VJOURNAL".equalsIgnoreCase(supportedComponent))
info.setSupportingNotes(true);
isCalendar = true;
else if ("VTODO".equalsIgnoreCase(supportedComponent))
info.setSupportingTasks(true);
isTodoList = true;
if (!info.isSupportingEvents() && !info.isSupportingNotes() && !info.isSupportingTasks()) {
Log.i(TAG, "Ignoring this calendar because it supports neither VEVENT nor VJOURNAL nor VTODO");
if (!isCalendar && !isTodoList) {
Log.i(TAG, "Ignoring this calendar because it supports neither VEVENT nor VTODO");
continue;
}
}
calendars.add(info);
// use a copy constructor to allow different "enabled" status for calendars and todo lists
if (isCalendar)
calendars.add(new ServerInfo.ResourceInfo(info));
if (isTodoList)
todoLists.add(new ServerInfo.ResourceInfo(info));
}
serverInfo.setCalendars(calendars);
serverInfo.setTodoLists(todoLists);
} else
Log.w(TAG, "Found calendar home set, but it doesn't advertise CalDAV support");
}
if (!serverInfo.isCalDAV() && !serverInfo.isCardDAV())
throw new DavIncapableException(context.getString(R.string.setup_neither_caldav_nor_carddav));
}

View File

@ -658,8 +658,7 @@ public class LocalAddressBook extends LocalCollection<Contact> {
Contact contact = (Contact)resource;
if (!update)
builder = builder
.withValue(RawContacts.ACCOUNT_NAME, account.name)
builder .withValue(RawContacts.ACCOUNT_NAME, account.name)
.withValue(RawContacts.ACCOUNT_TYPE, account.type);
return builder
@ -800,14 +799,13 @@ public class LocalAddressBook extends LocalCollection<Contact> {
typeLabel = xNameToLabel(type.getValue());
}
builder = builder
.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
.withValue(Phone.NUMBER, number.getText())
.withValue(Phone.TYPE, typeCode)
.withValue(Phone.IS_PRIMARY, is_primary ? 1 : 0)
.withValue(Phone.IS_SUPER_PRIMARY, is_primary ? 1 : 0);
builder .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
.withValue(Phone.NUMBER, number.getText())
.withValue(Phone.TYPE, typeCode)
.withValue(Phone.IS_PRIMARY, is_primary ? 1 : 0)
.withValue(Phone.IS_SUPER_PRIMARY, is_primary ? 1 : 0);
if (typeLabel != null)
builder = builder.withValue(Phone.LABEL, typeLabel);
builder.withValue(Phone.LABEL, typeLabel);
return builder;
}
@ -834,14 +832,13 @@ public class LocalAddressBook extends LocalCollection<Contact> {
}
}
builder = builder
.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
builder .withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
.withValue(Email.ADDRESS, email.getValue())
.withValue(Email.TYPE, typeCode)
.withValue(Email.IS_PRIMARY, is_primary ? 1 : 0)
.withValue(Phone.IS_SUPER_PRIMARY, is_primary ? 1 : 0);
if (typeLabel != null)
builder = builder.withValue(Email.LABEL, typeLabel);
builder.withValue(Email.LABEL, typeLabel);
return builder;
}
@ -928,22 +925,20 @@ public class LocalAddressBook extends LocalCollection<Contact> {
if (sipAddress)
// save as SIP address
builder = builder
.withValue(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE)
.withValue(Im.DATA, impp.getHandle())
.withValue(Im.TYPE, typeCode);
builder .withValue(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE)
.withValue(Im.DATA, impp.getHandle())
.withValue(Im.TYPE, typeCode);
else {
// save as IM address
builder = builder
.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE)
.withValue(Im.DATA, impp.getHandle())
.withValue(Im.TYPE, typeCode)
.withValue(Im.PROTOCOL, protocolCode);
builder .withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE)
.withValue(Im.DATA, impp.getHandle())
.withValue(Im.TYPE, typeCode)
.withValue(Im.PROTOCOL, protocolCode);
if (protocolLabel != null)
builder = builder.withValue(Im.CUSTOM_PROTOCOL, protocolLabel);
builder.withValue(Im.CUSTOM_PROTOCOL, protocolLabel);
}
if (typeLabel != null)
builder = builder.withValue(Im.LABEL, typeLabel);
builder.withValue(Im.LABEL, typeLabel);
return builder;
}
@ -996,19 +991,18 @@ public class LocalAddressBook extends LocalCollection<Contact> {
typeLabel = xNameToLabel(address.getTypes().iterator().next().getValue());
}
builder = builder
.withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE)
.withValue(StructuredPostal.FORMATTED_ADDRESS, formattedAddress)
.withValue(StructuredPostal.TYPE, typeCode)
.withValue(StructuredPostal.STREET, address.getStreetAddress())
.withValue(StructuredPostal.POBOX, address.getPoBox())
.withValue(StructuredPostal.NEIGHBORHOOD, address.getExtendedAddress())
.withValue(StructuredPostal.CITY, address.getLocality())
.withValue(StructuredPostal.REGION, address.getRegion())
.withValue(StructuredPostal.POSTCODE, address.getPostalCode())
.withValue(StructuredPostal.COUNTRY, address.getCountry());
builder .withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE)
.withValue(StructuredPostal.FORMATTED_ADDRESS, formattedAddress)
.withValue(StructuredPostal.TYPE, typeCode)
.withValue(StructuredPostal.STREET, address.getStreetAddress())
.withValue(StructuredPostal.POBOX, address.getPoBox())
.withValue(StructuredPostal.NEIGHBORHOOD, address.getExtendedAddress())
.withValue(StructuredPostal.CITY, address.getLocality())
.withValue(StructuredPostal.REGION, address.getRegion())
.withValue(StructuredPostal.POSTCODE, address.getPostalCode())
.withValue(StructuredPostal.COUNTRY, address.getCountry());
if (typeLabel != null)
builder = builder.withValue(StructuredPostal.LABEL, typeLabel);
builder.withValue(StructuredPostal.LABEL, typeLabel);
return builder;
}

View File

@ -75,6 +75,7 @@ import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import at.bitfire.davdroid.DAVUtils;
import at.bitfire.davdroid.DateUtils;
import lombok.Cleanup;
import lombok.Getter;
@ -120,24 +121,13 @@ public class LocalCalendar extends LocalCollection<Event> {
final ContentProviderClient client = resolver.acquireContentProviderClient(CalendarContract.AUTHORITY);
if (client == null)
throw new LocalStorageException("No Calendar Provider found (Calendar app disabled?)");
int color = 0xFFC3EA6E; // fallback: "DAVdroid green"
if (info.getColor() != null) {
Pattern p = Pattern.compile("#?(\\p{XDigit}{6})(\\p{XDigit}{2})?");
Matcher m = p.matcher(info.getColor());
if (m.find()) {
int color_rgb = Integer.parseInt(m.group(1), 16);
int color_alpha = m.group(2) != null ? (Integer.parseInt(m.group(2), 16) & 0xFF) : 0xFF;
color = (color_alpha << 24) | color_rgb;
}
}
ContentValues values = new ContentValues();
values.put(Calendars.ACCOUNT_NAME, account.name);
values.put(Calendars.ACCOUNT_TYPE, account.type);
values.put(Calendars.NAME, info.getURL());
values.put(Calendars.CALENDAR_DISPLAY_NAME, info.getTitle());
values.put(Calendars.CALENDAR_COLOR, color);
values.put(Calendars.CALENDAR_COLOR, DAVUtils.CalDAVtoARGBColor(info.getColor()));
values.put(Calendars.OWNER_ACCOUNT, account.name);
values.put(Calendars.SYNC_EVENTS, 1);
values.put(Calendars.VISIBLE, 1);
@ -527,12 +517,10 @@ public class LocalCalendar extends LocalCollection<Event> {
final Event event = (Event)resource;
if (!update)
builder = builder
.withValue(Events.ACCOUNT_TYPE, account.type)
builder .withValue(Events.ACCOUNT_TYPE, account.type)
.withValue(Events.ACCOUNT_NAME, account.name);
builder = builder
.withValue(Events.CALENDAR_ID, id)
builder .withValue(Events.CALENDAR_ID, id)
.withValue(Events.ALL_DAY, event.isAllDay() ? 1 : 0)
.withValue(Events.DTSTART, event.getDtStartInMillis())
.withValue(Events.EVENT_TIMEZONE, event.getDtStartTzID())
@ -545,12 +533,11 @@ public class LocalCalendar extends LocalCollection<Event> {
final RecurrenceId recurrenceId = event.getRecurrenceId();
if (recurrenceId == null) {
// this event is a "master event" (not an exception)
builder = builder
.withValue(entryColumnRemoteName(), event.getName())
builder .withValue(entryColumnRemoteName(), event.getName())
.withValue(entryColumnETag(), event.getETag())
.withValue(entryColumnUID(), event.getUid());
} else {
builder = builder.withValue(Events.ORIGINAL_SYNC_ID, event.getName());
builder.withValue(Events.ORIGINAL_SYNC_ID, event.getName());
// ORIGINAL_INSTANCE_TIME and ORIGINAL_ALL_DAY is set in buildExceptions.
// It's not possible to use only the RECURRENCE-ID to calculate
@ -561,40 +548,38 @@ public class LocalCalendar extends LocalCollection<Event> {
boolean recurring = false;
if (event.getRrule() != null) {
recurring = true;
builder = builder.withValue(Events.RRULE, event.getRrule().getValue());
builder.withValue(Events.RRULE, event.getRrule().getValue());
}
if (!event.getRdates().isEmpty()) {
recurring = true;
builder = builder.withValue(Events.RDATE, recurrenceSetsToAndroidString(event.getRdates()));
builder.withValue(Events.RDATE, recurrenceSetsToAndroidString(event.getRdates()));
}
if (event.getExrule() != null)
builder = builder.withValue(Events.EXRULE, event.getExrule().getValue());
builder.withValue(Events.EXRULE, event.getExrule().getValue());
if (!event.getExdates().isEmpty())
builder = builder.withValue(Events.EXDATE, recurrenceSetsToAndroidString(event.getExdates()));
builder.withValue(Events.EXDATE, recurrenceSetsToAndroidString(event.getExdates()));
// set either DTEND for single-time events or DURATION for recurring events
// because that's the way Android likes it (see docs)
if (recurring) {
// calculate DURATION from start and end date
Duration duration = new Duration(event.getDtStart().getDate(), event.getDtEnd().getDate());
builder = builder.withValue(Events.DURATION, duration.getValue());
} else {
builder = builder
.withValue(Events.DTEND, event.getDtEndInMillis())
builder.withValue(Events.DURATION, duration.getValue());
} else
builder .withValue(Events.DTEND, event.getDtEndInMillis())
.withValue(Events.EVENT_END_TIMEZONE, event.getDtEndTzID());
}
if (event.getSummary() != null)
builder = builder.withValue(Events.TITLE, event.getSummary());
builder.withValue(Events.TITLE, event.getSummary());
if (event.getLocation() != null)
builder = builder.withValue(Events.EVENT_LOCATION, event.getLocation());
builder.withValue(Events.EVENT_LOCATION, event.getLocation());
if (event.getDescription() != null)
builder = builder.withValue(Events.DESCRIPTION, event.getDescription());
builder.withValue(Events.DESCRIPTION, event.getDescription());
if (event.getOrganizer() != null && event.getOrganizer().getCalAddress() != null) {
URI organizer = event.getOrganizer().getCalAddress();
if (organizer.getScheme() != null && organizer.getScheme().equalsIgnoreCase("mailto"))
builder = builder.withValue(Events.ORGANIZER, organizer.getSchemeSpecificPart());
builder.withValue(Events.ORGANIZER, organizer.getSchemeSpecificPart());
}
Status status = event.getStatus();
@ -604,13 +589,13 @@ public class LocalCalendar extends LocalCollection<Event> {
statusCode = Events.STATUS_CONFIRMED;
else if (status == Status.VEVENT_CANCELLED)
statusCode = Events.STATUS_CANCELED;
builder = builder.withValue(Events.STATUS, statusCode);
builder.withValue(Events.STATUS, statusCode);
}
builder = builder.withValue(Events.AVAILABILITY, event.isOpaque() ? Events.AVAILABILITY_BUSY : Events.AVAILABILITY_FREE);
builder.withValue(Events.AVAILABILITY, event.isOpaque() ? Events.AVAILABILITY_BUSY : Events.AVAILABILITY_FREE);
if (event.getForPublic() != null)
builder = builder.withValue(Events.ACCESS_LEVEL, event.getForPublic() ? Events.ACCESS_PUBLIC : Events.ACCESS_PRIVATE);
builder.withValue(Events.ACCESS_LEVEL, event.getForPublic() ? Events.ACCESS_PUBLIC : Events.ACCESS_PRIVATE);
return builder;
}
@ -683,7 +668,7 @@ public class LocalCalendar extends LocalCollection<Event> {
final Cn cn = (Cn)attendee.getParameter(Parameter.CN);
if (cn != null)
builder = builder.withValue(Attendees.ATTENDEE_NAME, cn.getValue());
builder.withValue(Attendees.ATTENDEE_NAME, cn.getValue());
int type = Attendees.TYPE_NONE;
@ -702,7 +687,7 @@ public class LocalCalendar extends LocalCollection<Event> {
else if (role == Role.REQ_PARTICIPANT)
type = Attendees.TYPE_REQUIRED;
}
builder = builder.withValue(Attendees.ATTENDEE_RELATIONSHIP, relationship);
builder.withValue(Attendees.ATTENDEE_RELATIONSHIP, relationship);
}
int status = Attendees.ATTENDEE_STATUS_NONE;

View File

@ -1,179 +0,0 @@
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

@ -6,6 +6,7 @@ import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
@ -13,15 +14,25 @@ import android.util.Log;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.Dur;
import net.fortuna.ical4j.model.TimeZone;
import net.fortuna.ical4j.model.TimeZoneRegistry;
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.DtStart;
import net.fortuna.ical4j.model.property.Due;
import net.fortuna.ical4j.model.property.Duration;
import net.fortuna.ical4j.model.property.Status;
import net.fortuna.ical4j.util.TimeZones;
import org.apache.commons.lang.StringUtils;
import org.dmfs.provider.tasks.TaskContract;
import java.util.LinkedList;
import at.bitfire.davdroid.DAVUtils;
import at.bitfire.davdroid.DateUtils;
import lombok.Cleanup;
import lombok.Getter;
@ -31,9 +42,11 @@ public class LocalTaskList extends LocalCollection<Task> {
@Getter protected String url;
@Getter protected long id;
public static final String TASKS_AUTHORITY = "org.dmfs.tasks";
protected static String COLLECTION_COLUMN_CTAG = TaskContract.TaskLists.SYNC1;
@Override protected Uri entriesURI() { return syncAdapterURI(TaskContract.Tasks.CONTENT_URI); }
@Override protected Uri entriesURI() { return syncAdapterURI(TaskContract.Tasks.getContentUri(TASKS_AUTHORITY)); }
@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; }
@ -46,15 +59,16 @@ public class LocalTaskList extends LocalCollection<Task> {
public static Uri create(Account account, ContentResolver resolver, ServerInfo.ResourceInfo info) throws LocalStorageException {
final ContentProviderClient client = resolver.acquireContentProviderClient(TaskContract.AUTHORITY);
final ContentProviderClient client = resolver.acquireContentProviderClient(TASKS_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.ACCOUNT_TYPE, account.type);
values.put(TaskContract.TaskLists._SYNC_ID, info.getURL());
values.put(TaskContract.TaskLists.LIST_NAME, info.getTitle());
values.put(TaskContract.TaskLists.LIST_COLOR, DAVUtils.CalDAVtoARGBColor(info.getColor()));
values.put(TaskContract.TaskLists.OWNER, account.name);
values.put(TaskContract.TaskLists.ACCESS_LEVEL, 0);
values.put(TaskContract.TaskLists.SYNC_ENABLED, 1);
@ -122,19 +136,36 @@ public class LocalTaskList extends LocalCollection<Task> {
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*/
/* 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.TZ, TaskContract.Tasks.DTSTART, TaskContract.Tasks.IS_ALLDAY,
/* 11 */ TaskContract.Tasks.DUE, TaskContract.Tasks.DURATION, TaskContract.Tasks.COMPLETED,
/* 14 */ TaskContract.Tasks.CREATED, TaskContract.Tasks.LAST_MODIFIED, TaskContract.Tasks.PRIORITY
}, 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(14))
task.setCreatedAt(new DateTime(cursor.getLong(14)));
if (!cursor.isNull(15))
task.setLastModified(new DateTime(cursor.getLong(15)));
if (!StringUtils.isEmpty(cursor.getString(1)))
task.setSummary(cursor.getString(1));
if (!StringUtils.isEmpty(cursor.getString(2)))
task.setLocation(cursor.getString(2));
if (!StringUtils.isEmpty(cursor.getString(3)))
task.setDescription(cursor.getString(3));
if (!StringUtils.isEmpty(cursor.getString(4)))
task.setUrl(cursor.getString(4));
if (!cursor.isNull(16))
task.setPriority(cursor.getInt(16));
if (!cursor.isNull(5))
switch (cursor.getInt(5)) {
@ -165,17 +196,37 @@ public class LocalTaskList extends LocalCollection<Task> {
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)));
TimeZone tz = null;
if (!cursor.isNull(8))
tz = DateUtils.getTimeZone(cursor.getString(8));
if (!cursor.isNull(9) && !cursor.isNull(10)) {
long ts = cursor.getLong(9);
boolean allDay = cursor.getInt(10) != 0;
Date dt;
if (allDay)
dt = new Date(ts);
else {
dt = new DateTime(ts);
if (tz != null)
((DateTime)dt).setTimeZone(tz);
}
task.setDtStart(new DtStart(dt));
}
/*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)));
}*/
if (!cursor.isNull(11)) {
DateTime dt = new DateTime(cursor.getLong(11));
if (tz != null)
dt.setTimeZone(tz);
task.setDue(new Due(dt));
}
if (!cursor.isNull(12))
task.setDuration(new Duration(new Dur(cursor.getString(12))));
if (!cursor.isNull(13))
task.setCompletedAt(new Completed(new DateTime(cursor.getLong(13))));
}
} catch (RemoteException e) {
@ -188,17 +239,21 @@ public class LocalTaskList extends LocalCollection<Task> {
final Task task = (Task)resource;
if (!update)
builder = builder
.withValue(entryColumnParentID(), id)
builder .withValue(entryColumnParentID(), id)
.withValue(entryColumnRemoteName(), task.getName());
builder = builder
.withValue(entryColumnUID(), task.getUid())
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());
.withValue(TaskContract.Tasks.URL, task.getUrl())
.withValue(TaskContract.Tasks.PRIORITY, task.getPriority());
if (task.getCreatedAt() != null)
builder.withValue(TaskContract.Tasks.CREATED, task.getCreatedAt().getTime());
if (task.getLastModified() != null)
builder.withValue(TaskContract.Tasks.LAST_MODIFIED, task.getLastModified().getTime());
if (task.getClassification() != null) {
int classCode = TaskContract.Tasks.CLASSIFICATION_PRIVATE;
@ -220,40 +275,54 @@ public class LocalTaskList extends LocalCollection<Task> {
else if (task.getStatus() == Status.VTODO_CANCELLED)
statusCode = TaskContract.Tasks.STATUS_CANCELLED;
}
builder = builder
.withValue(TaskContract.Tasks.STATUS, statusCode)
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());/*
TimeZone tz = null;
if (task.getDtStart() != null) {
Date start = task.getDtStart().getDate();
boolean allDay;
if (start instanceof DateTime)
if (start instanceof DateTime) {
allDay = false;
else {
task.getDtStart().setUtc(true);
tz = ((DateTime)start).getTimeZone();
} else
allDay = true;
}
long ts = start.getTime();
builder = builder.withValue(TaskContract.Tasks.DTSTART, ts);
builder = builder.withValue(TaskContract.Tasks.IS_ALLDAY, allDay ? 1 : 0);
builder .withValue(TaskContract.Tasks.DTSTART, ts)
.withValue(TaskContract.Tasks.IS_ALLDAY, allDay ? 1 : 0);
}
/*if (task.getCompletedAt() != null) {
if (task.getDue() != null) {
Due due = task.getDue();
builder.withValue(TaskContract.Tasks.DUE, due.getDate().getTime());
if (tz == null)
tz = due.getTimeZone();
} else if (task.getDuration() != null)
builder.withValue(TaskContract.Tasks.DURATION, task.getDuration().getValue());
if (task.getCompletedAt() != null) {
Date completed = task.getCompletedAt().getDate();
boolean allDay;
if (completed instanceof DateTime)
if (completed instanceof DateTime) {
allDay = false;
else {
if (tz == null)
tz = ((DateTime)completed).getTimeZone();
} 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);
}*/
builder .withValue(TaskContract.Tasks.COMPLETED, ts)
.withValue(TaskContract.Tasks.COMPLETED_IS_ALLDAY, allDay ? 1 : 0);
}
// TZ *must* be provided when DTSTART or DUE is set
if ((task.getDtStart() != null || task.getDue() != null) && tz == null)
tz = DateUtils.getTimeZone(TimeZones.GMT_ID);
if (tz != null)
builder.withValue(TaskContract.Tasks.TZ, DateUtils.findAndroidTimezoneID(tz.getID()));
return builder;
}
@ -269,18 +338,28 @@ public class LocalTaskList extends LocalCollection<Task> {
// helpers
public static boolean isAvailable(Context context) {
try {
@Cleanup("release") ContentProviderClient client = context.getContentResolver().acquireContentProviderClient(TASKS_AUTHORITY);
return client != null;
} catch (SecurityException e) {
Log.e(TAG, "DAVdroid is not allowed to access tasks", e);
return false;
}
}
@Override
protected Uri syncAdapterURI(Uri baseURI) {
return baseURI.buildUpon()
.appendQueryParameter(entryColumnAccountType(), /*account.type*/"davdroid.new")
.appendQueryParameter(entryColumnAccountType(), account.type)
.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")
return TaskContract.TaskLists.getContentUri(TASKS_AUTHORITY).buildUpon()
.appendQueryParameter(TaskContract.TaskLists.ACCOUNT_TYPE, account.type)
.appendQueryParameter(TaskContract.TaskLists.ACCOUNT_NAME, account.name)
.appendQueryParameter(TaskContract.CALLER_IS_SYNCADAPTER, "true")
.build();

View File

@ -19,9 +19,7 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor(suppressConstructorProperties=true)
@Data
public class ServerInfo implements Serializable {
private static final long serialVersionUID = 6744847358282980437L;
public class ServerInfo {
enum Scheme {
HTTP, HTTPS, MAILTO
}
@ -34,8 +32,9 @@ public class ServerInfo implements Serializable {
private boolean calDAV = false, cardDAV = false;
private List<ResourceInfo>
addressBooks = new LinkedList<ResourceInfo>(),
calendars = new LinkedList<ResourceInfo>();
addressBooks = new LinkedList<>(),
calendars = new LinkedList<>(),
todoLists = new LinkedList<>();
public boolean hasEnabledCalendars() {
@ -48,9 +47,7 @@ public class ServerInfo implements Serializable {
@RequiredArgsConstructor(suppressConstructorProperties=true)
@Data
public static class ResourceInfo implements Serializable {
private static final long serialVersionUID = -5516934508229552112L;
public static class ResourceInfo {
public enum Type {
ADDRESS_BOOK,
CALENDAR
@ -69,9 +66,25 @@ public class ServerInfo implements Serializable {
VCardVersion vCardVersion;
String timezone;
boolean supportingEvents = false,
supportingNotes = false,
supportingTasks = false;
// copy constructor
public ResourceInfo(ResourceInfo src) {
enabled = src.enabled;
type = src.type;
readOnly = src.readOnly;
URL = src.URL;
title = src.title;
description = src.description;
color = src.color;
vCardVersion = src.vCardVersion;
timezone = src.timezone;
}
// some logic
public String getTitle() {
if (title == null) {

View File

@ -7,6 +7,8 @@ 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.Date;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.PropertyList;
import net.fortuna.ical4j.model.ValidationException;
import net.fortuna.ical4j.model.component.VToDo;
@ -15,6 +17,9 @@ 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.Due;
import net.fortuna.ical4j.model.property.Duration;
import net.fortuna.ical4j.model.property.LastModified;
import net.fortuna.ical4j.model.property.Location;
import net.fortuna.ical4j.model.property.PercentComplete;
import net.fortuna.ical4j.model.property.Priority;
@ -41,13 +46,17 @@ import lombok.Setter;
public class Task extends Resource {
private final static String TAG = "davdroid.Task";
@Getter @Setter DateTime createdAt;
@Getter @Setter DateTime lastModified;
@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 Due due;
@Getter @Setter Duration duration;
@Getter @Setter Completed completedAt;
@Getter @Setter Integer percentComplete;
@ -90,6 +99,11 @@ public class Task extends Resource {
if (todo.getUid() != null)
uid = todo.getUid().getValue();
if (todo.getCreated() != null)
createdAt = todo.getCreated().getDateTime();
if (todo.getLastModified() != null)
lastModified = todo.getLastModified().getDateTime();
if (todo.getSummary() != null)
summary = todo.getSummary().getValue();
if (todo.getLocation() != null)
@ -105,8 +119,10 @@ public class Task extends Resource {
if (todo.getStatus() != null)
status = todo.getStatus();
if (todo.getCreated() != null)
createdAt = todo.getCreated();
if (todo.getDue() != null)
due = todo.getDue();
if (todo.getDuration() != null)
duration = todo.getDuration();
if (todo.getStartDate() != null)
dtStart = todo.getStartDate();
if (todo.getDateCompleted() != null)
@ -134,6 +150,11 @@ public class Task extends Resource {
if (uid != null)
props.add(new Uid(uid));
if (createdAt != null)
props.add(new Created(createdAt));
if (lastModified != null)
props.add(new LastModified(lastModified));
if (summary != null)
props.add(new Summary(summary));
if (location != null)
@ -153,8 +174,10 @@ public class Task extends Resource {
if (status != null)
props.add(status);
if (createdAt != null)
props.add(createdAt);
if (due != null)
props.add(due);
if (duration != null)
props.add(duration);
if (dtStart != null)
props.add(dtStart);
if (completedAt != null)

View File

@ -102,12 +102,12 @@ public class AccountSettings {
// sync. settings
public Long getContactsSyncInterval() {
if (ContentResolver.getIsSyncable(account, ContactsContract.AUTHORITY) <= 0)
public Long getSyncInterval(String authority) {
if (ContentResolver.getIsSyncable(account, authority) <= 0)
return null;
if (ContentResolver.getSyncAutomatically(account, ContactsContract.AUTHORITY)) {
List<PeriodicSync> syncs = ContentResolver.getPeriodicSyncs(account, ContactsContract.AUTHORITY);
if (ContentResolver.getSyncAutomatically(account, authority)) {
List<PeriodicSync> syncs = ContentResolver.getPeriodicSyncs(account, authority);
if (syncs.isEmpty())
return SYNC_INTERVAL_MANUALLY;
else
@ -116,35 +116,12 @@ public class AccountSettings {
return SYNC_INTERVAL_MANUALLY;
}
public void setContactsSyncInterval(long seconds) {
public void setSyncInterval(String authority, long seconds) {
if (seconds == SYNC_INTERVAL_MANUALLY) {
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, false);
ContentResolver.setSyncAutomatically(account, authority, false);
} else {
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
ContentResolver.addPeriodicSync(account, ContactsContract.AUTHORITY, new Bundle(), seconds);
}
}
public Long getCalendarsSyncInterval() {
if (ContentResolver.getIsSyncable(account, CalendarContract.AUTHORITY) <= 0)
return null;
if (ContentResolver.getSyncAutomatically(account, CalendarContract.AUTHORITY)) {
List<PeriodicSync> syncs = ContentResolver.getPeriodicSyncs(account, CalendarContract.AUTHORITY);
if (syncs.isEmpty())
return SYNC_INTERVAL_MANUALLY;
else
return syncs.get(0).period;
} else
return SYNC_INTERVAL_MANUALLY;
}
public void setCalendarsSyncInterval(long seconds) {
if (seconds == SYNC_INTERVAL_MANUALLY) {
ContentResolver.setSyncAutomatically(account, CalendarContract.AUTHORITY, false);
} else {
ContentResolver.setSyncAutomatically(account, CalendarContract.AUTHORITY, true);
ContentResolver.addPeriodicSync(account, CalendarContract.AUTHORITY, new Bundle(), seconds);
ContentResolver.setSyncAutomatically(account, authority, true);
ContentResolver.addPeriodicSync(account, authority, new Bundle(), seconds);
}
}

View File

@ -1,73 +0,0 @@
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

@ -20,9 +20,7 @@ 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;

View File

@ -17,8 +17,13 @@ import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.SwitchPreference;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
import org.dmfs.provider.tasks.TaskContract;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.LocalTaskList;
import at.bitfire.davdroid.syncadapter.AccountSettings;
import ezvcard.VCardVersion;
import lombok.Setter;
@ -78,7 +83,7 @@ public class AccountFragment extends PreferenceFragment {
// category: synchronization
final ListPreference prefSyncContacts = (ListPreference)findPreference("sync_interval_contacts");
final Long syncIntervalContacts = settings.getContactsSyncInterval();
final Long syncIntervalContacts = settings.getSyncInterval(ContactsContract.AUTHORITY);
if (syncIntervalContacts != null) {
prefSyncContacts.setValue(syncIntervalContacts.toString());
if (syncIntervalContacts == AccountSettings.SYNC_INTERVAL_MANUALLY)
@ -88,7 +93,7 @@ public class AccountFragment extends PreferenceFragment {
prefSyncContacts.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setContactsSyncInterval(Long.parseLong((String)newValue));
settings.setSyncInterval(ContactsContract.AUTHORITY, Long.parseLong((String) newValue));
readFromAccount();
return true;
}
@ -99,7 +104,7 @@ public class AccountFragment extends PreferenceFragment {
}
final ListPreference prefSyncCalendars = (ListPreference)findPreference("sync_interval_calendars");
final Long syncIntervalCalendars = settings.getCalendarsSyncInterval();
final Long syncIntervalCalendars = settings.getSyncInterval(CalendarContract.AUTHORITY);
if (syncIntervalCalendars != null) {
prefSyncCalendars.setValue(syncIntervalCalendars.toString());
if (syncIntervalCalendars == AccountSettings.SYNC_INTERVAL_MANUALLY)
@ -109,7 +114,7 @@ public class AccountFragment extends PreferenceFragment {
prefSyncCalendars.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setCalendarsSyncInterval(Long.parseLong((String)newValue));
settings.setSyncInterval(CalendarContract.AUTHORITY, Long.parseLong((String) newValue));
readFromAccount();
return true;
}
@ -119,6 +124,27 @@ public class AccountFragment extends PreferenceFragment {
prefSyncCalendars.setSummary(R.string.settings_sync_summary_not_available);
}
final ListPreference prefSyncTasks = (ListPreference)findPreference("sync_interval_tasks");
final Long syncIntervalTasks = settings.getSyncInterval(LocalTaskList.TASKS_AUTHORITY);
if (syncIntervalTasks != null) {
prefSyncTasks.setValue(syncIntervalTasks.toString());
if (syncIntervalTasks == AccountSettings.SYNC_INTERVAL_MANUALLY)
prefSyncTasks.setSummary(R.string.settings_sync_summary_manually);
else
prefSyncTasks.setSummary(getString(R.string.settings_sync_summary_periodically, syncIntervalTasks / 60));
prefSyncTasks.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setSyncInterval(LocalTaskList.TASKS_AUTHORITY, Long.parseLong((String) newValue));
readFromAccount();
return true;
}
});
} else {
prefSyncTasks.setEnabled(false);
prefSyncTasks.setSummary(R.string.settings_sync_summary_not_available);
}
// category: address book
final CheckBoxPreference prefVCard4 = (CheckBoxPreference) findPreference("vcard4_support");
if (settings.getAddressBookURL() != null) { // does this account even have an address book?

View File

@ -16,6 +16,7 @@ import android.provider.CalendarContract;
import android.provider.ContactsContract;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -26,22 +27,19 @@ import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import org.dmfs.provider.tasks.TaskContract;
import java.util.List;
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";
public static final String TAG = "davdroid.AccountDetails";
ServerInfo serverInfo;
EditText editAccountName;
@ -51,7 +49,7 @@ public class AccountDetailsFragment extends Fragment implements TextWatcher {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.setup_account_details, container, false);
serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO);
serverInfo = ((AddAccountActivity)getActivity()).serverInfo;
editAccountName = (EditText)v.findViewById(R.id.account_name);
editAccountName.addTextChangedListener(this);
@ -87,81 +85,59 @@ public class AccountDetailsFragment extends Fragment implements TextWatcher {
// actions
void addAccount() {
ServerInfo serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO);
String accountName = editAccountName.getText().toString();
AccountManager accountManager = AccountManager.get(getActivity());
Account account = new Account(accountName, Constants.ACCOUNT_TYPE);
Bundle userData = AccountSettings.createBundle(serverInfo);
boolean syncContacts = false;
for (ServerInfo.ResourceInfo addressBook : serverInfo.getAddressBooks())
if (addressBook.isEnabled()) {
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
syncContacts = true;
continue;
}
if (syncContacts) {
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
} else
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0);
if (accountManager.addAccountExplicitly(account, serverInfo.getPassword(), userData)) {
// account created, now create calendars ...
boolean syncCalendars = false;
for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars())
if (calendar.isEnabled() && calendar.isSupportingEvents())
try {
LocalCalendar.create(account, getActivity().getContentResolver(), calendar);
syncCalendars = true;
} catch (LocalStorageException e) {
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);
addSync(account, ContactsContract.AUTHORITY, serverInfo.getAddressBooks(), null);
// ... 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);
addSync(account, CalendarContract.AUTHORITY, serverInfo.getCalendars(), new AddSyncCallback() {
@Override
public void createLocalCollection(Account account, ServerInfo.ResourceInfo calendar) throws LocalStorageException {
LocalCalendar.create(account, getActivity().getContentResolver(), calendar);
}
});
// ... 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);
addSync(account, LocalTaskList.TASKS_AUTHORITY, serverInfo.getTodoLists(), new AddSyncCallback() {
@Override
public void createLocalCollection(Account account, ServerInfo.ResourceInfo todoList) throws LocalStorageException {
LocalTaskList.create(account, getActivity().getContentResolver(), todoList);
}
});
getActivity().finish();
} else
Toast.makeText(getActivity(), "Couldn't create account (account with this name already existing?)", Toast.LENGTH_LONG).show();
}
protected interface AddSyncCallback {
void createLocalCollection(Account account, ServerInfo.ResourceInfo resource) throws LocalStorageException;
}
protected void addSync(Account account, String authority, List<ServerInfo.ResourceInfo> resourceList, AddSyncCallback callback) {
boolean sync = false;
for (ServerInfo.ResourceInfo resource : resourceList)
if (resource.isEnabled()) {
sync = true;
if (callback != null)
try {
callback.createLocalCollection(account, resource);
} catch(LocalStorageException e) {
Log.e(TAG, "Couldn't add sync collection", e);
Toast.makeText(getActivity(), "Couldn't set up synchronization for " + authority, Toast.LENGTH_LONG).show();
}
}
if (sync) {
ContentResolver.setIsSyncable(account, authority, 1);
ContentResolver.setSyncAutomatically(account, authority, true);
} else
ContentResolver.setIsSyncable(account, authority, 0);
}
// input validation
@Override

View File

@ -14,12 +14,17 @@ import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.ServerInfo;
public class AddAccountActivity extends Activity {
protected ServerInfo serverInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -40,6 +45,11 @@ public class AddAccountActivity extends Activity {
return true;
}
public void installTasksApp(View view) {
final Intent intent = new Intent(Intent.ACTION_VIEW).setData(Uri.parse("market://details?id=org.dmfs.tasks"));
startActivity(intent);
}
public void showHelp(MenuItem item) {
startActivityForResult(new Intent(Intent.ACTION_VIEW, Uri.parse(Constants.WEB_URL_HELP)), 0);
}

View File

@ -0,0 +1,96 @@
/*
* 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.ui.setup;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.LocalTaskList;
public class InstallAppsFragment extends Fragment implements Runnable {
private static final String TAG = "davdroid.setup";
final protected Handler timerHandler = new Handler();
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.setup_install_apps, container, false);
setHasOptionsMenu(true);
return v;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.only_skip, menu);
}
@Override
public void onResume() {
super.onResume();
timerHandler.postDelayed(this, 1000);
}
@Override
public void onPause() {
super.onPause();
timerHandler.removeCallbacks(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.skip:
skip(true);
break;
default:
return false;
}
return true;
}
@Override
public void run() {
if (LocalTaskList.isAvailable(getActivity()))
skip(false);
else
timerHandler.postDelayed(this, 1000);
}
protected void skip(boolean addToBackStack) {
FragmentManager fm = getFragmentManager();
if (!addToBackStack)
fm.popBackStack();
getFragmentManager().beginTransaction()
.replace(R.id.right_pane, new SelectCollectionsFragment())
.addToBackStack(null)
.commit();
}
}

View File

@ -8,6 +8,7 @@
package at.bitfire.davdroid.ui.setup;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.AsyncTaskLoader;
import android.content.Context;
@ -30,12 +31,13 @@ import java.security.cert.CertPathValidatorException;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.DavResourceFinder;
import at.bitfire.davdroid.resource.LocalTaskList;
import at.bitfire.davdroid.resource.ServerInfo;
import at.bitfire.davdroid.webdav.DavException;
import lombok.Cleanup;
public class QueryServerDialogFragment extends DialogFragment implements LoaderCallbacks<ServerInfo> {
private static final String TAG = "davdroid.QueryServerDialogFragment";
private static final String TAG = "davdroid.QueryServer";
public static final String
EXTRA_BASE_URI = "base_uri",
EXTRA_USER_NAME = "user_name",
@ -71,13 +73,16 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
if (serverInfo.getErrorMessage() != null)
Toast.makeText(getActivity(), serverInfo.getErrorMessage(), Toast.LENGTH_LONG).show();
else {
SelectCollectionsFragment selectCollections = new SelectCollectionsFragment();
Bundle arguments = new Bundle();
arguments.putSerializable(SelectCollectionsFragment.KEY_SERVER_INFO, serverInfo);
selectCollections.setArguments(arguments);
((AddAccountActivity)getActivity()).serverInfo = serverInfo;
Fragment nextFragment;
if (!serverInfo.getTodoLists().isEmpty() && !LocalTaskList.isAvailable(getActivity()))
nextFragment = new InstallAppsFragment();
else
nextFragment = new SelectCollectionsFragment();
getFragmentManager().beginTransaction()
.replace(R.id.right_pane, selectCollections)
.replace(R.id.right_pane, nextFragment)
.addToBackStack(null)
.commitAllowingStateLoss();
}

View File

@ -18,28 +18,50 @@ import android.widget.CheckedTextView;
import android.widget.ListAdapter;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.LocalTaskList;
import at.bitfire.davdroid.resource.ServerInfo;
import lombok.Getter;
/**
* Order of display:
*
* number of rows type
* nAddressBookHeadings (0 or 1) heading: "address books"
* nAddressBooks address book info
* nCalendarHeadings (0 or 1) heading: "calendars"
* nCalendars calendar info
* nNotebookHeadings (0 or 1) heading: "notebooks"
* nNotebooks notebook info
* nTaskListHeadings (0 or 1) heading: "task lists"
* nTaskLists task list info
*/
public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter {
final static int TYPE_ADDRESS_BOOKS_HEADING = 0,
TYPE_ADDRESS_BOOKS_ROW = 1,
TYPE_CALENDARS_HEADING = 2,
TYPE_CALENDARS_ROW = 3;
final static int
TYPE_ADDRESS_BOOKS_HEADING = 0,
TYPE_ADDRESS_BOOKS_ROW = 1,
TYPE_CALENDARS_HEADING = 2,
TYPE_CALENDARS_ROW = 3,
TYPE_TASK_LISTS_HEADING = 4,
TYPE_TASK_LISTS_ROW = 5;
protected Context context;
protected ServerInfo serverInfo;
@Getter protected int nAddressBooks, nAddressbookHeadings, nCalendars, nCalendarHeadings;
@Getter protected int
nAddressBooks, nAddressBookHeadings,
nCalendars, nCalendarHeadings,
nTaskLists, nTaskListHeadings;
public SelectCollectionsAdapter(Context context, ServerInfo serverInfo) {
this.context = context;
this.serverInfo = serverInfo;
nAddressBooks = (serverInfo.getAddressBooks() == null) ? 0 : serverInfo.getAddressBooks().size();
nAddressbookHeadings = (nAddressBooks == 0) ? 0 : 1;
nCalendars = (serverInfo.getCalendars() == null) ? 0 : serverInfo.getCalendars().size();
nCalendarHeadings = (nCalendars == 0) ? 0 : 1;
nAddressBooks = serverInfo.getAddressBooks() == null ? 0 : serverInfo.getAddressBooks().size();
nAddressBookHeadings = nAddressBooks == 0 ? 0 : 1;
nCalendars = serverInfo.getCalendars() == null ? 0 : serverInfo.getCalendars().size();
nCalendarHeadings = nCalendars == 0 ? 0 : 1;
nTaskLists = serverInfo.getTodoLists() == null ? 0 : serverInfo.getTodoLists().size();
nTaskListHeadings = nTaskLists == 0 ? 0 : 1;
}
@ -47,17 +69,23 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter
@Override
public int getCount() {
return nAddressbookHeadings + nAddressBooks + nCalendarHeadings + nCalendars;
return nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars + nTaskListHeadings + nTaskLists;
}
@Override
public Object getItem(int position) {
if (position >= nAddressbookHeadings &&
position < (nAddressbookHeadings + nAddressBooks))
return serverInfo.getAddressBooks().get(position - nAddressbookHeadings);
else if (position >= (nAddressbookHeadings + nAddressBooks + nCalendarHeadings) &&
(position < (nAddressbookHeadings + nAddressBooks + nCalendarHeadings + nCalendars)))
return serverInfo.getCalendars().get(position - (nAddressbookHeadings + nAddressBooks + nCalendarHeadings));
if (position >= nAddressBookHeadings &&
position < (nAddressBookHeadings + nAddressBooks))
return serverInfo.getAddressBooks().get(position - nAddressBookHeadings);
else if (position >= (nAddressBookHeadings + nAddressBooks + nCalendarHeadings) &&
(position < (nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars)))
return serverInfo.getCalendars().get(position - (nAddressBookHeadings + nAddressBooks + nCalendarHeadings));
else if (position >= (nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars + nTaskListHeadings) &&
(position < (nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars + + nTaskListHeadings + nTaskLists)))
return serverInfo.getTodoLists().get(position - (nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars + nTaskListHeadings));
return null;
}
@ -76,19 +104,26 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter
@Override
public int getViewTypeCount() {
return 4;
return TYPE_TASK_LISTS_ROW + 1;
}
@Override
public int getItemViewType(int position) {
if ((nAddressbookHeadings != 0) && (position == 0))
if ((nAddressBookHeadings != 0) && (position == 0))
return TYPE_ADDRESS_BOOKS_HEADING;
else if ((nAddressbookHeadings != 0) && (position > 0) && (position < nAddressbookHeadings + nAddressBooks))
else if ((nAddressBooks != 0) && (position > 0) && (position < nAddressBookHeadings + nAddressBooks))
return TYPE_ADDRESS_BOOKS_ROW;
else if ((nCalendars != 0) && (position == nAddressbookHeadings + nAddressBooks))
else if ((nCalendarHeadings != 0) && (position == nAddressBookHeadings + nAddressBooks))
return TYPE_CALENDARS_HEADING;
else if ((nCalendars != 0) && (position > nAddressbookHeadings + nAddressBooks) && (position < nAddressbookHeadings + nAddressBooks + nCalendarHeadings + nCalendars))
else if ((nCalendars != 0) && (position > nAddressBookHeadings + nAddressBooks) && (position < nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars))
return TYPE_CALENDARS_ROW;
else if ((nTaskListHeadings != 0) && (position == nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars))
return TYPE_TASK_LISTS_HEADING;
else if ((nTaskLists != 0) && (position > nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars) && (position < nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars + nTaskListHeadings + nTaskLists))
return TYPE_TASK_LISTS_ROW;
else
return IGNORE_ITEM_VIEW_TYPE;
}
@ -97,11 +132,13 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter
@SuppressLint("InflateParams")
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
int viewType = getItemViewType(position);
// step 1: get view (either by creating or recycling)
if (v == null) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch (getItemViewType(position)) {
switch (viewType) {
case TYPE_ADDRESS_BOOKS_HEADING:
v = inflater.inflate(R.layout.setup_address_books_heading, parent, false);
break;
@ -112,21 +149,38 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter
case TYPE_CALENDARS_HEADING:
v = inflater.inflate(R.layout.setup_calendars_heading, parent, false);
break;
case TYPE_TASK_LISTS_HEADING:
v = inflater.inflate(R.layout.setup_task_lists_heading, parent, false);
break;
case TYPE_CALENDARS_ROW:
case TYPE_TASK_LISTS_ROW:
v = inflater.inflate(android.R.layout.simple_list_item_multiple_choice, null);
v.setPadding(0, 8, 0, 8);
}
}
// step 2: fill view with content
switch (getItemViewType(position)) {
switch (viewType) {
case TYPE_ADDRESS_BOOKS_ROW:
setContent((CheckedTextView)v, R.drawable.addressbook, (ServerInfo.ResourceInfo)getItem(position));
break;
case TYPE_CALENDARS_ROW:
setContent((CheckedTextView)v, R.drawable.calendar, (ServerInfo.ResourceInfo)getItem(position));
case TYPE_TASK_LISTS_ROW:
setContent((CheckedTextView)v, R.drawable.calendar, (ServerInfo.ResourceInfo) getItem(position));
}
// disable task list selection if there's no local task provider
if (viewType == TYPE_TASK_LISTS_ROW && !LocalTaskList.isAvailable(context)) {
final CheckedTextView check = (CheckedTextView)v;
check.setEnabled(false);
check.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
check.setChecked(false);
}
});
}
return v;
}
@ -156,6 +210,6 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter
@Override
public boolean isEnabled(int position) {
int type = getItemViewType(position);
return (type == TYPE_ADDRESS_BOOKS_ROW || type == TYPE_CALENDARS_ROW);
return (type == TYPE_ADDRESS_BOOKS_ROW || type == TYPE_CALENDARS_ROW || type == TYPE_TASK_LISTS_ROW);
}
}

View File

@ -24,13 +24,17 @@ import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.ServerInfo;
public class SelectCollectionsFragment extends ListFragment {
public static final String KEY_SERVER_INFO = "server_info";
protected ServerInfo serverInfo;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
serverInfo = ((AddAccountActivity)getActivity()).serverInfo;
View v = super.onCreateView(inflater, container, savedInstanceState);
setHasOptionsMenu(true);
return v;
}
@ -50,7 +54,6 @@ public class SelectCollectionsFragment extends ListFragment {
View header = getActivity().getLayoutInflater().inflate(R.layout.setup_select_collections_header, getListView(), false);
listView.addHeaderView(header, getListView(), false);
final ServerInfo serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO);
final SelectCollectionsAdapter adapter = new SelectCollectionsAdapter(view.getContext(), serverInfo);
setListAdapter(adapter);
@ -80,13 +83,13 @@ public class SelectCollectionsFragment extends ListFragment {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.next:
ServerInfo serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO);
// synchronize only selected collections
for (ServerInfo.ResourceInfo addressBook : serverInfo.getAddressBooks())
addressBook.setEnabled(false);
for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars())
calendar.setEnabled(false);
for (ServerInfo.ResourceInfo todoList : serverInfo.getTodoLists())
todoList.setEnabled(false);
ListAdapter adapter = getListView().getAdapter();
for (long id : getListView().getCheckedItemIds()) {
@ -95,14 +98,8 @@ public class SelectCollectionsFragment extends ListFragment {
info.setEnabled(true);
}
// pass to "account details" fragment
AccountDetailsFragment accountDetails = new AccountDetailsFragment();
Bundle arguments = new Bundle();
arguments.putSerializable(SelectCollectionsFragment.KEY_SERVER_INFO, serverInfo);
accountDetails.setArguments(arguments);
getFragmentManager().beginTransaction()
.replace(R.id.right_pane, accountDetails)
.replace(R.id.right_pane, new AccountDetailsFragment())
.addToBackStack(null)
.commitAllowingStateLoss();
break;

View File

@ -1 +0,0 @@
../../../../../../../../../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 @@
/home/rfc2822/Entwicklung/Android/task-provider/src/org/dmfs/provider/tasks/TaskContract.java

View File

@ -0,0 +1 @@
/home/rfc2822/Entwicklung/Android/task-provider/src/org/dmfs/provider/tasks/UriFactory.java

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
style="@style/TextView.Heading"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/setup_task_lists" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp"
android:text="@string/setup_select_task_lists" />
</LinearLayout>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp"
tools:context=".MainActivity" >
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/setup_install_apps_info"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/setup_install_tasks_app_info"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/setup_install_tasks_app"
android:onClick="installTasksApp"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
</ScrollView>

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2013 2015 Ricki Hirner (bitfire web engineering).
~ All rights reserved. This program and the accompanying materials
@ -6,10 +7,13 @@
~ 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" />
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/skip"
android:icon="@drawable/navigation_skip"
android:showAsAction="always|withText"
android:title="@string/skip">
</item>
</menu>

View File

@ -11,6 +11,7 @@
<!-- common strings -->
<string name="app_name">DAVdroid</string>
<string name="next">Weiter</string>
<string name="skip">Überspringen</string>
<string name="help">Hilfe</string>
<string name="exception_cert_path_validation">Nicht vertrauenswürdiges Zertifikat in der Zertifikatskette (siehe FAQ)</string>
@ -127,6 +128,7 @@
<string name="settings_sync_summary_periodically">Alle %d Minuten + sofort bei lokalen Änderungen</string>
<string name="settings_sync_summary_not_available">Nicht verfügbar</string>
<string name="settings_sync_interval_calendars">Häufigkeit der Kalender-Synchronisierung</string>
<string name="settings_sync_interval_tasks">Häufigkeit der Aufgaben-Synchronisierung</string>
<string-array name="settings_sync_interval_names">
<item>Nur manuell</item>
<item>Alle 5 Minuten</item>
@ -146,11 +148,18 @@
<string name="setup_neither_caldav_nor_carddav">An dieser Adresse konnte kein CalDAV- oder CardDAV-Dienst gefunden werden.</string>
<string name="setup_add_account">Konto hinzufügen</string>
<string name="setup_querying_server">Daten werden vom Server abgefragt. Bitte warten…</string>
<string name="setup_install_apps_info">Android bietet im Gegensatz zu Kontakten und Terminen keine integrierte Lösung für Aufgaben.</string>
<string name="setup_install_tasks_app_info">DAVdroid kann mit der "Aufgaben"-App von Marten Gajda synchronisieren. Sie können diese App installieren, um Aufgaben zu synchronisieren, oder auf Aufgaben verzichten und die Installation überspringen.</string>
<string name="setup_install_tasks_app">Aufgaben-App installieren</string>
<string name="setup_what_to_sync">Welche Ordner sollen synchronisiert werden?</string>
<string name="setup_address_books">Adressbücher</string>
<string name="setup_calendars">Kalender</string>
<string name="setup_notebooks">Notizbücher</string>
<string name="setup_task_lists">Aufgabenlisten</string>
<string name="setup_select_address_book">Ein oder kein Adressbuch auswählen (nochmal berühren, um abzuwählen):</string>
<string name="setup_select_calendars">Kalender zur Synchronisation auswählen:</string>
<string name="setup_select_notebooks">Notizbücher zur Synchronisation auswählen:</string>
<string name="setup_select_task_lists">Aufgabenlisten zur Synchronisation auswählen:</string>
<string name="setup_account_details">Konto-Details</string>
<string name="setup_account_name">Kontoname:</string>

View File

@ -12,6 +12,7 @@
<!-- common strings -->
<string name="app_name">DAVdroid</string>
<string name="next">Next</string>
<string name="skip">Skip</string>
<string name="help">Help</string>
<string name="exception_cert_path_validation">Untrusted certificate in certificate path. See FAQ for more info.</string>
@ -131,6 +132,7 @@
<string name="settings_sync_summary_periodically">Every %d minutes + immediately on local changes</string>
<string name="settings_sync_summary_not_available">Not available</string>
<string name="settings_sync_interval_calendars">Calendars sync. interval</string>
<string name="settings_sync_interval_tasks">Tasks sync. interval</string>
<string-array name="settings_sync_interval_seconds">
<item>-1</item>
<item>300</item>
@ -160,11 +162,16 @@
<string name="setup_neither_caldav_nor_carddav">No CalDAV-/CardDAV service is available at this location.</string>
<string name="setup_add_account">Add account</string>
<string name="setup_querying_server">Querying server. Please wait…</string>
<string name="setup_install_apps_info">Plain Android doesn\'t support to-do lists (in contrast to contacts and calendars).</string>
<string name="setup_install_tasks_app_info">DAVdroid is able to synchronize tasks with the "Tasks" app (by Marten Gajda). You may install this app if you want tasks to be synchronized or skip the installation.</string>
<string name="setup_install_tasks_app">Install Tasks app</string>
<string name="setup_what_to_sync">Which collections shall be synchronized?</string>
<string name="setup_address_books">Address books</string>
<string name="setup_calendars">Calendars</string>
<string name="setup_select_address_book">Select up to one address book (tap again to unselect):</string>
<string name="setup_select_calendars">Select your calendars:</string>
<string name="setup_task_lists">Task lists</string>
<string name="setup_select_address_book">Select up to one address book (tap again to unselect) for synchronization:</string>
<string name="setup_select_calendars">Select calendars for synchronization:</string>
<string name="setup_select_task_lists">Select task lists for synchronization:</string>
<string name="setup_account_details">Account details</string>
<string name="setup_account_name">Account name:</string>

View File

@ -50,6 +50,13 @@
android:entries="@array/settings_sync_interval_names"
android:entryValues="@array/settings_sync_interval_seconds" />
<ListPreference
android:key="sync_interval_tasks"
android:persistent="false"
android:title="@string/settings_sync_interval_tasks"
android:entries="@array/settings_sync_interval_names"
android:entryValues="@array/settings_sync_interval_seconds" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_carddav">

View File

@ -8,7 +8,7 @@
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="bitfire.at.davdroid"
android:contentAuthority="de.azapps.mirakel.provider"
android:contentAuthority="org.dmfs.tasks"
android:allowParallelSyncs="true"
android:supportsUploading="true"
android:isAlwaysSyncable="true"