1
0
mirror of https://github.com/etesync/android synced 2025-02-18 02:22:08 +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" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="at.bitfire.davdroid" package="at.bitfire.davdroid"
android:versionCode="63" android:versionName="0.7.7" android:versionCode="64" android:versionName="0.8.0-beta1"
android:installLocation="internalOnly"> android:installLocation="internalOnly">
<uses-sdk <uses-sdk
@ -26,11 +26,8 @@
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_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="org.dmfs.permission.READ_TASKS" />
<uses-permission android:name="org.dmfs.permission.WRITE_TASKS" />
<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 <application
android:allowBackup="true" android:allowBackup="true"
@ -73,16 +70,6 @@
android:name="android.content.SyncAdapter" android:name="android.content.SyncAdapter"
android:resource="@xml/sync_calendars" /> android:resource="@xml/sync_calendars" />
</service> </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 <service
android:name=".syncadapter.TasksSyncAdapterService" android:name=".syncadapter.TasksSyncAdapterService"
android:exported="true" > android:exported="true" >

View File

@ -11,7 +11,7 @@ import net.fortuna.ical4j.model.property.ProdId;
public class Constants { public class Constants {
public static final String public static final String
APP_VERSION = "0.7.7", APP_VERSION = "0.8.0-beta1",
ACCOUNT_TYPE = "bitfire.at.davdroid", ACCOUNT_TYPE = "bitfire.at.davdroid",
WEB_URL_HELP = "https://davdroid.bitfire.at/configuration?pk_campaign=davdroid-app", 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"; 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.text.format.Time;
import android.util.Log; 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 org.apache.commons.lang.StringUtils;
import java.util.SimpleTimeZone; import java.util.SimpleTimeZone;
@ -18,13 +22,16 @@ import java.util.SimpleTimeZone;
public class DateUtils { public class DateUtils {
private final static String TAG = "davdroid.DateUtils"; private final static String TAG = "davdroid.DateUtils";
private final static TimeZoneRegistry tzRegistry = new DefaultTimeZoneRegistryFactory().createRegistry();
public static String findAndroidTimezoneID(String tzID) { public static String findAndroidTimezoneID(String tzID) {
String localTZ = null; String localTZ = null;
String availableTZs[] = SimpleTimeZone.getAvailableIDs(); String availableTZs[] = SimpleTimeZone.getAvailableIDs();
// first, try to find an exact match (case insensitive) // first, try to find an exact match (case insensitive)
for (String availableTZ : availableTZs) for (String availableTZ : availableTZs)
if (tzID.equalsIgnoreCase(availableTZ)) { if (availableTZ.equalsIgnoreCase(tzID)) {
localTZ = availableTZ; localTZ = availableTZ;
break; break;
} }
@ -48,4 +55,9 @@ public class DateUtils {
Log.d(TAG, "Assuming time zone " + localTZ + " for " + tzID); Log.d(TAG, "Assuming time zone " + localTZ + " for " + tzID);
return localTZ; 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.content.Context;
import android.util.Log; import android.util.Log;
import android.widget.Toast;
import org.apache.http.HttpException; import org.apache.http.HttpException;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
@ -127,7 +128,9 @@ public class DavResourceFinder implements Closeable {
if (homeSetCalendars.getMembers() != null) if (homeSetCalendars.getMembers() != null)
possibleCalendars.addAll(homeSetCalendars.getMembers()); possibleCalendars.addAll(homeSetCalendars.getMembers());
List<ServerInfo.ResourceInfo> calendars = new LinkedList<>(); List<ServerInfo.ResourceInfo>
calendars = new LinkedList<>(),
todoLists = new LinkedList<>();
for (WebDavResource resource : possibleCalendars) for (WebDavResource resource : possibleCalendars)
if (resource.isCalendar()) { if (resource.isCalendar()) {
Log.i(TAG, "Found calendar: " + resource.getLocation().getPath()); Log.i(TAG, "Found calendar: " + resource.getLocation().getPath());
@ -140,36 +143,41 @@ public class DavResourceFinder implements Closeable {
); );
info.setTimezone(resource.getTimezone()); info.setTimezone(resource.getTimezone());
boolean isCalendar = false,
isTodoList = false;
if (resource.getSupportedComponents() == null) { if (resource.getSupportedComponents() == null) {
// no info about supported components, assuming all components are supported // no info about supported components, assuming all components are supported
info.setSupportingEvents(true); isCalendar = true;
info.setSupportingNotes(true); isTodoList = true;
} else { } else {
// CALDAV:supported-calendar-component-set available // CALDAV:supported-calendar-component-set available
for (String supportedComponent : resource.getSupportedComponents()) for (String supportedComponent : resource.getSupportedComponents())
if ("VEVENT".equalsIgnoreCase(supportedComponent)) if ("VEVENT".equalsIgnoreCase(supportedComponent))
info.setSupportingEvents(true); isCalendar = true;
else if ("VJOURNAL".equalsIgnoreCase(supportedComponent))
info.setSupportingNotes(true);
else if ("VTODO".equalsIgnoreCase(supportedComponent)) else if ("VTODO".equalsIgnoreCase(supportedComponent))
info.setSupportingTasks(true); isTodoList = true;
if (!info.isSupportingEvents() && !info.isSupportingNotes() && !info.isSupportingTasks()) { if (!isCalendar && !isTodoList) {
Log.i(TAG, "Ignoring this calendar because it supports neither VEVENT nor VJOURNAL nor VTODO"); Log.i(TAG, "Ignoring this calendar because it supports neither VEVENT nor VTODO");
continue; 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.setCalendars(calendars);
serverInfo.setTodoLists(todoLists);
} else } else
Log.w(TAG, "Found calendar home set, but it doesn't advertise CalDAV support"); Log.w(TAG, "Found calendar home set, but it doesn't advertise CalDAV support");
} }
if (!serverInfo.isCalDAV() && !serverInfo.isCardDAV()) if (!serverInfo.isCalDAV() && !serverInfo.isCardDAV())
throw new DavIncapableException(context.getString(R.string.setup_neither_caldav_nor_carddav)); 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; Contact contact = (Contact)resource;
if (!update) if (!update)
builder = builder builder .withValue(RawContacts.ACCOUNT_NAME, account.name)
.withValue(RawContacts.ACCOUNT_NAME, account.name)
.withValue(RawContacts.ACCOUNT_TYPE, account.type); .withValue(RawContacts.ACCOUNT_TYPE, account.type);
return builder return builder
@ -800,14 +799,13 @@ public class LocalAddressBook extends LocalCollection<Contact> {
typeLabel = xNameToLabel(type.getValue()); typeLabel = xNameToLabel(type.getValue());
} }
builder = builder builder .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE) .withValue(Phone.NUMBER, number.getText())
.withValue(Phone.NUMBER, number.getText()) .withValue(Phone.TYPE, typeCode)
.withValue(Phone.TYPE, typeCode) .withValue(Phone.IS_PRIMARY, is_primary ? 1 : 0)
.withValue(Phone.IS_PRIMARY, is_primary ? 1 : 0) .withValue(Phone.IS_SUPER_PRIMARY, is_primary ? 1 : 0);
.withValue(Phone.IS_SUPER_PRIMARY, is_primary ? 1 : 0);
if (typeLabel != null) if (typeLabel != null)
builder = builder.withValue(Phone.LABEL, typeLabel); builder.withValue(Phone.LABEL, typeLabel);
return builder; return builder;
} }
@ -834,14 +832,13 @@ public class LocalAddressBook extends LocalCollection<Contact> {
} }
} }
builder = builder builder .withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
.withValue(Email.ADDRESS, email.getValue()) .withValue(Email.ADDRESS, email.getValue())
.withValue(Email.TYPE, typeCode) .withValue(Email.TYPE, typeCode)
.withValue(Email.IS_PRIMARY, is_primary ? 1 : 0) .withValue(Email.IS_PRIMARY, is_primary ? 1 : 0)
.withValue(Phone.IS_SUPER_PRIMARY, is_primary ? 1 : 0); .withValue(Phone.IS_SUPER_PRIMARY, is_primary ? 1 : 0);
if (typeLabel != null) if (typeLabel != null)
builder = builder.withValue(Email.LABEL, typeLabel); builder.withValue(Email.LABEL, typeLabel);
return builder; return builder;
} }
@ -928,22 +925,20 @@ public class LocalAddressBook extends LocalCollection<Contact> {
if (sipAddress) if (sipAddress)
// save as SIP address // save as SIP address
builder = builder builder .withValue(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE)
.withValue(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE) .withValue(Im.DATA, impp.getHandle())
.withValue(Im.DATA, impp.getHandle()) .withValue(Im.TYPE, typeCode);
.withValue(Im.TYPE, typeCode);
else { else {
// save as IM address // save as IM address
builder = builder builder .withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE)
.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE) .withValue(Im.DATA, impp.getHandle())
.withValue(Im.DATA, impp.getHandle()) .withValue(Im.TYPE, typeCode)
.withValue(Im.TYPE, typeCode) .withValue(Im.PROTOCOL, protocolCode);
.withValue(Im.PROTOCOL, protocolCode);
if (protocolLabel != null) if (protocolLabel != null)
builder = builder.withValue(Im.CUSTOM_PROTOCOL, protocolLabel); builder.withValue(Im.CUSTOM_PROTOCOL, protocolLabel);
} }
if (typeLabel != null) if (typeLabel != null)
builder = builder.withValue(Im.LABEL, typeLabel); builder.withValue(Im.LABEL, typeLabel);
return builder; return builder;
} }
@ -996,19 +991,18 @@ public class LocalAddressBook extends LocalCollection<Contact> {
typeLabel = xNameToLabel(address.getTypes().iterator().next().getValue()); typeLabel = xNameToLabel(address.getTypes().iterator().next().getValue());
} }
builder = builder builder .withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE)
.withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE) .withValue(StructuredPostal.FORMATTED_ADDRESS, formattedAddress)
.withValue(StructuredPostal.FORMATTED_ADDRESS, formattedAddress) .withValue(StructuredPostal.TYPE, typeCode)
.withValue(StructuredPostal.TYPE, typeCode) .withValue(StructuredPostal.STREET, address.getStreetAddress())
.withValue(StructuredPostal.STREET, address.getStreetAddress()) .withValue(StructuredPostal.POBOX, address.getPoBox())
.withValue(StructuredPostal.POBOX, address.getPoBox()) .withValue(StructuredPostal.NEIGHBORHOOD, address.getExtendedAddress())
.withValue(StructuredPostal.NEIGHBORHOOD, address.getExtendedAddress()) .withValue(StructuredPostal.CITY, address.getLocality())
.withValue(StructuredPostal.CITY, address.getLocality()) .withValue(StructuredPostal.REGION, address.getRegion())
.withValue(StructuredPostal.REGION, address.getRegion()) .withValue(StructuredPostal.POSTCODE, address.getPostalCode())
.withValue(StructuredPostal.POSTCODE, address.getPostalCode()) .withValue(StructuredPostal.COUNTRY, address.getCountry());
.withValue(StructuredPostal.COUNTRY, address.getCountry());
if (typeLabel != null) if (typeLabel != null)
builder = builder.withValue(StructuredPostal.LABEL, typeLabel); builder.withValue(StructuredPostal.LABEL, typeLabel);
return builder; return builder;
} }

View File

@ -75,6 +75,7 @@ import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import at.bitfire.davdroid.DAVUtils;
import at.bitfire.davdroid.DateUtils; import at.bitfire.davdroid.DateUtils;
import lombok.Cleanup; import lombok.Cleanup;
import lombok.Getter; import lombok.Getter;
@ -120,24 +121,13 @@ public class LocalCalendar extends LocalCollection<Event> {
final ContentProviderClient client = resolver.acquireContentProviderClient(CalendarContract.AUTHORITY); final ContentProviderClient client = resolver.acquireContentProviderClient(CalendarContract.AUTHORITY);
if (client == null) if (client == null)
throw new LocalStorageException("No Calendar Provider found (Calendar app disabled?)"); 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(); ContentValues values = new ContentValues();
values.put(Calendars.ACCOUNT_NAME, account.name); values.put(Calendars.ACCOUNT_NAME, account.name);
values.put(Calendars.ACCOUNT_TYPE, account.type); values.put(Calendars.ACCOUNT_TYPE, account.type);
values.put(Calendars.NAME, info.getURL()); values.put(Calendars.NAME, info.getURL());
values.put(Calendars.CALENDAR_DISPLAY_NAME, info.getTitle()); 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.OWNER_ACCOUNT, account.name);
values.put(Calendars.SYNC_EVENTS, 1); values.put(Calendars.SYNC_EVENTS, 1);
values.put(Calendars.VISIBLE, 1); values.put(Calendars.VISIBLE, 1);
@ -527,12 +517,10 @@ public class LocalCalendar extends LocalCollection<Event> {
final Event event = (Event)resource; final Event event = (Event)resource;
if (!update) if (!update)
builder = builder builder .withValue(Events.ACCOUNT_TYPE, account.type)
.withValue(Events.ACCOUNT_TYPE, account.type)
.withValue(Events.ACCOUNT_NAME, account.name); .withValue(Events.ACCOUNT_NAME, account.name);
builder = builder builder .withValue(Events.CALENDAR_ID, id)
.withValue(Events.CALENDAR_ID, id)
.withValue(Events.ALL_DAY, event.isAllDay() ? 1 : 0) .withValue(Events.ALL_DAY, event.isAllDay() ? 1 : 0)
.withValue(Events.DTSTART, event.getDtStartInMillis()) .withValue(Events.DTSTART, event.getDtStartInMillis())
.withValue(Events.EVENT_TIMEZONE, event.getDtStartTzID()) .withValue(Events.EVENT_TIMEZONE, event.getDtStartTzID())
@ -545,12 +533,11 @@ public class LocalCalendar extends LocalCollection<Event> {
final RecurrenceId recurrenceId = event.getRecurrenceId(); final RecurrenceId recurrenceId = event.getRecurrenceId();
if (recurrenceId == null) { if (recurrenceId == null) {
// this event is a "master event" (not an exception) // this event is a "master event" (not an exception)
builder = builder builder .withValue(entryColumnRemoteName(), event.getName())
.withValue(entryColumnRemoteName(), event.getName())
.withValue(entryColumnETag(), event.getETag()) .withValue(entryColumnETag(), event.getETag())
.withValue(entryColumnUID(), event.getUid()); .withValue(entryColumnUID(), event.getUid());
} else { } 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. // ORIGINAL_INSTANCE_TIME and ORIGINAL_ALL_DAY is set in buildExceptions.
// It's not possible to use only the RECURRENCE-ID to calculate // 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; boolean recurring = false;
if (event.getRrule() != null) { if (event.getRrule() != null) {
recurring = true; recurring = true;
builder = builder.withValue(Events.RRULE, event.getRrule().getValue()); builder.withValue(Events.RRULE, event.getRrule().getValue());
} }
if (!event.getRdates().isEmpty()) { if (!event.getRdates().isEmpty()) {
recurring = true; recurring = true;
builder = builder.withValue(Events.RDATE, recurrenceSetsToAndroidString(event.getRdates())); builder.withValue(Events.RDATE, recurrenceSetsToAndroidString(event.getRdates()));
} }
if (event.getExrule() != null) if (event.getExrule() != null)
builder = builder.withValue(Events.EXRULE, event.getExrule().getValue()); builder.withValue(Events.EXRULE, event.getExrule().getValue());
if (!event.getExdates().isEmpty()) 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 // set either DTEND for single-time events or DURATION for recurring events
// because that's the way Android likes it (see docs) // because that's the way Android likes it (see docs)
if (recurring) { if (recurring) {
// calculate DURATION from start and end date // calculate DURATION from start and end date
Duration duration = new Duration(event.getDtStart().getDate(), event.getDtEnd().getDate()); Duration duration = new Duration(event.getDtStart().getDate(), event.getDtEnd().getDate());
builder = builder.withValue(Events.DURATION, duration.getValue()); builder.withValue(Events.DURATION, duration.getValue());
} else { } else
builder = builder builder .withValue(Events.DTEND, event.getDtEndInMillis())
.withValue(Events.DTEND, event.getDtEndInMillis())
.withValue(Events.EVENT_END_TIMEZONE, event.getDtEndTzID()); .withValue(Events.EVENT_END_TIMEZONE, event.getDtEndTzID());
}
if (event.getSummary() != null) if (event.getSummary() != null)
builder = builder.withValue(Events.TITLE, event.getSummary()); builder.withValue(Events.TITLE, event.getSummary());
if (event.getLocation() != null) if (event.getLocation() != null)
builder = builder.withValue(Events.EVENT_LOCATION, event.getLocation()); builder.withValue(Events.EVENT_LOCATION, event.getLocation());
if (event.getDescription() != null) 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) { if (event.getOrganizer() != null && event.getOrganizer().getCalAddress() != null) {
URI organizer = event.getOrganizer().getCalAddress(); URI organizer = event.getOrganizer().getCalAddress();
if (organizer.getScheme() != null && organizer.getScheme().equalsIgnoreCase("mailto")) 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(); Status status = event.getStatus();
@ -604,13 +589,13 @@ public class LocalCalendar extends LocalCollection<Event> {
statusCode = Events.STATUS_CONFIRMED; statusCode = Events.STATUS_CONFIRMED;
else if (status == Status.VEVENT_CANCELLED) else if (status == Status.VEVENT_CANCELLED)
statusCode = Events.STATUS_CANCELED; 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) 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; return builder;
} }
@ -683,7 +668,7 @@ public class LocalCalendar extends LocalCollection<Event> {
final Cn cn = (Cn)attendee.getParameter(Parameter.CN); final Cn cn = (Cn)attendee.getParameter(Parameter.CN);
if (cn != null) if (cn != null)
builder = builder.withValue(Attendees.ATTENDEE_NAME, cn.getValue()); builder.withValue(Attendees.ATTENDEE_NAME, cn.getValue());
int type = Attendees.TYPE_NONE; int type = Attendees.TYPE_NONE;
@ -702,7 +687,7 @@ public class LocalCalendar extends LocalCollection<Event> {
else if (role == Role.REQ_PARTICIPANT) else if (role == Role.REQ_PARTICIPANT)
type = Attendees.TYPE_REQUIRED; type = Attendees.TYPE_REQUIRED;
} }
builder = builder.withValue(Attendees.ATTENDEE_RELATIONSHIP, relationship); builder.withValue(Attendees.ATTENDEE_RELATIONSHIP, relationship);
} }
int status = Attendees.ATTENDEE_STATUS_NONE; 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.ContentResolver;
import android.content.ContentUris; import android.content.ContentUris;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.RemoteException; import android.os.RemoteException;
@ -13,15 +14,25 @@ import android.util.Log;
import net.fortuna.ical4j.model.Date; import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateTime; 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.Clazz;
import net.fortuna.ical4j.model.property.Completed; 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.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.model.property.Status;
import net.fortuna.ical4j.util.TimeZones;
import org.apache.commons.lang.StringUtils;
import org.dmfs.provider.tasks.TaskContract; import org.dmfs.provider.tasks.TaskContract;
import java.util.LinkedList; import java.util.LinkedList;
import at.bitfire.davdroid.DAVUtils;
import at.bitfire.davdroid.DateUtils;
import lombok.Cleanup; import lombok.Cleanup;
import lombok.Getter; import lombok.Getter;
@ -31,9 +42,11 @@ public class LocalTaskList extends LocalCollection<Task> {
@Getter protected String url; @Getter protected String url;
@Getter protected long id; @Getter protected long id;
public static final String TASKS_AUTHORITY = "org.dmfs.tasks";
protected static String COLLECTION_COLUMN_CTAG = TaskContract.TaskLists.SYNC1; 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 entryColumnAccountType() { return TaskContract.Tasks.ACCOUNT_TYPE; }
@Override protected String entryColumnAccountName() { return TaskContract.Tasks.ACCOUNT_NAME; } @Override protected String entryColumnAccountName() { return TaskContract.Tasks.ACCOUNT_NAME; }
@Override protected String entryColumnParentID() { return TaskContract.Tasks.LIST_ID; } @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 { 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) if (client == null)
throw new LocalStorageException("No tasks provider found"); throw new LocalStorageException("No tasks provider found");
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(TaskContract.TaskLists.ACCOUNT_NAME, account.name); 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._SYNC_ID, info.getURL());
values.put(TaskContract.TaskLists.LIST_NAME, info.getTitle()); 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.OWNER, account.name);
values.put(TaskContract.TaskLists.ACCESS_LEVEL, 0); values.put(TaskContract.TaskLists.ACCESS_LEVEL, 0);
values.put(TaskContract.TaskLists.SYNC_ENABLED, 1); values.put(TaskContract.TaskLists.SYNC_ENABLED, 1);
@ -122,19 +136,36 @@ public class LocalTaskList extends LocalCollection<Task> {
try { try {
@Cleanup final Cursor cursor = providerClient.query(entriesURI(), @Cleanup final Cursor cursor = providerClient.query(entriesURI(),
new String[] { new String[] {
/* 0 */ entryColumnUID(), TaskContract.Tasks.TITLE, TaskContract.Tasks.LOCATION, TaskContract.Tasks.DESCRIPTION, TaskContract.Tasks.URL, /* 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, /* 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*/ /* 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); }, entryColumnID() + "=?", new String[]{ String.valueOf(record.getLocalID()) }, null);
Task task = (Task)record; Task task = (Task)record;
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
task.setUid(cursor.getString(0)); task.setUid(cursor.getString(0));
task.setSummary(cursor.getString(1)); if (!cursor.isNull(14))
task.setLocation(cursor.getString(2)); task.setCreatedAt(new DateTime(cursor.getLong(14)));
task.setDescription(cursor.getString(3)); if (!cursor.isNull(15))
task.setUrl(cursor.getString(4)); 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)) if (!cursor.isNull(5))
switch (cursor.getInt(5)) { switch (cursor.getInt(5)) {
@ -165,17 +196,37 @@ public class LocalTaskList extends LocalCollection<Task> {
if (!cursor.isNull(7)) if (!cursor.isNull(7))
task.setPercentComplete(cursor.getInt(7)); task.setPercentComplete(cursor.getInt(7));
if (!cursor.isNull(8) && !cursor.isNull(9)) { TimeZone tz = null;
long ts = cursor.getLong(8); if (!cursor.isNull(8))
boolean allDay = cursor.getInt(9) != 0; tz = DateUtils.getTimeZone(cursor.getString(8));
task.setDtStart(new DtStart(allDay ? new Date(ts) : new DateTime(ts)));
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)) { if (!cursor.isNull(11)) {
long ts = cursor.getLong(10); DateTime dt = new DateTime(cursor.getLong(11));
// boolean allDay = cursor.getInt(11) != 0; if (tz != null)
task.setCompletedAt(new Completed(allDay ? new Date(ts) : new DateTime(ts))); 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) { } catch (RemoteException e) {
@ -188,17 +239,21 @@ public class LocalTaskList extends LocalCollection<Task> {
final Task task = (Task)resource; final Task task = (Task)resource;
if (!update) if (!update)
builder = builder builder .withValue(entryColumnParentID(), id)
.withValue(entryColumnParentID(), id)
.withValue(entryColumnRemoteName(), task.getName()); .withValue(entryColumnRemoteName(), task.getName());
builder = builder builder.withValue(entryColumnUID(), task.getUid())
.withValue(entryColumnUID(), task.getUid())
.withValue(entryColumnETag(), task.getETag()) .withValue(entryColumnETag(), task.getETag())
.withValue(TaskContract.Tasks.TITLE, task.getSummary()) .withValue(TaskContract.Tasks.TITLE, task.getSummary())
.withValue(TaskContract.Tasks.LOCATION, task.getLocation()) .withValue(TaskContract.Tasks.LOCATION, task.getLocation())
.withValue(TaskContract.Tasks.DESCRIPTION, task.getDescription()) .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) { if (task.getClassification() != null) {
int classCode = TaskContract.Tasks.CLASSIFICATION_PRIVATE; int classCode = TaskContract.Tasks.CLASSIFICATION_PRIVATE;
@ -220,40 +275,54 @@ public class LocalTaskList extends LocalCollection<Task> {
else if (task.getStatus() == Status.VTODO_CANCELLED) else if (task.getStatus() == Status.VTODO_CANCELLED)
statusCode = TaskContract.Tasks.STATUS_CANCELLED; statusCode = TaskContract.Tasks.STATUS_CANCELLED;
} }
builder = builder builder .withValue(TaskContract.Tasks.STATUS, statusCode)
.withValue(TaskContract.Tasks.STATUS, statusCode)
.withValue(TaskContract.Tasks.PERCENT_COMPLETE, task.getPercentComplete()); .withValue(TaskContract.Tasks.PERCENT_COMPLETE, task.getPercentComplete());
/*if (task.getCreatedAt() != null) TimeZone tz = null;
builder = builder.withValue(TaskContract.Tasks.CREATED, task.getCreatedAt().getDate().getTime());/*
if (task.getDtStart() != null) { if (task.getDtStart() != null) {
Date start = task.getDtStart().getDate(); Date start = task.getDtStart().getDate();
boolean allDay; boolean allDay;
if (start instanceof DateTime) if (start instanceof DateTime) {
allDay = false; allDay = false;
else { tz = ((DateTime)start).getTimeZone();
task.getDtStart().setUtc(true); } else
allDay = true; allDay = true;
}
long ts = start.getTime(); long ts = start.getTime();
builder = builder.withValue(TaskContract.Tasks.DTSTART, ts); builder .withValue(TaskContract.Tasks.DTSTART, ts)
builder = builder.withValue(TaskContract.Tasks.IS_ALLDAY, allDay ? 1 : 0); .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(); Date completed = task.getCompletedAt().getDate();
boolean allDay; boolean allDay;
if (completed instanceof DateTime) if (completed instanceof DateTime) {
allDay = false; allDay = false;
else { if (tz == null)
tz = ((DateTime)completed).getTimeZone();
} else {
task.getCompletedAt().setUtc(true); task.getCompletedAt().setUtc(true);
allDay = true; allDay = true;
} }
long ts = completed.getTime(); long ts = completed.getTime();
builder = builder.withValue(TaskContract.Tasks.COMPLETED, ts); builder .withValue(TaskContract.Tasks.COMPLETED, ts)
builder = builder.withValue(TaskContract.Tasks.COMPLETED_IS_ALLDAY, allDay ? 1 : 0); .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; return builder;
} }
@ -269,18 +338,28 @@ public class LocalTaskList extends LocalCollection<Task> {
// helpers // 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 @Override
protected Uri syncAdapterURI(Uri baseURI) { protected Uri syncAdapterURI(Uri baseURI) {
return baseURI.buildUpon() return baseURI.buildUpon()
.appendQueryParameter(entryColumnAccountType(), /*account.type*/"davdroid.new") .appendQueryParameter(entryColumnAccountType(), account.type)
.appendQueryParameter(entryColumnAccountName(), account.name) .appendQueryParameter(entryColumnAccountName(), account.name)
.appendQueryParameter(TaskContract.CALLER_IS_SYNCADAPTER, "true") .appendQueryParameter(TaskContract.CALLER_IS_SYNCADAPTER, "true")
.build(); .build();
} }
protected static Uri taskListsURI(Account account) { protected static Uri taskListsURI(Account account) {
return TaskContract.TaskLists.CONTENT_URI.buildUpon() return TaskContract.TaskLists.getContentUri(TASKS_AUTHORITY).buildUpon()
.appendQueryParameter(TaskContract.TaskLists.ACCOUNT_TYPE, /*account.type*/"davdroid.new") .appendQueryParameter(TaskContract.TaskLists.ACCOUNT_TYPE, account.type)
.appendQueryParameter(TaskContract.TaskLists.ACCOUNT_NAME, account.name) .appendQueryParameter(TaskContract.TaskLists.ACCOUNT_NAME, account.name)
.appendQueryParameter(TaskContract.CALLER_IS_SYNCADAPTER, "true") .appendQueryParameter(TaskContract.CALLER_IS_SYNCADAPTER, "true")
.build(); .build();

View File

@ -19,9 +19,7 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor(suppressConstructorProperties=true) @RequiredArgsConstructor(suppressConstructorProperties=true)
@Data @Data
public class ServerInfo implements Serializable { public class ServerInfo {
private static final long serialVersionUID = 6744847358282980437L;
enum Scheme { enum Scheme {
HTTP, HTTPS, MAILTO HTTP, HTTPS, MAILTO
} }
@ -34,8 +32,9 @@ public class ServerInfo implements Serializable {
private boolean calDAV = false, cardDAV = false; private boolean calDAV = false, cardDAV = false;
private List<ResourceInfo> private List<ResourceInfo>
addressBooks = new LinkedList<ResourceInfo>(), addressBooks = new LinkedList<>(),
calendars = new LinkedList<ResourceInfo>(); calendars = new LinkedList<>(),
todoLists = new LinkedList<>();
public boolean hasEnabledCalendars() { public boolean hasEnabledCalendars() {
@ -48,9 +47,7 @@ public class ServerInfo implements Serializable {
@RequiredArgsConstructor(suppressConstructorProperties=true) @RequiredArgsConstructor(suppressConstructorProperties=true)
@Data @Data
public static class ResourceInfo implements Serializable { public static class ResourceInfo {
private static final long serialVersionUID = -5516934508229552112L;
public enum Type { public enum Type {
ADDRESS_BOOK, ADDRESS_BOOK,
CALENDAR CALENDAR
@ -69,9 +66,25 @@ public class ServerInfo implements Serializable {
VCardVersion vCardVersion; VCardVersion vCardVersion;
String timezone; 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() { public String getTitle() {
if (title == null) { 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.data.ParserException;
import net.fortuna.ical4j.model.Component; import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.ComponentList; 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.PropertyList;
import net.fortuna.ical4j.model.ValidationException; import net.fortuna.ical4j.model.ValidationException;
import net.fortuna.ical4j.model.component.VToDo; 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.Created;
import net.fortuna.ical4j.model.property.Description; import net.fortuna.ical4j.model.property.Description;
import net.fortuna.ical4j.model.property.DtStart; 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.Location;
import net.fortuna.ical4j.model.property.PercentComplete; import net.fortuna.ical4j.model.property.PercentComplete;
import net.fortuna.ical4j.model.property.Priority; import net.fortuna.ical4j.model.property.Priority;
@ -41,13 +46,17 @@ import lombok.Setter;
public class Task extends Resource { public class Task extends Resource {
private final static String TAG = "davdroid.Task"; private final static String TAG = "davdroid.Task";
@Getter @Setter DateTime createdAt;
@Getter @Setter DateTime lastModified;
@Getter @Setter String summary, location, description, url; @Getter @Setter String summary, location, description, url;
@Getter @Setter int priority; @Getter @Setter int priority;
@Getter @Setter Clazz classification; @Getter @Setter Clazz classification;
@Getter @Setter Status status; @Getter @Setter Status status;
@Getter @Setter Created createdAt;
@Getter @Setter DtStart dtStart; @Getter @Setter DtStart dtStart;
@Getter @Setter Due due;
@Getter @Setter Duration duration;
@Getter @Setter Completed completedAt; @Getter @Setter Completed completedAt;
@Getter @Setter Integer percentComplete; @Getter @Setter Integer percentComplete;
@ -90,6 +99,11 @@ public class Task extends Resource {
if (todo.getUid() != null) if (todo.getUid() != null)
uid = todo.getUid().getValue(); uid = todo.getUid().getValue();
if (todo.getCreated() != null)
createdAt = todo.getCreated().getDateTime();
if (todo.getLastModified() != null)
lastModified = todo.getLastModified().getDateTime();
if (todo.getSummary() != null) if (todo.getSummary() != null)
summary = todo.getSummary().getValue(); summary = todo.getSummary().getValue();
if (todo.getLocation() != null) if (todo.getLocation() != null)
@ -105,8 +119,10 @@ public class Task extends Resource {
if (todo.getStatus() != null) if (todo.getStatus() != null)
status = todo.getStatus(); status = todo.getStatus();
if (todo.getCreated() != null) if (todo.getDue() != null)
createdAt = todo.getCreated(); due = todo.getDue();
if (todo.getDuration() != null)
duration = todo.getDuration();
if (todo.getStartDate() != null) if (todo.getStartDate() != null)
dtStart = todo.getStartDate(); dtStart = todo.getStartDate();
if (todo.getDateCompleted() != null) if (todo.getDateCompleted() != null)
@ -134,6 +150,11 @@ public class Task extends Resource {
if (uid != null) if (uid != null)
props.add(new Uid(uid)); props.add(new Uid(uid));
if (createdAt != null)
props.add(new Created(createdAt));
if (lastModified != null)
props.add(new LastModified(lastModified));
if (summary != null) if (summary != null)
props.add(new Summary(summary)); props.add(new Summary(summary));
if (location != null) if (location != null)
@ -153,8 +174,10 @@ public class Task extends Resource {
if (status != null) if (status != null)
props.add(status); props.add(status);
if (createdAt != null) if (due != null)
props.add(createdAt); props.add(due);
if (duration != null)
props.add(duration);
if (dtStart != null) if (dtStart != null)
props.add(dtStart); props.add(dtStart);
if (completedAt != null) if (completedAt != null)

View File

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

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.HashMap;
import java.util.Map; import java.util.Map;
import at.bitfire.davdroid.resource.CalDavCalendar;
import at.bitfire.davdroid.resource.CalDavTaskList; import at.bitfire.davdroid.resource.CalDavTaskList;
import at.bitfire.davdroid.resource.LocalCalendar;
import at.bitfire.davdroid.resource.LocalCollection; import at.bitfire.davdroid.resource.LocalCollection;
import at.bitfire.davdroid.resource.LocalTaskList; import at.bitfire.davdroid.resource.LocalTaskList;
import at.bitfire.davdroid.resource.RemoteCollection; import at.bitfire.davdroid.resource.RemoteCollection;

View File

@ -17,8 +17,13 @@ import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.preference.SwitchPreference; 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.R;
import at.bitfire.davdroid.resource.LocalTaskList;
import at.bitfire.davdroid.syncadapter.AccountSettings; import at.bitfire.davdroid.syncadapter.AccountSettings;
import ezvcard.VCardVersion; import ezvcard.VCardVersion;
import lombok.Setter; import lombok.Setter;
@ -78,7 +83,7 @@ public class AccountFragment extends PreferenceFragment {
// category: synchronization // category: synchronization
final ListPreference prefSyncContacts = (ListPreference)findPreference("sync_interval_contacts"); final ListPreference prefSyncContacts = (ListPreference)findPreference("sync_interval_contacts");
final Long syncIntervalContacts = settings.getContactsSyncInterval(); final Long syncIntervalContacts = settings.getSyncInterval(ContactsContract.AUTHORITY);
if (syncIntervalContacts != null) { if (syncIntervalContacts != null) {
prefSyncContacts.setValue(syncIntervalContacts.toString()); prefSyncContacts.setValue(syncIntervalContacts.toString());
if (syncIntervalContacts == AccountSettings.SYNC_INTERVAL_MANUALLY) if (syncIntervalContacts == AccountSettings.SYNC_INTERVAL_MANUALLY)
@ -88,7 +93,7 @@ public class AccountFragment extends PreferenceFragment {
prefSyncContacts.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { prefSyncContacts.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setContactsSyncInterval(Long.parseLong((String)newValue)); settings.setSyncInterval(ContactsContract.AUTHORITY, Long.parseLong((String) newValue));
readFromAccount(); readFromAccount();
return true; return true;
} }
@ -99,7 +104,7 @@ public class AccountFragment extends PreferenceFragment {
} }
final ListPreference prefSyncCalendars = (ListPreference)findPreference("sync_interval_calendars"); final ListPreference prefSyncCalendars = (ListPreference)findPreference("sync_interval_calendars");
final Long syncIntervalCalendars = settings.getCalendarsSyncInterval(); final Long syncIntervalCalendars = settings.getSyncInterval(CalendarContract.AUTHORITY);
if (syncIntervalCalendars != null) { if (syncIntervalCalendars != null) {
prefSyncCalendars.setValue(syncIntervalCalendars.toString()); prefSyncCalendars.setValue(syncIntervalCalendars.toString());
if (syncIntervalCalendars == AccountSettings.SYNC_INTERVAL_MANUALLY) if (syncIntervalCalendars == AccountSettings.SYNC_INTERVAL_MANUALLY)
@ -109,7 +114,7 @@ public class AccountFragment extends PreferenceFragment {
prefSyncCalendars.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { prefSyncCalendars.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setCalendarsSyncInterval(Long.parseLong((String)newValue)); settings.setSyncInterval(CalendarContract.AUTHORITY, Long.parseLong((String) newValue));
readFromAccount(); readFromAccount();
return true; return true;
} }
@ -119,6 +124,27 @@ public class AccountFragment extends PreferenceFragment {
prefSyncCalendars.setSummary(R.string.settings_sync_summary_not_available); 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 // category: address book
final CheckBoxPreference prefVCard4 = (CheckBoxPreference) findPreference("vcard4_support"); final CheckBoxPreference prefVCard4 = (CheckBoxPreference) findPreference("vcard4_support");
if (settings.getAddressBookURL() != null) { // does this account even have an address book? 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.provider.ContactsContract;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -26,22 +27,19 @@ import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.dmfs.provider.tasks.TaskContract; import java.util.List;
import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.LocalCalendar; import at.bitfire.davdroid.resource.LocalCalendar;
import at.bitfire.davdroid.resource.LocalNotebook;
import at.bitfire.davdroid.resource.LocalStorageException; import at.bitfire.davdroid.resource.LocalStorageException;
import at.bitfire.davdroid.resource.LocalTaskList; import at.bitfire.davdroid.resource.LocalTaskList;
import at.bitfire.davdroid.resource.ServerInfo; import at.bitfire.davdroid.resource.ServerInfo;
import at.bitfire.davdroid.resource.Task;
import at.bitfire.davdroid.syncadapter.AccountSettings; import at.bitfire.davdroid.syncadapter.AccountSettings;
import at.bitfire.notebooks.provider.NoteContract;
public class AccountDetailsFragment extends Fragment implements TextWatcher { 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; ServerInfo serverInfo;
EditText editAccountName; EditText editAccountName;
@ -51,7 +49,7 @@ public class AccountDetailsFragment extends Fragment implements TextWatcher {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.setup_account_details, container, false); 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 = (EditText)v.findViewById(R.id.account_name);
editAccountName.addTextChangedListener(this); editAccountName.addTextChangedListener(this);
@ -87,81 +85,59 @@ public class AccountDetailsFragment extends Fragment implements TextWatcher {
// actions // actions
void addAccount() { void addAccount() {
ServerInfo serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO);
String accountName = editAccountName.getText().toString(); String accountName = editAccountName.getText().toString();
AccountManager accountManager = AccountManager.get(getActivity()); AccountManager accountManager = AccountManager.get(getActivity());
Account account = new Account(accountName, Constants.ACCOUNT_TYPE); Account account = new Account(accountName, Constants.ACCOUNT_TYPE);
Bundle userData = AccountSettings.createBundle(serverInfo); 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)) { if (accountManager.addAccountExplicitly(account, serverInfo.getPassword(), userData)) {
// account created, now create calendars ... addSync(account, ContactsContract.AUTHORITY, serverInfo.getAddressBooks(), null);
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);
// ... and notes addSync(account, CalendarContract.AUTHORITY, serverInfo.getCalendars(), new AddSyncCallback() {
boolean syncNotes = false; @Override
for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars()) public void createLocalCollection(Account account, ServerInfo.ResourceInfo calendar) throws LocalStorageException {
if (calendar.isEnabled() && calendar.isSupportingNotes()) LocalCalendar.create(account, getActivity().getContentResolver(), calendar);
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 addSync(account, LocalTaskList.TASKS_AUTHORITY, serverInfo.getTodoLists(), new AddSyncCallback() {
boolean syncTasks = false; @Override
for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars()) public void createLocalCollection(Account account, ServerInfo.ResourceInfo todoList) throws LocalStorageException {
if (calendar.isEnabled() && calendar.isSupportingTasks()) LocalTaskList.create(account, getActivity().getContentResolver(), todoList);
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(); getActivity().finish();
} else } else
Toast.makeText(getActivity(), "Couldn't create account (account with this name already existing?)", Toast.LENGTH_LONG).show(); 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 // input validation
@Override @Override

View File

@ -14,12 +14,17 @@ import android.os.Bundle;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.ServerInfo;
public class AddAccountActivity extends Activity { public class AddAccountActivity extends Activity {
protected ServerInfo serverInfo;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -40,6 +45,11 @@ public class AddAccountActivity extends Activity {
return true; 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) { public void showHelp(MenuItem item) {
startActivityForResult(new Intent(Intent.ACTION_VIEW, Uri.parse(Constants.WEB_URL_HELP)), 0); 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; package at.bitfire.davdroid.ui.setup;
import android.app.DialogFragment; import android.app.DialogFragment;
import android.app.Fragment;
import android.app.LoaderManager.LoaderCallbacks; import android.app.LoaderManager.LoaderCallbacks;
import android.content.AsyncTaskLoader; import android.content.AsyncTaskLoader;
import android.content.Context; import android.content.Context;
@ -30,12 +31,13 @@ import java.security.cert.CertPathValidatorException;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.DavResourceFinder; import at.bitfire.davdroid.resource.DavResourceFinder;
import at.bitfire.davdroid.resource.LocalTaskList;
import at.bitfire.davdroid.resource.ServerInfo; import at.bitfire.davdroid.resource.ServerInfo;
import at.bitfire.davdroid.webdav.DavException; import at.bitfire.davdroid.webdav.DavException;
import lombok.Cleanup; import lombok.Cleanup;
public class QueryServerDialogFragment extends DialogFragment implements LoaderCallbacks<ServerInfo> { 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 public static final String
EXTRA_BASE_URI = "base_uri", EXTRA_BASE_URI = "base_uri",
EXTRA_USER_NAME = "user_name", EXTRA_USER_NAME = "user_name",
@ -71,13 +73,16 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
if (serverInfo.getErrorMessage() != null) if (serverInfo.getErrorMessage() != null)
Toast.makeText(getActivity(), serverInfo.getErrorMessage(), Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), serverInfo.getErrorMessage(), Toast.LENGTH_LONG).show();
else { else {
SelectCollectionsFragment selectCollections = new SelectCollectionsFragment(); ((AddAccountActivity)getActivity()).serverInfo = serverInfo;
Bundle arguments = new Bundle();
arguments.putSerializable(SelectCollectionsFragment.KEY_SERVER_INFO, serverInfo); Fragment nextFragment;
selectCollections.setArguments(arguments); if (!serverInfo.getTodoLists().isEmpty() && !LocalTaskList.isAvailable(getActivity()))
nextFragment = new InstallAppsFragment();
else
nextFragment = new SelectCollectionsFragment();
getFragmentManager().beginTransaction() getFragmentManager().beginTransaction()
.replace(R.id.right_pane, selectCollections) .replace(R.id.right_pane, nextFragment)
.addToBackStack(null) .addToBackStack(null)
.commitAllowingStateLoss(); .commitAllowingStateLoss();
} }

View File

@ -18,28 +18,50 @@ import android.widget.CheckedTextView;
import android.widget.ListAdapter; import android.widget.ListAdapter;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.LocalTaskList;
import at.bitfire.davdroid.resource.ServerInfo; import at.bitfire.davdroid.resource.ServerInfo;
import lombok.Getter; 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 { public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter {
final static int TYPE_ADDRESS_BOOKS_HEADING = 0, final static int
TYPE_ADDRESS_BOOKS_ROW = 1, TYPE_ADDRESS_BOOKS_HEADING = 0,
TYPE_CALENDARS_HEADING = 2, TYPE_ADDRESS_BOOKS_ROW = 1,
TYPE_CALENDARS_ROW = 3; TYPE_CALENDARS_HEADING = 2,
TYPE_CALENDARS_ROW = 3,
TYPE_TASK_LISTS_HEADING = 4,
TYPE_TASK_LISTS_ROW = 5;
protected Context context; protected Context context;
protected ServerInfo serverInfo; 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) { public SelectCollectionsAdapter(Context context, ServerInfo serverInfo) {
this.context = context; this.context = context;
this.serverInfo = serverInfo; this.serverInfo = serverInfo;
nAddressBooks = (serverInfo.getAddressBooks() == null) ? 0 : serverInfo.getAddressBooks().size(); nAddressBooks = serverInfo.getAddressBooks() == null ? 0 : serverInfo.getAddressBooks().size();
nAddressbookHeadings = (nAddressBooks == 0) ? 0 : 1; nAddressBookHeadings = nAddressBooks == 0 ? 0 : 1;
nCalendars = (serverInfo.getCalendars() == null) ? 0 : serverInfo.getCalendars().size(); nCalendars = serverInfo.getCalendars() == null ? 0 : serverInfo.getCalendars().size();
nCalendarHeadings = (nCalendars == 0) ? 0 : 1; 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 @Override
public int getCount() { public int getCount() {
return nAddressbookHeadings + nAddressBooks + nCalendarHeadings + nCalendars; return nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars + nTaskListHeadings + nTaskLists;
} }
@Override @Override
public Object getItem(int position) { public Object getItem(int position) {
if (position >= nAddressbookHeadings && if (position >= nAddressBookHeadings &&
position < (nAddressbookHeadings + nAddressBooks)) position < (nAddressBookHeadings + nAddressBooks))
return serverInfo.getAddressBooks().get(position - nAddressbookHeadings); return serverInfo.getAddressBooks().get(position - nAddressBookHeadings);
else if (position >= (nAddressbookHeadings + nAddressBooks + nCalendarHeadings) &&
(position < (nAddressbookHeadings + nAddressBooks + nCalendarHeadings + nCalendars))) else if (position >= (nAddressBookHeadings + nAddressBooks + nCalendarHeadings) &&
return serverInfo.getCalendars().get(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; return null;
} }
@ -76,19 +104,26 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter
@Override @Override
public int getViewTypeCount() { public int getViewTypeCount() {
return 4; return TYPE_TASK_LISTS_ROW + 1;
} }
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
if ((nAddressbookHeadings != 0) && (position == 0)) if ((nAddressBookHeadings != 0) && (position == 0))
return TYPE_ADDRESS_BOOKS_HEADING; 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; return TYPE_ADDRESS_BOOKS_ROW;
else if ((nCalendars != 0) && (position == nAddressbookHeadings + nAddressBooks))
else if ((nCalendarHeadings != 0) && (position == nAddressBookHeadings + nAddressBooks))
return TYPE_CALENDARS_HEADING; 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; 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 else
return IGNORE_ITEM_VIEW_TYPE; return IGNORE_ITEM_VIEW_TYPE;
} }
@ -97,11 +132,13 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView; View v = convertView;
int viewType = getItemViewType(position);
// step 1: get view (either by creating or recycling) // step 1: get view (either by creating or recycling)
if (v == null) { if (v == null) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext()); LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch (getItemViewType(position)) { switch (viewType) {
case TYPE_ADDRESS_BOOKS_HEADING: case TYPE_ADDRESS_BOOKS_HEADING:
v = inflater.inflate(R.layout.setup_address_books_heading, parent, false); v = inflater.inflate(R.layout.setup_address_books_heading, parent, false);
break; break;
@ -112,21 +149,38 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter
case TYPE_CALENDARS_HEADING: case TYPE_CALENDARS_HEADING:
v = inflater.inflate(R.layout.setup_calendars_heading, parent, false); v = inflater.inflate(R.layout.setup_calendars_heading, parent, false);
break; break;
case TYPE_TASK_LISTS_HEADING:
v = inflater.inflate(R.layout.setup_task_lists_heading, parent, false);
break;
case TYPE_CALENDARS_ROW: case TYPE_CALENDARS_ROW:
case TYPE_TASK_LISTS_ROW:
v = inflater.inflate(android.R.layout.simple_list_item_multiple_choice, null); v = inflater.inflate(android.R.layout.simple_list_item_multiple_choice, null);
v.setPadding(0, 8, 0, 8); v.setPadding(0, 8, 0, 8);
} }
} }
// step 2: fill view with content // step 2: fill view with content
switch (getItemViewType(position)) { switch (viewType) {
case TYPE_ADDRESS_BOOKS_ROW: case TYPE_ADDRESS_BOOKS_ROW:
setContent((CheckedTextView)v, R.drawable.addressbook, (ServerInfo.ResourceInfo)getItem(position)); setContent((CheckedTextView)v, R.drawable.addressbook, (ServerInfo.ResourceInfo)getItem(position));
break; break;
case TYPE_CALENDARS_ROW: 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; return v;
} }
@ -156,6 +210,6 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter
@Override @Override
public boolean isEnabled(int position) { public boolean isEnabled(int position) {
int type = getItemViewType(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; import at.bitfire.davdroid.resource.ServerInfo;
public class SelectCollectionsFragment extends ListFragment { public class SelectCollectionsFragment extends ListFragment {
public static final String KEY_SERVER_INFO = "server_info";
protected ServerInfo serverInfo;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
serverInfo = ((AddAccountActivity)getActivity()).serverInfo;
View v = super.onCreateView(inflater, container, savedInstanceState); View v = super.onCreateView(inflater, container, savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
return v; return v;
} }
@ -50,7 +54,6 @@ public class SelectCollectionsFragment extends ListFragment {
View header = getActivity().getLayoutInflater().inflate(R.layout.setup_select_collections_header, getListView(), false); View header = getActivity().getLayoutInflater().inflate(R.layout.setup_select_collections_header, getListView(), false);
listView.addHeaderView(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); final SelectCollectionsAdapter adapter = new SelectCollectionsAdapter(view.getContext(), serverInfo);
setListAdapter(adapter); setListAdapter(adapter);
@ -80,13 +83,13 @@ public class SelectCollectionsFragment extends ListFragment {
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.next: case R.id.next:
ServerInfo serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO);
// synchronize only selected collections // synchronize only selected collections
for (ServerInfo.ResourceInfo addressBook : serverInfo.getAddressBooks()) for (ServerInfo.ResourceInfo addressBook : serverInfo.getAddressBooks())
addressBook.setEnabled(false); addressBook.setEnabled(false);
for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars()) for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars())
calendar.setEnabled(false); calendar.setEnabled(false);
for (ServerInfo.ResourceInfo todoList : serverInfo.getTodoLists())
todoList.setEnabled(false);
ListAdapter adapter = getListView().getAdapter(); ListAdapter adapter = getListView().getAdapter();
for (long id : getListView().getCheckedItemIds()) { for (long id : getListView().getCheckedItemIds()) {
@ -95,14 +98,8 @@ public class SelectCollectionsFragment extends ListFragment {
info.setEnabled(true); 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() getFragmentManager().beginTransaction()
.replace(R.id.right_pane, accountDetails) .replace(R.id.right_pane, new AccountDetailsFragment())
.addToBackStack(null) .addToBackStack(null)
.commitAllowingStateLoss(); .commitAllowingStateLoss();
break; 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). ~ Copyright (c) 2013 2015 Ricki Hirner (bitfire web engineering).
~ All rights reserved. This program and the accompanying materials ~ All rights reserved. This program and the accompanying materials
@ -6,10 +7,13 @@
~ http://www.gnu.org/licenses/gpl.html ~ http://www.gnu.org/licenses/gpl.html
--> -->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android">
android:accountType="bitfire.at.davdroid"
android:contentAuthority="at.bitfire.notebooks.provider" <item
android:allowParallelSyncs="true" android:id="@+id/skip"
android:supportsUploading="true" android:icon="@drawable/navigation_skip"
android:isAlwaysSyncable="true" android:showAsAction="always|withText"
android:userVisible="true" /> android:title="@string/skip">
</item>
</menu>

View File

@ -11,6 +11,7 @@
<!-- common strings --> <!-- common strings -->
<string name="app_name">DAVdroid</string> <string name="app_name">DAVdroid</string>
<string name="next">Weiter</string> <string name="next">Weiter</string>
<string name="skip">Überspringen</string>
<string name="help">Hilfe</string> <string name="help">Hilfe</string>
<string name="exception_cert_path_validation">Nicht vertrauenswürdiges Zertifikat in der Zertifikatskette (siehe FAQ)</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_periodically">Alle %d Minuten + sofort bei lokalen Änderungen</string>
<string name="settings_sync_summary_not_available">Nicht verfügbar</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_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"> <string-array name="settings_sync_interval_names">
<item>Nur manuell</item> <item>Nur manuell</item>
<item>Alle 5 Minuten</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_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_add_account">Konto hinzufügen</string>
<string name="setup_querying_server">Daten werden vom Server abgefragt. Bitte warten…</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_what_to_sync">Welche Ordner sollen synchronisiert werden?</string>
<string name="setup_address_books">Adressbücher</string> <string name="setup_address_books">Adressbücher</string>
<string name="setup_calendars">Kalender</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_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_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_details">Konto-Details</string>
<string name="setup_account_name">Kontoname:</string> <string name="setup_account_name">Kontoname:</string>

View File

@ -12,6 +12,7 @@
<!-- common strings --> <!-- common strings -->
<string name="app_name">DAVdroid</string> <string name="app_name">DAVdroid</string>
<string name="next">Next</string> <string name="next">Next</string>
<string name="skip">Skip</string>
<string name="help">Help</string> <string name="help">Help</string>
<string name="exception_cert_path_validation">Untrusted certificate in certificate path. See FAQ for more info.</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_periodically">Every %d minutes + immediately on local changes</string>
<string name="settings_sync_summary_not_available">Not available</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_calendars">Calendars sync. interval</string>
<string name="settings_sync_interval_tasks">Tasks sync. interval</string>
<string-array name="settings_sync_interval_seconds"> <string-array name="settings_sync_interval_seconds">
<item>-1</item> <item>-1</item>
<item>300</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_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_add_account">Add account</string>
<string name="setup_querying_server">Querying server. Please wait…</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_what_to_sync">Which collections shall be synchronized?</string>
<string name="setup_address_books">Address books</string> <string name="setup_address_books">Address books</string>
<string name="setup_calendars">Calendars</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_task_lists">Task lists</string>
<string name="setup_select_calendars">Select your calendars:</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_details">Account details</string>
<string name="setup_account_name">Account name:</string> <string name="setup_account_name">Account name:</string>

View File

@ -50,6 +50,13 @@
android:entries="@array/settings_sync_interval_names" android:entries="@array/settings_sync_interval_names"
android:entryValues="@array/settings_sync_interval_seconds" /> 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>
<PreferenceCategory android:title="@string/settings_carddav"> <PreferenceCategory android:title="@string/settings_carddav">

View File

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