From f1eabb6227af48f74571af26f7a66b7932f22635 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Fri, 1 May 2015 02:47:08 +0200 Subject: [PATCH] Support relations the VCard 4.0 way (closes #278) --- .../at/bitfire/davdroid/resource/Contact.java | 48 +++++-- .../davdroid/resource/LocalAddressBook.java | 119 +++++++++++++++++- app/src/main/res/xml/contacts.xml | 18 +++ 3 files changed, 171 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/resource/Contact.java b/app/src/main/java/at/bitfire/davdroid/resource/Contact.java index 8a48cd80..3441fd00 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/Contact.java +++ b/app/src/main/java/at/bitfire/davdroid/resource/Contact.java @@ -18,8 +18,11 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.UUID; import at.bitfire.davdroid.Constants; @@ -27,8 +30,10 @@ import ezvcard.Ezvcard; import ezvcard.VCard; import ezvcard.VCardVersion; import ezvcard.ValidationWarnings; +import ezvcard.Warning; import ezvcard.parameter.EmailType; import ezvcard.parameter.ImageType; +import ezvcard.parameter.RelatedType; import ezvcard.parameter.TelephoneType; import ezvcard.property.Address; import ezvcard.property.Anniversary; @@ -44,6 +49,7 @@ 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; @@ -53,6 +59,8 @@ import ezvcard.property.Telephone; import ezvcard.property.Title; import ezvcard.property.Uid; import ezvcard.property.Url; +import ezvcard.property.VCardProperty; +import ezvcard.util.ListMultimap; import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -83,6 +91,13 @@ public class Contact extends Resource { PHONE_TYPE_RADIO = TelephoneType.get("X-RADIO"), PHONE_TYPE_ASSISTANT = TelephoneType.get("X-ASSISTANT"), PHONE_TYPE_MMS = TelephoneType.get("X-MMS"); + public final static 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"); @Getter @Setter private String unknownProperties; @@ -100,13 +115,13 @@ public class Contact extends Resource { @Getter @Setter private Anniversary anniversary; @Getter @Setter private Birthday birthDay; - @Getter private List phoneNumbers = new LinkedList(); - @Getter private List emails = new LinkedList(); - @Getter private List impps = new LinkedList(); - @Getter private List
addresses = new LinkedList
(); - @Getter private List categories = new LinkedList(); - @Getter private List URLs = new LinkedList(); - + @Getter private List phoneNumbers = new LinkedList<>(); + @Getter private List emails = new LinkedList<>(); + @Getter private List impps = new LinkedList<>(); + @Getter private List
addresses = new LinkedList<>(); + @Getter private List categories = new LinkedList<>(); + @Getter private List URLs = new LinkedList<>(); + @Getter private List relations = new LinkedList<>(); /* instance methods */ @@ -278,7 +293,17 @@ public class Contact extends Resource { // 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())); @@ -411,6 +436,9 @@ public class Contact extends Resource { // PHOTO if (photo != null) vcard.addPhoto(new Photo(photo, ImageType.JPEG)); + + for (Related related : relations) + vcard.addRelated(related); // PRODID, REV vcard.setProductId("DAVdroid/" + Constants.APP_VERSION + " (ez-vcard/" + Ezvcard.VERSION + ")"); @@ -419,8 +447,8 @@ public class Contact extends Resource { // validate and print warnings ValidationWarnings warnings = vcard.validate(vCardVersion); if (!warnings.isEmpty()) - Log.w(TAG, "Created potentially invalid VCard! " + warnings); - + Log.w(TAG, "Created potentially invalid VCard:\n" + warnings); + ByteArrayOutputStream os = new ByteArrayOutputStream(); Ezvcard .write(vcard) diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.java b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.java index 5d620927..7774cb7f 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.java +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.java @@ -27,6 +27,7 @@ import android.provider.ContactsContract.CommonDataKinds.Note; import android.provider.ContactsContract.CommonDataKinds.Organization; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.Photo; +import android.provider.ContactsContract.CommonDataKinds.Relation; import android.provider.ContactsContract.CommonDataKinds.SipAddress; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; @@ -45,22 +46,27 @@ import java.io.InputStream; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import at.bitfire.davdroid.syncadapter.AccountSettings; import ezvcard.parameter.AddressType; import ezvcard.parameter.EmailType; import ezvcard.parameter.ImppType; +import ezvcard.parameter.RelatedType; import ezvcard.parameter.TelephoneType; import ezvcard.property.Address; import ezvcard.property.Anniversary; import ezvcard.property.Birthday; import ezvcard.property.DateOrTimeProperty; import ezvcard.property.Impp; +import ezvcard.property.Related; import ezvcard.property.Telephone; import lombok.Cleanup; @@ -201,6 +207,7 @@ public class LocalAddressBook extends LocalCollection { populateCategories(c); populateURLs(c); populateEvents(c); + populateRelations(c); populateSipAddress(c); } catch(RemoteException ex) { throw new LocalStorageException(ex); @@ -573,6 +580,80 @@ public class LocalAddressBook extends LocalCollection { } } } + + protected void populateRelations(Contact c) throws RemoteException { + @Cleanup Cursor cursor = providerClient.query(dataURI(), + new String[] { Relation.NAME, Relation.TYPE, Relation.LABEL }, + Relation.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", + new String[] { String.valueOf(c.getLocalID()), Relation.CONTENT_ITEM_TYPE }, null); + + Map> relations = new HashMap<>(); + while (cursor != null && cursor.moveToNext()) { + String name = cursor.getString(0); + List types = relations.get(name); + if (types == null) { + types = new LinkedList<>(); + relations.put(name, types); + } + + switch (cursor.getInt(1)) { + case Relation.TYPE_ASSISTANT: + types.add(RelatedType.AGENT); + break; + case Relation.TYPE_BROTHER: + types.add(RelatedType.SIBLING); + types.add(Contact.RELATED_TYPE_BROTHER); + break; + case Relation.TYPE_CHILD: + types.add(RelatedType.CHILD); + break; + case Relation.TYPE_DOMESTIC_PARTNER: + types.add(RelatedType.CO_RESIDENT); + break; + case Relation.TYPE_FATHER: + types.add(Contact.RELATED_TYPE_FATHER); + break; + case Relation.TYPE_FRIEND: + types.add(RelatedType.FRIEND); + break; + case Relation.TYPE_MANAGER: + types.add(Contact.RELATED_TYPE_MANAGER); + break; + case Relation.TYPE_MOTHER: + types.add(Contact.RELATED_TYPE_MOTHER); + break; + case Relation.TYPE_PARENT: + types.add(RelatedType.PARENT); + break; + case Relation.TYPE_PARTNER: + types.add(RelatedType.SWEETHEART); + break; + case Relation.TYPE_REFERRED_BY: + types.add(Contact.RELATED_TYPE_REFERRED_BY); + case Relation.TYPE_RELATIVE: + types.add(RelatedType.KIN); + break; + case Relation.TYPE_SISTER: + types.add(RelatedType.SIBLING); + types.add(Contact.RELATED_TYPE_SISTER); + break; + case Relation.TYPE_SPOUSE: + types.add(RelatedType.SPOUSE); + case Relation.TYPE_CUSTOM: + String customType = cursor.getString(2); + if (!StringUtils.isEmpty(customType)) + types.add(RelatedType.get(customType)); + } + } + + for (String name : relations.keySet()) { + Related related = new Related(); + related.setText(name); + for (RelatedType type : relations.get(name)) + related.addType(type); + c.getRelations().add(related); + } + } protected void populateSipAddress(Contact c) throws RemoteException { @Cleanup Cursor cursor = providerClient.query(dataURI(), @@ -659,9 +740,11 @@ public class LocalAddressBook extends LocalCollection { queueOperation(buildEvent(newDataInsertBuilder(localID, backrefIdx), contact.getAnniversary(), CommonDataKinds.Event.TYPE_ANNIVERSARY)); if (contact.getBirthDay() != null) queueOperation(buildEvent(newDataInsertBuilder(localID, backrefIdx), contact.getBirthDay(), CommonDataKinds.Event.TYPE_BIRTHDAY)); - - // TODO relations - + + for (Related related : contact.getRelations()) + for (RelatedType type : related.getTypes()) + queueOperation(buildRelated(newDataInsertBuilder(localID, backrefIdx), type, related.getText())); + // SIP addresses are built by buildIMPP } @@ -981,7 +1064,35 @@ public class LocalAddressBook extends LocalCollection { .withValue(CommonDataKinds.Event.TYPE, type) .withValue(CommonDataKinds.Event.START_DATE, formatter.format(date.getDate())); } - + + protected Builder buildRelated(Builder builder, RelatedType type, String name) { + int typeCode = 0; + String typeLabel = null; + if (type == RelatedType.CHILD) + typeCode = Relation.TYPE_CHILD; + else if (type == RelatedType.CO_RESIDENT) + typeCode = Relation.TYPE_DOMESTIC_PARTNER; + else if (type == RelatedType.FRIEND) + typeCode = Relation.TYPE_FRIEND; + else if (type == RelatedType.PARENT) + typeCode = Relation.TYPE_PARENT; + else if (type == RelatedType.SPOUSE) + typeCode = Relation.TYPE_SPOUSE; + else if (type == RelatedType.KIN) + typeCode = Relation.TYPE_RELATIVE; + else if (type == RelatedType.SWEETHEART) + typeCode = Relation.TYPE_PARTNER; + else { + typeCode = Relation.TYPE_CUSTOM; + typeLabel = type.getValue(); + } + + return builder + .withValue(Data.MIMETYPE, Relation.CONTENT_ITEM_TYPE) + .withValue(Relation.TYPE, typeCode) + .withValue(Relation.NAME, name) + .withValue(Relation.LABEL, typeLabel); + } /* helper methods */ diff --git a/app/src/main/res/xml/contacts.xml b/app/src/main/res/xml/contacts.xml index 91dcb48e..1b98b37e 100644 --- a/app/src/main/res/xml/contacts.xml +++ b/app/src/main/res/xml/contacts.xml @@ -87,6 +87,24 @@ + + + + + + + + + + + + + + + + + +