1
0
mirror of https://github.com/etesync/android synced 2024-12-23 07:08:16 +00:00

First implementation of CardDAV sync with dav4android and vcard4android

* try to get rid of Apache Commons
This commit is contained in:
Ricki Hirner 2015-10-10 23:30:38 +02:00
parent bc2d1ba96d
commit 7f4b4855a0
11 changed files with 127 additions and 1727 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "dav4android"]
path = dav4android
url = git@gitlab.com:bitfireAT/dav4android.git
[submodule "vcard4android"]
path = vcard4android
url = git@gitlab.com:bitfireAT/vcard4android.git

View File

@ -56,14 +56,6 @@ dependencies {
exclude group: 'org.codehaus.groovy', module: 'groovy-all'
}
compile('org.slf4j:slf4j-android:1.7.12') // slf4j is used by ical4j
// ez-vcard for parsing/generating VCards
compile('com.googlecode.ez-vcard:ez-vcard:0.9.6') {
// hCard functionality not needed
exclude group: 'org.jsoup', module: 'jsoup'
exclude group: 'org.freemarker', module: 'freemarker'
// jCard functionality not needed
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-core'
}
// dnsjava for querying SRV/TXT records
compile 'dnsjava:dnsjava:2.1.7'
// HttpClient 4.3, Android flavour for WebDAV operations
@ -76,4 +68,5 @@ dependencies {
}
compile project(':dav4android')
compile project(':vcard4android')
}

View File

@ -1,109 +0,0 @@
/*
* Copyright © 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.resource;
import android.content.res.AssetManager;
import android.test.InstrumentationTestCase;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.CharEncoding;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Arrays;
import at.bitfire.davdroid.webdav.DavException;
import at.bitfire.davdroid.webdav.HttpException;
import ezvcard.VCardVersion;
import ezvcard.property.Email;
import ezvcard.property.Telephone;
import lombok.Cleanup;
public class ContactTest extends InstrumentationTestCase {
AssetManager assetMgr;
public void setUp() throws IOException, InvalidResourceException {
assetMgr = getInstrumentation().getContext().getResources().getAssets();
}
public void testGenerateDifferentVersions() throws Exception {
Contact c = new Contact("test.vcf", null);
// should generate VCard 3.0 by default
assertEquals("text/vcard; charset=utf-8", c.getContentType().toString().toLowerCase());
assertTrue(new String(c.toEntity().toByteArray()).contains("VERSION:3.0"));
// now let's generate VCard 4.0
c.vCardVersion = VCardVersion.V4_0;
assertEquals("text/vcard; version=4.0", c.getContentType().toString());
assertTrue(new String(c.toEntity().toByteArray()).contains("VERSION:4.0"));
}
public void testReferenceVCard3() throws IOException, InvalidResourceException {
Contact c = parseVCF("reference-vcard3.vcf", Charset.forName(CharEncoding.UTF_8));
assertEquals("Gümp", c.familyName);
assertEquals("Förrest", c.givenName);
assertEquals("Förrest Gümp", c.displayName);
assertEquals("Bubba Gump Shrimpß Co.", c.organization.getValues().get(0));
assertEquals("Shrimp Man", c.jobTitle);
Telephone phone1 = c.getPhoneNumbers().get(0);
assertEquals("(111) 555-1212", phone1.getText());
assertEquals("WORK", phone1.getParameters("TYPE").get(0));
assertEquals("VOICE", phone1.getParameters("TYPE").get(1));
Telephone phone2 = c.getPhoneNumbers().get(1);
assertEquals("(404) 555-1212", phone2.getText());
assertEquals("HOME", phone2.getParameters("TYPE").get(0));
assertEquals("VOICE", phone2.getParameters("TYPE").get(1));
Email email = c.getEmails().get(0);
assertEquals("forrestgump@example.com", email.getValue());
assertEquals("PREF", email.getParameters("TYPE").get(0));
assertEquals("INTERNET", email.getParameters("TYPE").get(1));
@Cleanup InputStream photoStream = assetMgr.open("davdroid-logo-192.png", AssetManager.ACCESS_STREAMING);
byte[] expectedPhoto = IOUtils.toByteArray(photoStream);
assertTrue(Arrays.equals(c.photo, expectedPhoto));
}
public void testParseInvalidUnknownProperties() throws IOException {
Contact c = parseVCF("invalid-unknown-properties.vcf");
assertEquals("VCard with invalid unknown properties", c.displayName);
assertNull(c.unknownProperties);
}
public void testParseLatin1() throws IOException {
Contact c = parseVCF("latin1.vcf", Charset.forName(CharEncoding.ISO_8859_1));
assertEquals("Özkan Äuçek", c.displayName);
assertEquals("Özkan", c.givenName);
assertEquals("Äuçek", c.familyName);
assertNull(c.unknownProperties);
}
protected Contact parseVCF(String fname, Charset charset) throws IOException {
@Cleanup InputStream in = assetMgr.open(fname, AssetManager.ACCESS_STREAMING);
Contact c = new Contact(fname, null);
c.parseEntity(in, charset, new Resource.AssetDownloader() {
@Override
public byte[] download(URI uri) throws URISyntaxException, IOException, HttpException, DavException {
return IOUtils.toByteArray(uri);
}
});
return c;
}
protected Contact parseVCF(String fname) throws IOException {
return parseVCF(fname, null);
}
}

View File

@ -30,7 +30,6 @@ public class DavResourceFinderTest extends InstrumentationTestCase {
@Override
protected void tearDown() throws IOException {
finder.close();
}
@ -39,7 +38,6 @@ public class DavResourceFinderTest extends InstrumentationTestCase {
finder.findResources(info);
/*** CardDAV ***/
assertTrue(info.isCardDAV());
List<ResourceInfo> collections = info.getAddressBooks();
// two address books
assertEquals(2, collections.size());
@ -52,7 +50,6 @@ public class DavResourceFinderTest extends InstrumentationTestCase {
assertEquals("Absolute URI VCard Book", collection.getDescription());
/*** CalDAV ***/
assertTrue(info.isCalDAV());
collections = info.getCalendars();
assertEquals(2, collections.size());

View File

@ -1,42 +0,0 @@
/*
* Copyright © 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.resource;
import org.apache.http.impl.client.CloseableHttpClient;
import java.net.URISyntaxException;
import at.bitfire.davdroid.syncadapter.AccountSettings;
import at.bitfire.davdroid.webdav.DavMultiget;
import ezvcard.VCardVersion;
public class CardDavAddressBook extends WebDavCollection<Contact> {
AccountSettings accountSettings;
@Override
protected String memberAcceptedMimeTypes() {
return "text/vcard;q=0.8, text/vcard;version=4.0";
}
@Override
protected DavMultiget.Type multiGetType() {
return accountSettings.getAddressBookVCardVersion() == VCardVersion.V4_0 ?
DavMultiget.Type.ADDRESS_BOOK_V4 : DavMultiget.Type.ADDRESS_BOOK;
}
@Override
protected Contact newResourceSkeleton(String name, String ETag) {
return new Contact(name, ETag);
}
public CardDavAddressBook(AccountSettings settings, CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
super(httpClient, baseURL, user, password, preemptiveAuth);
accountSettings = settings;
}
}

View File

@ -1,463 +0,0 @@
/*
* Copyright © 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.resource;
import android.util.Log;
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.ContentType;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import at.bitfire.davdroid.Constants;
import ezvcard.Ezvcard;
import ezvcard.VCard;
import ezvcard.VCardVersion;
import ezvcard.ValidationWarnings;
import ezvcard.parameter.EmailType;
import ezvcard.parameter.ImageType;
import ezvcard.parameter.RelatedType;
import ezvcard.parameter.TelephoneType;
import ezvcard.property.Address;
import ezvcard.property.Anniversary;
import ezvcard.property.Birthday;
import ezvcard.property.Categories;
import ezvcard.property.Email;
import ezvcard.property.FormattedName;
import ezvcard.property.Impp;
import ezvcard.property.Logo;
import ezvcard.property.Nickname;
import ezvcard.property.Note;
import ezvcard.property.Organization;
import ezvcard.property.Photo;
import ezvcard.property.ProductId;
import ezvcard.property.RawProperty;
import ezvcard.property.Related;
import ezvcard.property.Revision;
import ezvcard.property.Role;
import ezvcard.property.Sound;
import ezvcard.property.Source;
import ezvcard.property.StructuredName;
import ezvcard.property.Telephone;
import ezvcard.property.Title;
import ezvcard.property.Uid;
import ezvcard.property.Url;
import lombok.Cleanup;
import lombok.Getter;
import lombok.ToString;
/**
* Represents a contact. Locally, this is a Contact in the Android
* device; remote, this is a VCard.
*/
@ToString(callSuper = true)
public class Contact extends Resource {
private final static String TAG = "davdroid.Contact";
protected VCardVersion vCardVersion = VCardVersion.V3_0;
public static final ContentType
MIME_VCARD3 = ContentType.create("text/vcard", CharEncoding.UTF_8),
MIME_VCARD4 = ContentType.parse("text/vcard; version=4.0");
public static final String
PROPERTY_STARRED = "X-DAVDROID-STARRED",
PROPERTY_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME",
PROPERTY_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME",
PROPERTY_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME",
PROPERTY_SIP = "X-SIP";
public static final EmailType EMAIL_TYPE_MOBILE = EmailType.get("x-mobile");
public static final TelephoneType
PHONE_TYPE_CALLBACK = TelephoneType.get("x-callback"),
PHONE_TYPE_COMPANY_MAIN = TelephoneType.get("x-company_main"),
PHONE_TYPE_RADIO = TelephoneType.get("x-radio"),
PHONE_TYPE_ASSISTANT = TelephoneType.get("X-assistant"),
PHONE_TYPE_MMS = TelephoneType.get("x-mms");
public static final RelatedType
RELATED_TYPE_BROTHER = RelatedType.get("brother"),
RELATED_TYPE_FATHER = RelatedType.get("father"),
RELATED_TYPE_MANAGER = RelatedType.get("manager"),
RELATED_TYPE_MOTHER = RelatedType.get("mother"),
RELATED_TYPE_REFERRED_BY = RelatedType.get("referred-by"),
RELATED_TYPE_SISTER = RelatedType.get("sister");
protected String unknownProperties;
protected boolean starred;
protected String displayName, nickName;
protected String prefix, givenName, middleName, familyName, suffix;
protected String phoneticGivenName, phoneticMiddleName, phoneticFamilyName;
protected String note;
protected Organization organization;
protected String jobTitle, jobDescription;
protected byte[] photo;
protected Anniversary anniversary;
protected Birthday birthDay;
// lists must not be set to null (because they're iterated using "for"), so only getters are exposed
@Getter private List<Telephone> phoneNumbers = new LinkedList<>();
@Getter private List<Email> emails = new LinkedList<>();
@Getter private List<Impp> impps = new LinkedList<>();
@Getter private List<Address> addresses = new LinkedList<>();
@Getter private List<String> categories = new LinkedList<>();
@Getter private List<String> URLs = new LinkedList<>();
@Getter private List<Related> relations = new LinkedList<>();
/* instance methods */
Contact(String name, String ETag) {
super(name, ETag);
}
Contact(long localID, String resourceName, String eTag) {
super(localID, resourceName, eTag);
}
@Override
public void initialize() {
generateUID();
name = uid + ".vcf";
}
protected void generateUID() {
uid = UUID.randomUUID().toString();
}
/* VCard methods */
@SuppressWarnings("LoopStatementThatDoesntLoop")
@Override
public void parseEntity(InputStream is, Charset charset, AssetDownloader downloader) throws IOException {
final VCard vcard;
if (charset != null) {
@Cleanup InputStreamReader reader = new InputStreamReader(is, charset);
vcard = Ezvcard.parse(reader).first();
} else
vcard = Ezvcard.parse(is).first();
if (vcard == null)
return;
// now work through all supported properties
// supported properties are removed from the VCard after parsing
// so that only unknown properties are left and can be stored separately
// UID
Uid uid = vcard.getUid();
if (uid != null) {
this.uid = uid.getValue();
vcard.removeProperties(Uid.class);
} else {
Log.w(TAG, "Received VCard without UID, generating new one");
generateUID();
}
// X-DAVDROID-STARRED
RawProperty starred = vcard.getExtendedProperty(PROPERTY_STARRED);
if (starred != null && starred.getValue() != null) {
this.starred = starred.getValue().equals("1");
vcard.removeExtendedProperty(PROPERTY_STARRED);
} else
this.starred = false;
// FN
FormattedName fn = vcard.getFormattedName();
if (fn != null) {
displayName = fn.getValue();
vcard.removeProperties(FormattedName.class);
} else
Log.w(TAG, "Received invalid VCard without FN (formatted name) property");
// N
StructuredName n = vcard.getStructuredName();
if (n != null) {
prefix = StringUtils.join(n.getPrefixes(), " ");
givenName = n.getGiven();
middleName = StringUtils.join(n.getAdditional(), " ");
familyName = n.getFamily();
suffix = StringUtils.join(n.getSuffixes(), " ");
vcard.removeProperties(StructuredName.class);
}
// phonetic names
RawProperty
phoneticFirstName = vcard.getExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME),
phoneticMiddleName = vcard.getExtendedProperty(PROPERTY_PHONETIC_MIDDLE_NAME),
phoneticLastName = vcard.getExtendedProperty(PROPERTY_PHONETIC_LAST_NAME);
if (phoneticFirstName != null) {
phoneticGivenName = phoneticFirstName.getValue();
vcard.removeExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME);
}
if (phoneticMiddleName != null) {
this.phoneticMiddleName = phoneticMiddleName.getValue();
vcard.removeExtendedProperty(PROPERTY_PHONETIC_MIDDLE_NAME);
}
if (phoneticLastName != null) {
phoneticFamilyName = phoneticLastName.getValue();
vcard.removeExtendedProperty(PROPERTY_PHONETIC_LAST_NAME);
}
// TEL
phoneNumbers = vcard.getTelephoneNumbers();
vcard.removeProperties(Telephone.class);
// EMAIL
emails = vcard.getEmails();
vcard.removeProperties(Email.class);
// PHOTO
for (Photo photo : vcard.getPhotos()) {
this.photo = photo.getData();
if (this.photo == null && photo.getUrl() != null)
try {
URI uri = new URI(photo.getUrl());
Log.i(TAG, "Downloading contact photo from " + uri);
this.photo = downloader.download(uri);
} catch(Exception e) {
Log.w(TAG, "Couldn't fetch contact photo", e);
}
vcard.removeProperties(Photo.class);
break;
}
// ORG
organization = vcard.getOrganization();
vcard.removeProperties(Organization.class);
// TITLE
for (Title title : vcard.getTitles()) {
jobTitle = title.getValue();
vcard.removeProperties(Title.class);
break;
}
// ROLE
for (Role role : vcard.getRoles()) {
this.jobDescription = role.getValue();
vcard.removeProperties(Role.class);
break;
}
// IMPP
impps = vcard.getImpps();
vcard.removeProperties(Impp.class);
// NICKNAME
Nickname nicknames = vcard.getNickname();
if (nicknames != null) {
if (nicknames.getValues() != null)
nickName = StringUtils.join(nicknames.getValues(), ", ");
vcard.removeProperties(Nickname.class);
}
// NOTE
List<String> notes = new LinkedList<>();
for (Note note : vcard.getNotes())
notes.add(note.getValue());
if (!notes.isEmpty())
note = StringUtils.join(notes, "\n---\n");
vcard.removeProperties(Note.class);
// ADR
addresses = vcard.getAddresses();
vcard.removeProperties(Address.class);
// CATEGORY
Categories categories = vcard.getCategories();
if (categories != null)
this.categories = categories.getValues();
vcard.removeProperties(Categories.class);
// URL
for (Url url : vcard.getUrls())
URLs.add(url.getValue());
vcard.removeProperties(Url.class);
// BDAY
birthDay = vcard.getBirthday();
vcard.removeProperties(Birthday.class);
// ANNIVERSARY
anniversary = vcard.getAnniversary();
vcard.removeProperties(Anniversary.class);
// RELATED
for (Related related : vcard.getRelations()) {
String text = related.getText();
if (!StringUtils.isNotEmpty(text)) {
// process only free-form relations with text
relations.add(related);
vcard.removeProperty(related);
}
}
// X-SIP
for (RawProperty sip : vcard.getExtendedProperties(PROPERTY_SIP))
impps.add(new Impp("sip", sip.getValue()));
vcard.removeExtendedProperty(PROPERTY_SIP);
// remove binary properties because of potential OutOfMemory / TransactionTooLarge exceptions
vcard.removeProperties(Logo.class);
vcard.removeProperties(Sound.class);
// remove properties that don't apply anymore
vcard.removeProperties(ProductId.class);
vcard.removeProperties(Revision.class);
vcard.removeProperties(Source.class);
// store all remaining properties into unknownProperties
if (!vcard.getProperties().isEmpty() || !vcard.getExtendedProperties().isEmpty())
try {
unknownProperties = vcard.write();
} catch(Exception e) {
Log.w(TAG, "Couldn't store unknown properties (maybe illegal syntax), dropping them");
}
}
@Override
public ContentType getContentType() {
return (vCardVersion == VCardVersion.V4_0) ? MIME_VCARD4 : MIME_VCARD3;
}
@Override
public ByteArrayOutputStream toEntity() throws IOException {
VCard vcard = null;
try {
if (unknownProperties != null)
vcard = Ezvcard.parse(unknownProperties).first();
} catch (Exception e) {
Log.w(TAG, "Couldn't parse original property set, beginning from scratch");
}
if (vcard == null)
vcard = new VCard();
if (uid != null)
vcard.setUid(new Uid(uid));
else
Log.wtf(TAG, "Generating VCard without UID");
if (starred)
vcard.setExtendedProperty(PROPERTY_STARRED, "1");
if (displayName != null)
vcard.setFormattedName(displayName);
else if (organization != null && organization.getValues() != null && organization.getValues().get(0) != null)
vcard.setFormattedName(organization.getValues().get(0));
else
Log.w(TAG, "No FN (formatted name) available to generate VCard");
// N
if (prefix != null || familyName != null || middleName != null || givenName != null || suffix != null) {
StructuredName n = new StructuredName();
if (prefix != null)
for (String p : StringUtils.split(prefix))
n.addPrefix(p);
n.setGiven(givenName);
if (middleName != null)
for (String middle : StringUtils.split(middleName))
n.addAdditional(middle);
n.setFamily(familyName);
if (suffix != null)
for (String s : StringUtils.split(suffix))
n.addSuffix(s);
vcard.setStructuredName(n);
}
// phonetic names
if (phoneticGivenName != null)
vcard.addExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME, phoneticGivenName);
if (phoneticMiddleName != null)
vcard.addExtendedProperty(PROPERTY_PHONETIC_MIDDLE_NAME, phoneticMiddleName);
if (phoneticFamilyName != null)
vcard.addExtendedProperty(PROPERTY_PHONETIC_LAST_NAME, phoneticFamilyName);
// TEL
for (Telephone phoneNumber : phoneNumbers)
vcard.addTelephoneNumber(phoneNumber);
// EMAIL
for (Email email : emails)
vcard.addEmail(email);
// ORG, TITLE, ROLE
if (organization != null)
vcard.setOrganization(organization);
if (jobTitle != null)
vcard.addTitle(jobTitle);
if (jobDescription != null)
vcard.addRole(jobDescription);
// IMPP
for (Impp impp : impps)
vcard.addImpp(impp);
// NICKNAME
if (!StringUtils.isBlank(nickName))
vcard.setNickname(nickName);
// NOTE
if (!StringUtils.isBlank(note))
vcard.addNote(note);
// ADR
for (Address address : addresses)
vcard.addAddress(address);
// CATEGORY
if (!categories.isEmpty())
vcard.setCategories(categories.toArray(new String[categories.size()]));
// URL
for (String url : URLs)
vcard.addUrl(url);
// ANNIVERSARY
if (anniversary != null)
vcard.setAnniversary(anniversary);
// BDAY
if (birthDay != null)
vcard.setBirthday(birthDay);
// PHOTO
if (photo != null)
vcard.addPhoto(new Photo(photo, ImageType.JPEG));
// REL
for (Related related : relations)
vcard.addRelated(related);
// PRODID, REV
vcard.setProductId("DAVdroid/" + Constants.APP_VERSION + " (ez-vcard/" + Ezvcard.VERSION + ")");
vcard.setRevision(Revision.now());
// validate and print warnings
ValidationWarnings warnings = vcard.validate(vCardVersion);
if (!warnings.isEmpty())
Log.w(TAG, "Created potentially invalid VCard:\n" + warnings);
ByteArrayOutputStream os = new ByteArrayOutputStream();
Ezvcard
.write(vcard)
.version(vCardVersion)
.versionStrict(false)
.prodId(false) // we provide our own PRODID
.go(os);
return os;
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright © 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.resource;
import at.bitfire.vcard4android.AndroidAddressBook;
import at.bitfire.vcard4android.AndroidContact;
import at.bitfire.vcard4android.AndroidContactFactory;
import at.bitfire.vcard4android.Contact;
public class LocalContact extends AndroidContact {
protected LocalContact(AndroidAddressBook addressBook, long id) {
super(addressBook, id);
}
public LocalContact(AndroidAddressBook addressBook, Contact contact) {
super(addressBook, contact);
}
static class Factory extends AndroidContactFactory {
static final Factory INSTANCE = new Factory();
@Override
public LocalContact newInstance(AndroidAddressBook addressBook, long id) {
return new LocalContact(addressBook, id);
}
@Override
public LocalContact newInstance(AndroidAddressBook addressBook, Contact contact) {
return new LocalContact(addressBook, contact);
}
public LocalContact[] newArray(int size) {
return new LocalContact[size];
}
}
}

View File

@ -9,20 +9,29 @@ package at.bitfire.davdroid.syncadapter;
import android.accounts.Account;
import android.app.Service;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import com.squareup.okhttp.HttpUrl;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.ResponseBody;
import at.bitfire.davdroid.resource.CardDavAddressBook;
import org.apache.commons.io.Charsets;
import at.bitfire.dav4android.DavAddressBook;
import at.bitfire.dav4android.DavResource;
import at.bitfire.dav4android.property.SupportedAddressData;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.HttpClient;
import at.bitfire.davdroid.resource.LocalAddressBook;
import at.bitfire.davdroid.resource.LocalCollection;
import at.bitfire.davdroid.resource.WebDavCollection;
import at.bitfire.davdroid.resource.LocalContact;
import at.bitfire.vcard4android.Contact;
public class ContactsSyncAdapterService extends Service {
private static ContactsSyncAdapter syncAdapter;
@ -35,7 +44,6 @@ public class ContactsSyncAdapterService extends Service {
@Override
public void onDestroy() {
syncAdapter.close();
syncAdapter = null;
}
@ -45,37 +53,53 @@ public class ContactsSyncAdapterService extends Service {
}
private static class ContactsSyncAdapter extends DavSyncAdapter {
private final static String TAG = "davdroid.ContactsSync";
private static class ContactsSyncAdapter extends AbstractThreadedSyncAdapter {
private ContactsSyncAdapter(Context context) {
super(context);
}
public ContactsSyncAdapter(Context context) {
super(context, false);
}
@Override
protected Map<LocalCollection<?>, WebDavCollection<?>> getSyncPairs(Account account, ContentProviderClient provider) {
AccountSettings settings = new AccountSettings(getContext(), account);
String userName = settings.getUserName(),
password = settings.getPassword();
boolean preemptive = settings.getPreemptiveAuth();
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
Constants.log.info("Starting sync for authority " + authority);
String addressBookURL = settings.getAddressBookURL();
if (addressBookURL == null)
return null;
try {
LocalCollection<?> database = new LocalAddressBook(account, provider, settings);
WebDavCollection<?> dav = new CardDavAddressBook(settings, httpClient, addressBookURL, userName, password, preemptive);
Map<LocalCollection<?>, WebDavCollection<?>> map = new HashMap<>();
map.put(database, dav);
return map;
} catch (URISyntaxException ex) {
Log.e(TAG, "Couldn't build address book URI", ex);
}
return null;
}
}
AccountSettings settings = new AccountSettings(getContext(), account);
HttpClient httpClient = new HttpClient(settings.getUserName(), settings.getPassword(), settings.getPreemptiveAuth());
DavAddressBook dav = new DavAddressBook(httpClient, HttpUrl.parse(settings.getAddressBookURL()));
try {
boolean hasVCard4 = false;
dav.propfind(0, SupportedAddressData.NAME);
SupportedAddressData supportedAddressData = (SupportedAddressData)dav.properties.get(SupportedAddressData.NAME);
if (supportedAddressData != null)
for (MediaType type : supportedAddressData.types)
if ("text/vcard; version=4.0".equalsIgnoreCase(type.toString()))
hasVCard4 = true;
Constants.log.info("Server advertises VCard/4 support: " + hasVCard4);
LocalAddressBook addressBook = new LocalAddressBook(account, provider);
dav.queryMemberETags();
for (DavResource vCard : dav.members) {
Constants.log.info("Found remote VCard: " + vCard.location);
ResponseBody body = vCard.get("text/vcard;q=0.8, text/vcard;version=4.0");
Contact contacts[] = Contact.fromStream(body.byteStream(), body.contentType().charset(Charsets.UTF_8));
if (contacts.length == 1) {
Contact contact = contacts[0];
Constants.log.info(contact.toString());
LocalContact localContact = new LocalContact(addressBook, contact);
localContact.add();
} else
Constants.log.error("Received VCard with not exactly one VCARD");
}
} catch (Exception e) {
Log.e("davdroid", "querying member etags", e);
}
Constants.log.info("Sync complete for authority " + authority);
}
}
}

View File

@ -8,3 +8,4 @@
include ':app'
include ':dav4android'
include ':vcard4android'

1
vcard4android Submodule

@ -0,0 +1 @@
Subproject commit 644ee03c74d35837974db771a7093f6f28623fbc