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:
parent
bc2d1ba96d
commit
7f4b4855a0
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
|
||||
|
@ -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')
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,3 +8,4 @@
|
||||
|
||||
include ':app'
|
||||
include ':dav4android'
|
||||
include ':vcard4android'
|
||||
|
1
vcard4android
Submodule
1
vcard4android
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 644ee03c74d35837974db771a7093f6f28623fbc
|
Loading…
Reference in New Issue
Block a user