mirror of
https://github.com/etesync/android
synced 2024-12-26 08:28:17 +00:00
Fix "can't edit contact" / "account doesn't show up" and other bugs; refactoring
* support for phonetic names (closes #19) * update contacts.xml, tested with 4.0 (Samsung), 4.2 (Cyanogen), 4.3 (Cyanogen) (fixes #5, fixes #6, fixes #7) * smarter error handling (1): notify sync manager in case of HTTP auth errors * smarter error handling (2): just ignore the dubious resources instead of notifying Android sync service * refactoring: created DavSyncAdapter and move common code to it * version bump to 0.3.4-alpha
This commit is contained in:
parent
c87fb7bedd
commit
5db8bcb9d8
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="at.bitfire.davdroid"
|
||||
android:versionCode="6"
|
||||
android:versionName="0.3.3-alpha" >
|
||||
android:versionCode="7"
|
||||
android:versionName="0.3.4-alpha" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="14"
|
||||
@ -34,7 +34,8 @@
|
||||
</service>
|
||||
<service
|
||||
android:name=".syncadapter.ContactsSyncAdapterService"
|
||||
android:exported="true" >
|
||||
android:exported="true"
|
||||
android:process=":sync" >
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
@ -48,7 +49,8 @@
|
||||
</service>
|
||||
<service
|
||||
android:name=".syncadapter.CalendarsSyncAdapterService"
|
||||
android:exported="true" >
|
||||
android:exported="true"
|
||||
android:process=":sync" >
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
|
@ -30,6 +30,6 @@
|
||||
<string name="calendars">Calendars</string>
|
||||
<string name="select_address_book">Select up to one address book (tap again to unselect):</string>
|
||||
<string name="select_calendars">Select your calendars:</string>
|
||||
<string name="auth_preemptive">Preemptive authentification (recommended, but incompatible with Digest auth)</string>
|
||||
<string name="auth_preemptive">Preemptive authentication (recommended, but incompatible with Digest auth)</string>
|
||||
|
||||
</resources>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<Preference android:title="DAVdroid Web site" >
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:data="http://davdroid.bitfire.at" />
|
||||
android:data="http://davdroid.bitfire.at/?pk_campaign=in-app" />
|
||||
</Preference>
|
||||
|
||||
</PreferenceScreen>
|
@ -1,54 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ContactsSource xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<ContactsAccountType
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<EditSchema>
|
||||
<DataKind
|
||||
kind="name"
|
||||
maxOccurs="1"
|
||||
supportsDisplayName="true"
|
||||
supportsMiddleName="true"
|
||||
supportsFamilyName="true"
|
||||
supportsPhoneticFamilyName="false"
|
||||
supportsPhoneticGivenName="false"
|
||||
supportsPhoneticMiddleName="false"
|
||||
supportsPrefix="true"
|
||||
supportsSuffix="true" />
|
||||
|
||||
<DataKind
|
||||
kind="photo"
|
||||
maxOccurs="1" />
|
||||
|
||||
<DataKind kind="phone">
|
||||
<Type type="fax_home" />
|
||||
<Type type="fax_work" />
|
||||
<Type type="home" />
|
||||
<Type type="mobile" />
|
||||
<Type type="other_fax" />
|
||||
<Type type="pager" />
|
||||
<Type type="work" />
|
||||
<Type type="work_mobile" />
|
||||
<Type type="work_pager" />
|
||||
</DataKind>
|
||||
|
||||
<DataKind kind="email">
|
||||
<Type type="home" />
|
||||
<Type type="work" />
|
||||
<Type type="other" />
|
||||
</DataKind>
|
||||
|
||||
<DataKind
|
||||
kind="nickname"
|
||||
maxOccurs="1" />
|
||||
|
||||
<DataKind kind="website" />
|
||||
<DataKind kind="note" />
|
||||
|
||||
<DataKind
|
||||
dateWithTime="false"
|
||||
kind="event">
|
||||
<Type
|
||||
maxOccurs="1"
|
||||
type="birthday"
|
||||
yearOptional="false" />
|
||||
</DataKind>
|
||||
</EditSchema>
|
||||
</ContactsSource>
|
||||
<DataKind
|
||||
kind="name"
|
||||
maxOccurs="1"
|
||||
supportsDisplayName="true"
|
||||
supportsMiddleName="true"
|
||||
supportsFamilyName="true"
|
||||
supportsPhoneticFamilyName="true"
|
||||
supportsPhoneticGivenName="true"
|
||||
supportsPhoneticMiddleName="true"
|
||||
supportsPrefix="true"
|
||||
supportsSuffix="true" />
|
||||
|
||||
<DataKind
|
||||
kind="photo"
|
||||
maxOccurs="1" />
|
||||
|
||||
<DataKind kind="phone">
|
||||
<Type type="fax_home" />
|
||||
<Type type="fax_work" />
|
||||
<Type type="home" />
|
||||
<Type type="mobile" />
|
||||
<Type type="other_fax" />
|
||||
<Type type="pager" />
|
||||
<Type type="work" />
|
||||
<Type type="work_mobile" />
|
||||
<Type type="work_pager" />
|
||||
</DataKind>
|
||||
|
||||
<DataKind kind="email">
|
||||
<Type type="home" />
|
||||
<Type type="work" />
|
||||
<Type type="other" />
|
||||
</DataKind>
|
||||
|
||||
<DataKind
|
||||
kind="nickname"
|
||||
maxOccurs="1" />
|
||||
|
||||
<DataKind kind="website" />
|
||||
<DataKind
|
||||
kind="note"
|
||||
maxOccurs="1" />
|
||||
|
||||
<DataKind
|
||||
dateWithTime="false"
|
||||
kind="event">
|
||||
<Type
|
||||
maxOccurs="1"
|
||||
type="birthday"
|
||||
yearOptional="false" />
|
||||
</DataKind>
|
||||
</EditSchema>
|
||||
</ContactsAccountType>
|
||||
|
@ -9,7 +9,7 @@ package at.bitfire.davdroid;
|
||||
|
||||
public class Constants {
|
||||
public static final String
|
||||
APP_VERSION = "0.3.3-alpha",
|
||||
APP_VERSION = "0.3.4-alpha",
|
||||
|
||||
ACCOUNT_TYPE = "bitfire.at.davdroid",
|
||||
|
||||
|
46
src/at/bitfire/davdroid/ical4j/PhoneticFirstName.java
Normal file
46
src/at/bitfire/davdroid/ical4j/PhoneticFirstName.java
Normal file
@ -0,0 +1,46 @@
|
||||
package at.bitfire.davdroid.ical4j;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.fortuna.ical4j.model.ValidationException;
|
||||
import net.fortuna.ical4j.vcard.Group;
|
||||
import net.fortuna.ical4j.vcard.Parameter;
|
||||
import net.fortuna.ical4j.vcard.Property;
|
||||
import net.fortuna.ical4j.vcard.PropertyFactory;
|
||||
|
||||
public class PhoneticFirstName extends Property {
|
||||
private static final long serialVersionUID = 8096989375023262021L;
|
||||
|
||||
public static final String PROPERTY_NAME = "PHONETIC-FIRST-NAME";
|
||||
|
||||
protected String phoneticFirstName;
|
||||
|
||||
|
||||
public PhoneticFirstName(String value) {
|
||||
super(PROPERTY_NAME);
|
||||
|
||||
phoneticFirstName = value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return phoneticFirstName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws ValidationException {
|
||||
}
|
||||
|
||||
public static class Factory implements PropertyFactory<Property> {
|
||||
@Override
|
||||
public PhoneticFirstName createProperty(List<Parameter> params, String value) {
|
||||
return new PhoneticFirstName(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PhoneticFirstName createProperty(Group group, List<Parameter> params, String value) {
|
||||
return new PhoneticFirstName(value);
|
||||
}
|
||||
}
|
||||
}
|
46
src/at/bitfire/davdroid/ical4j/PhoneticLastName.java
Normal file
46
src/at/bitfire/davdroid/ical4j/PhoneticLastName.java
Normal file
@ -0,0 +1,46 @@
|
||||
package at.bitfire.davdroid.ical4j;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.fortuna.ical4j.model.ValidationException;
|
||||
import net.fortuna.ical4j.vcard.Group;
|
||||
import net.fortuna.ical4j.vcard.Parameter;
|
||||
import net.fortuna.ical4j.vcard.Property;
|
||||
import net.fortuna.ical4j.vcard.PropertyFactory;
|
||||
|
||||
public class PhoneticLastName extends Property {
|
||||
private static final long serialVersionUID = 8637699713562385556L;
|
||||
|
||||
public static final String PROPERTY_NAME = "PHONETIC-LAST-NAME";
|
||||
|
||||
protected String phoneticFirstName;
|
||||
|
||||
|
||||
public PhoneticLastName(String value) {
|
||||
super(PROPERTY_NAME);
|
||||
|
||||
phoneticFirstName = value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return phoneticFirstName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws ValidationException {
|
||||
}
|
||||
|
||||
public static class Factory implements PropertyFactory<Property> {
|
||||
@Override
|
||||
public PhoneticLastName createProperty(List<Parameter> params, String value) {
|
||||
return new PhoneticLastName(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PhoneticLastName createProperty(Group group, List<Parameter> params, String value) {
|
||||
return new PhoneticLastName(value);
|
||||
}
|
||||
}
|
||||
}
|
46
src/at/bitfire/davdroid/ical4j/PhoneticMiddleName.java
Normal file
46
src/at/bitfire/davdroid/ical4j/PhoneticMiddleName.java
Normal file
@ -0,0 +1,46 @@
|
||||
package at.bitfire.davdroid.ical4j;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.fortuna.ical4j.model.ValidationException;
|
||||
import net.fortuna.ical4j.vcard.Group;
|
||||
import net.fortuna.ical4j.vcard.Parameter;
|
||||
import net.fortuna.ical4j.vcard.Property;
|
||||
import net.fortuna.ical4j.vcard.PropertyFactory;
|
||||
|
||||
public class PhoneticMiddleName extends Property {
|
||||
private static final long serialVersionUID = 1310410178765057503L;
|
||||
|
||||
public static final String PROPERTY_NAME = "PHONETIC-MIDDLE-NAME";
|
||||
|
||||
protected String phoneticFirstName;
|
||||
|
||||
|
||||
public PhoneticMiddleName(String value) {
|
||||
super(PROPERTY_NAME);
|
||||
|
||||
phoneticFirstName = value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return phoneticFirstName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws ValidationException {
|
||||
}
|
||||
|
||||
public static class Factory implements PropertyFactory<Property> {
|
||||
@Override
|
||||
public PhoneticMiddleName createProperty(List<Parameter> params, String value) {
|
||||
return new PhoneticMiddleName(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PhoneticMiddleName createProperty(Group group, List<Parameter> params, String value) {
|
||||
return new PhoneticMiddleName(value);
|
||||
}
|
||||
}
|
||||
}
|
@ -31,7 +31,7 @@ public class CalDavCalendar extends RemoteCollection<Event> {
|
||||
}
|
||||
|
||||
|
||||
public CalDavCalendar(String baseURL, String user, String password, boolean preemptiveAuth) throws IOException, URISyntaxException {
|
||||
public CalDavCalendar(String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
|
||||
super(baseURL, user, password, preemptiveAuth);
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ public class CardDavAddressBook extends RemoteCollection<Contact> {
|
||||
}
|
||||
|
||||
|
||||
public CardDavAddressBook(String baseURL, String user, String password, boolean preemptiveAuth) throws IOException, URISyntaxException {
|
||||
public CardDavAddressBook(String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
|
||||
super(baseURL, user, password, preemptiveAuth);
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,9 @@ import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import at.bitfire.davdroid.ical4j.PhoneticFirstName;
|
||||
import at.bitfire.davdroid.ical4j.PhoneticLastName;
|
||||
import at.bitfire.davdroid.ical4j.PhoneticMiddleName;
|
||||
import at.bitfire.davdroid.ical4j.Starred;
|
||||
|
||||
@ToString(callSuper = true)
|
||||
@ -61,6 +64,7 @@ public class Contact extends Resource {
|
||||
|
||||
@Getter @Setter private String displayName;
|
||||
@Getter @Setter private String prefix, givenName, middleName, familyName, suffix;
|
||||
@Getter @Setter private String phoneticGivenName, phoneticMiddleName, phoneticFamilyName;
|
||||
@Getter @Setter private String[] nickNames;
|
||||
|
||||
@Getter @Setter private byte[] photo;
|
||||
@ -107,7 +111,14 @@ public class Contact extends Resource {
|
||||
@Override
|
||||
public void parseEntity(InputStream is) throws IOException, ParserException {
|
||||
PropertyFactoryRegistry propertyFactoryRegistry = new PropertyFactoryRegistry();
|
||||
|
||||
// add support for X-DAVDROID-STARRED
|
||||
propertyFactoryRegistry.register("X-" + Starred.PROPERTY_NAME, new Starred.Factory());
|
||||
|
||||
// add support for phonetic names
|
||||
propertyFactoryRegistry.register("X-" + PhoneticFirstName.PROPERTY_NAME, new PhoneticFirstName.Factory());
|
||||
propertyFactoryRegistry.register("X-" + PhoneticMiddleName.PROPERTY_NAME, new PhoneticMiddleName.Factory());
|
||||
propertyFactoryRegistry.register("X-" + PhoneticLastName.PROPERTY_NAME, new PhoneticLastName.Factory());
|
||||
|
||||
VCardBuilder builder = new VCardBuilder(
|
||||
new InputStreamReader(is),
|
||||
@ -115,9 +126,15 @@ public class Contact extends Resource {
|
||||
propertyFactoryRegistry,
|
||||
new ParameterFactoryRegistry()
|
||||
);
|
||||
VCard vcard = builder.build();
|
||||
if (vcard == null)
|
||||
return;
|
||||
|
||||
VCard vcard;
|
||||
try {
|
||||
vcard = builder.build();
|
||||
if (vcard == null)
|
||||
return;
|
||||
} catch(Exception ex) {
|
||||
throw new ParserException("VCard parser crashed", -1);
|
||||
}
|
||||
|
||||
Uid uid = (Uid)vcard.getProperty(Id.UID);
|
||||
if (uid != null)
|
||||
@ -137,6 +154,7 @@ public class Contact extends Resource {
|
||||
if (nickname != null)
|
||||
nickNames = nickname.getNames();
|
||||
|
||||
// structured name
|
||||
N n = (N)vcard.getProperty(Id.N);
|
||||
if (n != null) {
|
||||
prefix = StringUtils.join(n.getPrefixes(), " ");
|
||||
@ -146,6 +164,17 @@ public class Contact extends Resource {
|
||||
suffix = StringUtils.join(n.getSuffixes(), " ");
|
||||
}
|
||||
|
||||
// phonetic name
|
||||
PhoneticFirstName phoneticFirstName = (PhoneticFirstName)vcard.getExtendedProperty(PhoneticFirstName.PROPERTY_NAME);
|
||||
if (phoneticFirstName != null)
|
||||
phoneticGivenName = phoneticFirstName.getValue();
|
||||
PhoneticMiddleName phoneticMiddleName = (PhoneticMiddleName)vcard.getExtendedProperty(PhoneticMiddleName.PROPERTY_NAME);
|
||||
if (phoneticMiddleName != null)
|
||||
this.phoneticMiddleName = phoneticMiddleName.getValue();
|
||||
PhoneticLastName phoneticLastName = (PhoneticLastName)vcard.getExtendedProperty(PhoneticLastName.PROPERTY_NAME);
|
||||
if (phoneticLastName != null)
|
||||
phoneticFamilyName = phoneticLastName.getValue();
|
||||
|
||||
for (Property p : vcard.getProperties(Id.EMAIL))
|
||||
emails.add((Email)p);
|
||||
|
||||
@ -228,6 +257,13 @@ public class Contact extends Resource {
|
||||
properties.add(new N(familyName, givenName, StringUtils.split(middleName),
|
||||
StringUtils.split(prefix), StringUtils.split(suffix)));
|
||||
|
||||
if (phoneticGivenName != null)
|
||||
properties.add(new PhoneticFirstName(phoneticGivenName));
|
||||
if (phoneticMiddleName != null)
|
||||
properties.add(new PhoneticMiddleName(phoneticMiddleName));
|
||||
if (phoneticFamilyName != null)
|
||||
properties.add(new PhoneticLastName(phoneticFamilyName));
|
||||
|
||||
for (Email email : emails)
|
||||
properties.add(email);
|
||||
|
||||
|
@ -125,8 +125,9 @@ public class LocalAddressBook extends LocalCollection<Contact> {
|
||||
|
||||
// structured name
|
||||
cursor = providerClient.query(dataURI(), new String[] {
|
||||
StructuredName.DISPLAY_NAME, StructuredName.PREFIX, StructuredName.GIVEN_NAME,
|
||||
StructuredName.MIDDLE_NAME, StructuredName.FAMILY_NAME, StructuredName.SUFFIX
|
||||
/* 0 */ StructuredName.DISPLAY_NAME, StructuredName.PREFIX, StructuredName.GIVEN_NAME,
|
||||
/* 3 */ StructuredName.MIDDLE_NAME, StructuredName.FAMILY_NAME, StructuredName.SUFFIX,
|
||||
/* 6 */ StructuredName.PHONETIC_GIVEN_NAME, StructuredName.PHONETIC_MIDDLE_NAME, StructuredName.PHONETIC_FAMILY_NAME
|
||||
}, StructuredName.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?",
|
||||
new String[] { String.valueOf(res.getLocalID()), StructuredName.CONTENT_ITEM_TYPE }, null);
|
||||
if (cursor.moveToNext()) {
|
||||
@ -137,6 +138,10 @@ public class LocalAddressBook extends LocalCollection<Contact> {
|
||||
c.setMiddleName(cursor.getString(3));
|
||||
c.setFamilyName(cursor.getString(4));
|
||||
c.setSuffix(cursor.getString(5));
|
||||
|
||||
c.setPhoneticGivenName(cursor.getString(6));
|
||||
c.setPhoneticMiddleName(cursor.getString(7));
|
||||
c.setPhoneticFamilyName(cursor.getString(8));
|
||||
}
|
||||
|
||||
// nick names
|
||||
@ -347,7 +352,10 @@ public class LocalAddressBook extends LocalCollection<Contact> {
|
||||
.withValue(StructuredName.GIVEN_NAME, contact.getGivenName())
|
||||
.withValue(StructuredName.MIDDLE_NAME, contact.getMiddleName())
|
||||
.withValue(StructuredName.FAMILY_NAME, contact.getFamilyName())
|
||||
.withValue(StructuredName.SUFFIX, contact.getSuffix());
|
||||
.withValue(StructuredName.SUFFIX, contact.getSuffix())
|
||||
.withValue(StructuredName.PHONETIC_GIVEN_NAME, contact.getPhoneticGivenName())
|
||||
.withValue(StructuredName.PHONETIC_MIDDLE_NAME, contact.getPhoneticMiddleName())
|
||||
.withValue(StructuredName.PHONETIC_FAMILY_NAME, contact.getPhoneticFamilyName());
|
||||
}
|
||||
|
||||
protected Builder buildNickName(Builder builder, String nickName) {
|
||||
|
@ -11,7 +11,6 @@ import java.net.URISyntaxException;
|
||||
import java.text.ParseException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.fortuna.ical4j.model.Parameter;
|
||||
import net.fortuna.ical4j.model.ParameterList;
|
||||
@ -26,11 +25,10 @@ import net.fortuna.ical4j.model.property.Organizer;
|
||||
import net.fortuna.ical4j.model.property.RDate;
|
||||
import net.fortuna.ical4j.model.property.RRule;
|
||||
import net.fortuna.ical4j.model.property.Status;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentProviderOperation.Builder;
|
||||
@ -40,6 +38,7 @@ import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.CalendarContract.Attendees;
|
||||
@ -75,9 +74,9 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
protected String entryColumnDirty() { return Events.DIRTY; }
|
||||
protected String entryColumnDeleted() { return Events.DELETED; }
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
|
||||
protected String entryColumnUID() {
|
||||
return (android.os.Build.VERSION.SDK_INT >= 17) ?
|
||||
return (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) ?
|
||||
Events.UID_2445 : Events.SYNC_DATA2;
|
||||
}
|
||||
|
||||
|
@ -19,20 +19,24 @@ import net.fortuna.ical4j.model.ValidationException;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
|
||||
import android.util.Log;
|
||||
import at.bitfire.davdroid.webdav.HttpPropfind;
|
||||
import at.bitfire.davdroid.webdav.InvalidDavResponseException;
|
||||
import at.bitfire.davdroid.webdav.WebDavCollection;
|
||||
import at.bitfire.davdroid.webdav.WebDavCollection.MultigetType;
|
||||
import at.bitfire.davdroid.webdav.WebDavResource;
|
||||
import at.bitfire.davdroid.webdav.WebDavResource.PutMode;
|
||||
|
||||
public abstract class RemoteCollection<ResourceType extends Resource> {
|
||||
private static final String TAG = "davdroid.RemoteCollection";
|
||||
|
||||
@Getter WebDavCollection collection;
|
||||
|
||||
abstract protected String memberContentType();
|
||||
abstract protected MultigetType multiGetType();
|
||||
abstract protected ResourceType newResourceSkeleton(String name, String ETag);
|
||||
|
||||
public RemoteCollection(String baseURL, String user, String password, boolean preemptiveAuth) throws IOException, URISyntaxException {
|
||||
public RemoteCollection(String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
|
||||
collection = new WebDavCollection(new URI(baseURL), user, password, preemptiveAuth);
|
||||
}
|
||||
|
||||
@ -43,13 +47,13 @@ public abstract class RemoteCollection<ResourceType extends Resource> {
|
||||
try {
|
||||
if (collection.getCTag() == null && collection.getMembers() == null) // not already fetched
|
||||
collection.propfind(HttpPropfind.Mode.COLLECTION_CTAG);
|
||||
} catch (IncapableResourceException e) {
|
||||
} catch (InvalidDavResponseException e) {
|
||||
return null;
|
||||
}
|
||||
return collection.getCTag();
|
||||
}
|
||||
|
||||
public Resource[] getMemberETags() throws IOException, IncapableResourceException, HttpException {
|
||||
public Resource[] getMemberETags() throws IOException, InvalidDavResponseException, HttpException {
|
||||
collection.propfind(HttpPropfind.Mode.MEMBERS_ETAG);
|
||||
|
||||
List<ResourceType> resources = new LinkedList<ResourceType>();
|
||||
@ -59,7 +63,7 @@ public abstract class RemoteCollection<ResourceType extends Resource> {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Resource[] multiGet(ResourceType[] resources) throws IOException, IncapableResourceException, HttpException, ParserException {
|
||||
public Resource[] multiGet(ResourceType[] resources) throws IOException, InvalidDavResponseException, HttpException {
|
||||
try {
|
||||
if (resources.length == 1) {
|
||||
Resource resource = get(resources[0]);
|
||||
@ -75,19 +79,25 @@ public abstract class RemoteCollection<ResourceType extends Resource> {
|
||||
LinkedList<ResourceType> foundResources = new LinkedList<ResourceType>();
|
||||
for (WebDavResource member : collection.getMembers()) {
|
||||
ResourceType resource = newResourceSkeleton(member.getName(), member.getETag());
|
||||
resource.parseEntity(member.getContent());
|
||||
foundResources.add(resource);
|
||||
try {
|
||||
resource.parseEntity(member.getContent());
|
||||
foundResources.add(resource);
|
||||
} catch (ParserException ex) {
|
||||
Log.e(TAG, "Ignoring unparseable entity in multi-response: " + ex.toString(), ex);
|
||||
}
|
||||
}
|
||||
return foundResources.toArray(new Resource[0]);
|
||||
} catch(ValidationException ex) {
|
||||
return null;
|
||||
} catch (ParserException ex) {
|
||||
Log.w(TAG, "Couldn't parse single multi-get entity", ex);
|
||||
}
|
||||
|
||||
return new Resource[0];
|
||||
}
|
||||
|
||||
|
||||
/* internal member operations */
|
||||
|
||||
public ResourceType get(ResourceType resource) throws IOException, HttpException, ParserException, ValidationException {
|
||||
public ResourceType get(ResourceType resource) throws IOException, HttpException, ParserException {
|
||||
WebDavResource member = new WebDavResource(collection, resource.getName());
|
||||
member.get();
|
||||
resource.parseEntity(member.getContent());
|
||||
|
@ -10,11 +10,14 @@ package at.bitfire.davdroid.syncadapter;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.Synchronized;
|
||||
import net.fortuna.ical4j.data.ParserException;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.auth.AuthenticationException;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
@ -34,6 +37,7 @@ import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.resource.CalDavCalendar;
|
||||
import at.bitfire.davdroid.resource.IncapableResourceException;
|
||||
import at.bitfire.davdroid.resource.LocalCalendar;
|
||||
import at.bitfire.davdroid.resource.LocalCollection;
|
||||
import at.bitfire.davdroid.resource.RemoteCollection;
|
||||
import at.bitfire.davdroid.webdav.WebDavResource;
|
||||
|
||||
@ -51,58 +55,35 @@ public class CalendarsSyncAdapterService extends Service {
|
||||
return syncAdapter.getSyncAdapterBinder();
|
||||
}
|
||||
|
||||
private static class SyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
private static class SyncAdapter extends DavSyncAdapter {
|
||||
private final static String TAG = "davdroid.CalendarsSyncAdapter";
|
||||
private AccountManager accountManager;
|
||||
|
||||
public SyncAdapter(Context context) {
|
||||
super(context, true);
|
||||
accountManager = AccountManager.get(context);
|
||||
super(context);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
|
||||
SyncResult syncResult) {
|
||||
Log.i(TAG, "Performing sync for authority " + authority);
|
||||
|
||||
// set class loader for iCal4j ResourceLoader
|
||||
Thread.currentThread().setContextClassLoader(getContext().getClassLoader());
|
||||
|
||||
protected Map<LocalCollection, RemoteCollection> getSyncPairs(Account account, ContentProviderClient provider) {
|
||||
try {
|
||||
SyncManager syncManager = new SyncManager(account, accountManager);
|
||||
|
||||
LocalCalendar[] calendars = LocalCalendar.findAll(account, provider);
|
||||
for (LocalCalendar calendar : calendars) {
|
||||
Map<LocalCollection, RemoteCollection> map = new HashMap<LocalCollection, RemoteCollection>();
|
||||
|
||||
for (LocalCalendar calendar : LocalCalendar.findAll(account, provider)) {
|
||||
URI uri = new URI(accountManager.getUserData(account, Constants.ACCOUNT_KEY_BASE_URL)).resolve(calendar.getPath());
|
||||
RemoteCollection dav = new CalDavCalendar(uri.toString(),
|
||||
accountManager.getUserData(account, Constants.ACCOUNT_KEY_USERNAME),
|
||||
accountManager.getPassword(account),
|
||||
Boolean.parseBoolean(accountManager.getUserData(account, Constants.ACCOUNT_KEY_AUTH_PREEMPTIVE)));
|
||||
syncManager.synchronize(calendar, dav, extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL), syncResult);
|
||||
|
||||
map.put(calendar, dav);
|
||||
}
|
||||
|
||||
} catch (HttpException e) {
|
||||
syncResult.stats.numParseExceptions++;
|
||||
Log.e(TAG, e.toString());
|
||||
} catch (ParserException e) {
|
||||
syncResult.stats.numParseExceptions++;
|
||||
Log.e(TAG, e.toString());
|
||||
} catch (RemoteException e) {
|
||||
syncResult.stats.numParseExceptions++;
|
||||
Log.e(TAG, e.getLocalizedMessage());
|
||||
} catch (OperationApplicationException e) {
|
||||
syncResult.stats.numParseExceptions++;
|
||||
Log.e(TAG, e.getLocalizedMessage());
|
||||
} catch (IOException e) {
|
||||
syncResult.stats.numIoExceptions++;
|
||||
Log.e(TAG, e.toString());
|
||||
} catch (IncapableResourceException e) {
|
||||
syncResult.stats.numParseExceptions++;
|
||||
Log.e(TAG, e.toString());
|
||||
} catch (URISyntaxException e) {
|
||||
syncResult.stats.numParseExceptions++;
|
||||
Log.e(TAG, e.toString());
|
||||
return map;
|
||||
} catch (RemoteException ex) {
|
||||
Log.e(TAG, "Couldn't find local calendars", ex);
|
||||
} catch (URISyntaxException ex) {
|
||||
Log.e(TAG, "Couldn't build calendar URI", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,11 @@ package at.bitfire.davdroid.syncadapter;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.Synchronized;
|
||||
import net.fortuna.ical4j.data.ParserException;
|
||||
|
||||
@ -38,12 +42,12 @@ import at.bitfire.davdroid.resource.LocalCollection;
|
||||
import at.bitfire.davdroid.resource.RemoteCollection;
|
||||
|
||||
public class ContactsSyncAdapterService extends Service {
|
||||
private static SyncAdapter syncAdapter;
|
||||
private static ContactsSyncAdapter syncAdapter;
|
||||
|
||||
@Override @Synchronized
|
||||
public void onCreate() {
|
||||
if (syncAdapter == null)
|
||||
syncAdapter = new SyncAdapter(getApplicationContext());
|
||||
syncAdapter = new ContactsSyncAdapter(getApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -51,59 +55,38 @@ public class ContactsSyncAdapterService extends Service {
|
||||
return syncAdapter.getSyncAdapterBinder();
|
||||
}
|
||||
|
||||
private static class SyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
private static class ContactsSyncAdapter extends DavSyncAdapter {
|
||||
private final static String TAG = "davdroid.ContactsSyncAdapter";
|
||||
private AccountManager accountManager;
|
||||
|
||||
public SyncAdapter(Context context) {
|
||||
super(context, true);
|
||||
accountManager = AccountManager.get(context);
|
||||
public ContactsSyncAdapter(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
|
||||
Log.i(TAG, "Performing sync for authority " + authority);
|
||||
|
||||
protected Map<LocalCollection, RemoteCollection> getSyncPairs(Account account, ContentProviderClient provider) {
|
||||
String addressBookPath = accountManager.getUserData(account, Constants.ACCOUNT_KEY_ADDRESSBOOK_PATH);
|
||||
if (addressBookPath == null)
|
||||
return;
|
||||
return null;
|
||||
|
||||
try {
|
||||
URI uri = new URI(accountManager.getUserData(account, Constants.ACCOUNT_KEY_BASE_URL)).resolve(addressBookPath);
|
||||
LocalCollection database = new LocalAddressBook(account, provider, accountManager);
|
||||
|
||||
URI uri = new URI(accountManager.getUserData(account, Constants.ACCOUNT_KEY_BASE_URL)).resolve(addressBookPath);
|
||||
RemoteCollection dav = new CardDavAddressBook(
|
||||
uri.toString(),
|
||||
accountManager.getUserData(account, Constants.ACCOUNT_KEY_USERNAME),
|
||||
accountManager.getPassword(account),
|
||||
Boolean.parseBoolean(accountManager.getUserData(account, Constants.ACCOUNT_KEY_AUTH_PREEMPTIVE)));
|
||||
|
||||
LocalCollection database = new LocalAddressBook(account, provider, accountManager);
|
||||
Map<LocalCollection, RemoteCollection> map = new HashMap<LocalCollection, RemoteCollection>();
|
||||
map.put(database, dav);
|
||||
|
||||
SyncManager syncManager = new SyncManager(account, accountManager);
|
||||
syncManager.synchronize(database, dav, extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL), syncResult);
|
||||
|
||||
} catch (IOException e) {
|
||||
syncResult.stats.numIoExceptions++;
|
||||
Log.e(TAG, e.toString());
|
||||
} catch (ParserException e) {
|
||||
syncResult.stats.numParseExceptions++;
|
||||
Log.e(TAG, e.toString());
|
||||
} catch (HttpException e) {
|
||||
syncResult.stats.numParseExceptions++;
|
||||
Log.e(TAG, e.toString());
|
||||
} catch (IncapableResourceException e) {
|
||||
syncResult.stats.numParseExceptions++;
|
||||
Log.e(TAG, e.toString());
|
||||
} catch(RemoteException e) {
|
||||
syncResult.databaseError = true;
|
||||
Log.e(TAG, e.toString());
|
||||
} catch(OperationApplicationException e) {
|
||||
syncResult.databaseError = true;
|
||||
Log.e(TAG, e.toString());
|
||||
} catch (URISyntaxException e) {
|
||||
syncResult.stats.numIoExceptions++;
|
||||
Log.e(TAG, e.toString());
|
||||
return map;
|
||||
} catch (URISyntaxException ex) {
|
||||
Log.e(TAG, "Couldn't build address book URI", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
78
src/at/bitfire/davdroid/syncadapter/DavSyncAdapter.java
Normal file
78
src/at/bitfire/davdroid/syncadapter/DavSyncAdapter.java
Normal file
@ -0,0 +1,78 @@
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.auth.AuthenticationException;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.OperationApplicationException;
|
||||
import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import at.bitfire.davdroid.resource.IncapableResourceException;
|
||||
import at.bitfire.davdroid.resource.LocalCollection;
|
||||
import at.bitfire.davdroid.resource.RemoteCollection;
|
||||
import at.bitfire.davdroid.webdav.InvalidDavResponseException;
|
||||
|
||||
public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
private final static String TAG = "davdroid.DavSyncAdapter";
|
||||
|
||||
protected AccountManager accountManager;
|
||||
|
||||
|
||||
public DavSyncAdapter(Context context) {
|
||||
super(context, true);
|
||||
accountManager = AccountManager.get(context);
|
||||
}
|
||||
|
||||
|
||||
protected abstract Map<LocalCollection, RemoteCollection> getSyncPairs(Account account, ContentProviderClient provider);
|
||||
|
||||
|
||||
@Override
|
||||
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
|
||||
Log.i(TAG, "Performing sync for authority " + authority);
|
||||
|
||||
// set class loader for iCal4j ResourceLoader
|
||||
Thread.currentThread().setContextClassLoader(getContext().getClassLoader());
|
||||
|
||||
SyncManager syncManager = new SyncManager(account, accountManager);
|
||||
|
||||
Map<LocalCollection, RemoteCollection> syncCollections = getSyncPairs(account, provider);
|
||||
if (syncCollections == null)
|
||||
Log.i(TAG, "Nothing to synchronize");
|
||||
else
|
||||
try {
|
||||
for (Map.Entry<LocalCollection, RemoteCollection> entry : syncCollections.entrySet())
|
||||
syncManager.synchronize(entry.getKey(), entry.getValue(), extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL), syncResult);
|
||||
|
||||
} catch (AuthenticationException ex) {
|
||||
syncResult.stats.numAuthExceptions++;
|
||||
Log.e(TAG, "HTTP authorization error", ex);
|
||||
} catch (InvalidDavResponseException ex) {
|
||||
syncResult.stats.numParseExceptions++;
|
||||
Log.e(TAG, "Invalid DAV response", ex);
|
||||
} catch (HttpException ex) {
|
||||
syncResult.stats.numIoExceptions++;
|
||||
Log.e(TAG, "HTTP error", ex);
|
||||
} catch (OperationApplicationException ex) {
|
||||
syncResult.databaseError = true;
|
||||
Log.e(TAG, "Content provider operation error", ex);
|
||||
} catch (RemoteException ex) {
|
||||
syncResult.databaseError = true;
|
||||
Log.e(TAG, "Remote process (content provider?) died", ex);
|
||||
} catch (IOException ex) {
|
||||
syncResult.stats.numIoExceptions++;
|
||||
Log.e(TAG, "I/O error", ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -40,7 +40,7 @@ public class SyncManager {
|
||||
this.accountManager = accountManager;
|
||||
}
|
||||
|
||||
public void synchronize(LocalCollection local, RemoteCollection dav, boolean manualSync, SyncResult syncResult) throws RemoteException, OperationApplicationException, IOException, IncapableResourceException, HttpException, ParserException {
|
||||
public void synchronize(LocalCollection local, RemoteCollection dav, boolean manualSync, SyncResult syncResult) throws RemoteException, OperationApplicationException, IOException, HttpException {
|
||||
boolean fetchCollection = false;
|
||||
|
||||
// PHASE 1: UPLOAD LOCALLY-CHANGED RESOURCES
|
||||
@ -139,8 +139,8 @@ public class SyncManager {
|
||||
Log.i(TAG, "Adding " + res.getName());
|
||||
try {
|
||||
local.add(res);
|
||||
} catch (ValidationException e) {
|
||||
Log.e(TAG, "Invalid resource: " + res.getName());
|
||||
} catch (ValidationException ex) {
|
||||
Log.w(TAG, "Ignoring invalid remote resource: " + res.getName(), ex);
|
||||
}
|
||||
syncResult.stats.numInserts++;
|
||||
}
|
||||
@ -151,8 +151,8 @@ public class SyncManager {
|
||||
for (Resource res : dav.multiGet(resourcesToUpdate.toArray(new Resource[0]))) {
|
||||
try {
|
||||
local.updateByRemoteName(res);
|
||||
} catch (ValidationException e) {
|
||||
Log.e(TAG, "Invalid resource: " + res.getName());
|
||||
} catch (ValidationException ex) {
|
||||
Log.e(TAG, "Ignoring invalid remote resource: " + res.getName(), ex);
|
||||
}
|
||||
Log.i(TAG, "Updating " + res.getName());
|
||||
syncResult.stats.numInserts++;
|
||||
|
@ -0,0 +1,11 @@
|
||||
package at.bitfire.davdroid.webdav;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
|
||||
public class InvalidDavResponseException extends HttpException {
|
||||
private static final long serialVersionUID = -2118919144443165706L;
|
||||
|
||||
public InvalidDavResponseException() {
|
||||
super("Invalid DAV response");
|
||||
}
|
||||
}
|
@ -44,7 +44,7 @@ public class WebDavCollection extends WebDavResource {
|
||||
@Getter protected List<WebDavResource> members = new LinkedList<WebDavResource>();
|
||||
|
||||
|
||||
public WebDavCollection(URI baseURL, String username, String password, boolean preemptiveAuth) throws IOException {
|
||||
public WebDavCollection(URI baseURL, String username, String password, boolean preemptiveAuth) {
|
||||
super(baseURL, username, password, preemptiveAuth);
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ public class WebDavCollection extends WebDavResource {
|
||||
/* collection operations */
|
||||
|
||||
|
||||
public boolean propfind(HttpPropfind.Mode mode) throws IOException, IncapableResourceException, HttpException {
|
||||
public boolean propfind(HttpPropfind.Mode mode) throws IOException, InvalidDavResponseException, HttpException {
|
||||
HttpPropfind propfind = new HttpPropfind(location, mode);
|
||||
HttpResponse response = client.execute(propfind);
|
||||
checkResponse(response);
|
||||
@ -74,9 +74,9 @@ public class WebDavCollection extends WebDavResource {
|
||||
multistatus = serializer.read(DavMultistatus.class, is, false);
|
||||
|
||||
Log.d(TAG, "Received multistatus response: " + baos.toString("UTF-8"));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
throw new IncapableResourceException();
|
||||
} catch (Exception ex) {
|
||||
Log.w(TAG, "Invalid PROPFIND XML response", ex);
|
||||
throw new InvalidDavResponseException();
|
||||
}
|
||||
processMultiStatus(multistatus);
|
||||
return true;
|
||||
@ -85,7 +85,7 @@ public class WebDavCollection extends WebDavResource {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean multiGet(String[] names, MultigetType type) throws IOException, IncapableResourceException, HttpException {
|
||||
public boolean multiGet(String[] names, MultigetType type) throws IOException, InvalidDavResponseException, HttpException {
|
||||
DavMultiget multiget = (type == MultigetType.ADDRESS_BOOK) ? new DavAddressbookMultiget() : new DavCalendarMultiget();
|
||||
|
||||
multiget.prop = new DavProp();
|
||||
@ -128,7 +128,7 @@ public class WebDavCollection extends WebDavResource {
|
||||
processMultiStatus(multistatus);
|
||||
|
||||
} else
|
||||
throw new IncapableResourceException();
|
||||
throw new InvalidDavResponseException();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ public class WebDavResource {
|
||||
protected DefaultHttpClient client;
|
||||
|
||||
|
||||
public WebDavResource(URI baseURL, String username, String password, boolean preemptive) throws IOException {
|
||||
public WebDavResource(URI baseURL, String username, String password, boolean preemptive) {
|
||||
location = baseURL.normalize();
|
||||
|
||||
client = new DefaultHttpClient();
|
||||
|
Loading…
Reference in New Issue
Block a user