diff --git a/libs/ez-vcard-0.9.0.jar b/libs/ez-vcard-0.9.0.jar new file mode 100644 index 00000000..9daf6f6b Binary files /dev/null and b/libs/ez-vcard-0.9.0.jar differ diff --git a/libs/ical4j-vcard-0.9.6.2.jar b/libs/ical4j-vcard-0.9.6.2.jar deleted file mode 100644 index 206ecdc8..00000000 Binary files a/libs/ical4j-vcard-0.9.6.2.jar and /dev/null differ diff --git a/src/at/bitfire/davdroid/ical4j/PhoneticFirstName.java b/src/at/bitfire/davdroid/ical4j/PhoneticFirstName.java deleted file mode 100644 index 470c05dd..00000000 --- a/src/at/bitfire/davdroid/ical4j/PhoneticFirstName.java +++ /dev/null @@ -1,46 +0,0 @@ -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 { - @Override - public PhoneticFirstName createProperty(List params, String value) { - return new PhoneticFirstName(value); - } - - @Override - public PhoneticFirstName createProperty(Group group, List params, String value) { - return new PhoneticFirstName(value); - } - } -} diff --git a/src/at/bitfire/davdroid/ical4j/PhoneticLastName.java b/src/at/bitfire/davdroid/ical4j/PhoneticLastName.java deleted file mode 100644 index 2624420c..00000000 --- a/src/at/bitfire/davdroid/ical4j/PhoneticLastName.java +++ /dev/null @@ -1,46 +0,0 @@ -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 { - @Override - public PhoneticLastName createProperty(List params, String value) { - return new PhoneticLastName(value); - } - - @Override - public PhoneticLastName createProperty(Group group, List params, String value) { - return new PhoneticLastName(value); - } - } -} diff --git a/src/at/bitfire/davdroid/ical4j/PhoneticMiddleName.java b/src/at/bitfire/davdroid/ical4j/PhoneticMiddleName.java deleted file mode 100644 index 5f896753..00000000 --- a/src/at/bitfire/davdroid/ical4j/PhoneticMiddleName.java +++ /dev/null @@ -1,46 +0,0 @@ -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 { - @Override - public PhoneticMiddleName createProperty(List params, String value) { - return new PhoneticMiddleName(value); - } - - @Override - public PhoneticMiddleName createProperty(Group group, List params, String value) { - return new PhoneticMiddleName(value); - } - } -} diff --git a/src/at/bitfire/davdroid/ical4j/Starred.java b/src/at/bitfire/davdroid/ical4j/Starred.java deleted file mode 100644 index 9f5a84c7..00000000 --- a/src/at/bitfire/davdroid/ical4j/Starred.java +++ /dev/null @@ -1,52 +0,0 @@ -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 Starred extends Property { - private static final long serialVersionUID = -8703412738139555307L; - - public static final String PROPERTY_NAME = "DAVDROID-STARRED"; - - protected boolean isStarred; - - - public Starred(String value) { - super(PROPERTY_NAME); - - try { - isStarred = Integer.parseInt(value) > 0; - } catch(NumberFormatException ex) { - // value is not an integer, assume it's true because - // otherwise, the property would probably not be present - isStarred = true; - } - } - - @Override - public String getValue() { - return isStarred ? "1" : "0"; - } - - @Override - public void validate() throws ValidationException { - } - - - public static class Factory implements PropertyFactory { - @Override - public Starred createProperty(List params, String value) { - return new Starred(value); - } - - @Override - public Starred createProperty(Group group, List params, String value) { - return new Starred(value); - } - } -} diff --git a/src/at/bitfire/davdroid/resource/Contact.java b/src/at/bitfire/davdroid/resource/Contact.java index c55b686a..439413ce 100644 --- a/src/at/bitfire/davdroid/resource/Contact.java +++ b/src/at/bitfire/davdroid/resource/Contact.java @@ -9,64 +9,73 @@ package at.bitfire.davdroid.resource; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.StringWriter; -import java.net.URI; -import java.net.URISyntaxException; +import java.net.URL; import java.util.LinkedList; import java.util.List; import java.util.UUID; +import lombok.Cleanup; import lombok.Getter; import lombok.Setter; import lombok.ToString; -import net.fortuna.ical4j.data.ParserException; -import net.fortuna.ical4j.model.Date; -import net.fortuna.ical4j.model.ValidationException; -import net.fortuna.ical4j.vcard.GroupRegistry; -import net.fortuna.ical4j.vcard.ParameterFactoryRegistry; -import net.fortuna.ical4j.vcard.Property; -import net.fortuna.ical4j.vcard.Property.Id; -import net.fortuna.ical4j.vcard.PropertyFactoryRegistry; -import net.fortuna.ical4j.vcard.VCard; -import net.fortuna.ical4j.vcard.VCardBuilder; -import net.fortuna.ical4j.vcard.VCardOutputter; -import net.fortuna.ical4j.vcard.parameter.Type; -import net.fortuna.ical4j.vcard.property.Address; -import net.fortuna.ical4j.vcard.property.BDay; -import net.fortuna.ical4j.vcard.property.Email; -import net.fortuna.ical4j.vcard.property.Fn; -import net.fortuna.ical4j.vcard.property.N; -import net.fortuna.ical4j.vcard.property.Nickname; -import net.fortuna.ical4j.vcard.property.Note; -import net.fortuna.ical4j.vcard.property.Photo; -import net.fortuna.ical4j.vcard.property.Telephone; -import net.fortuna.ical4j.vcard.property.Uid; -import net.fortuna.ical4j.vcard.property.Url; -import net.fortuna.ical4j.vcard.property.Version; import org.apache.commons.lang.StringUtils; 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; +import at.bitfire.davdroid.Constants; +import ezvcard.Ezvcard; +import ezvcard.VCard; +import ezvcard.VCardException; +import ezvcard.VCardVersion; +import ezvcard.parameter.EmailType; +import ezvcard.parameter.ImageType; +import ezvcard.parameter.TelephoneType; +import ezvcard.property.Address; +import ezvcard.property.Anniversary; +import ezvcard.property.Birthday; +import ezvcard.property.Email; +import ezvcard.property.FormattedName; +import ezvcard.property.Nickname; +import ezvcard.property.Note; +import ezvcard.property.Photo; +import ezvcard.property.RawProperty; +import ezvcard.property.Revision; +import ezvcard.property.StructuredName; +import ezvcard.property.Telephone; +import ezvcard.property.Uid; +import ezvcard.property.Url; +import ezvcard.util.IOUtils; @ToString(callSuper = true) public class Contact extends Resource { private final static String TAG = "davdroid.Contact"; + public final static 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"; + + public final static EmailType EMAIL_TYPE_MOBILE = EmailType.get("X-MOBILE"); + + public final static 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"); + @Getter @Setter boolean starred; - @Getter @Setter private String displayName; + @Getter @Setter private String displayName, nickName; @Getter @Setter private String prefix, givenName, middleName, familyName, suffix; @Getter @Setter private String phoneticGivenName, phoneticMiddleName, phoneticFamilyName; - @Getter @Setter private String[] nickNames; + @Getter @Setter private String note, URL; @Getter @Setter private byte[] photo; - @Getter @Setter private Date birthDay; + @Getter @Setter private Anniversary anniversary; + @Getter @Setter private Birthday birthDay; public Contact(String name, String ETag) { @@ -82,187 +91,159 @@ public class Contact extends Resource { /* multiple-record fields */ @Getter private List emails = new LinkedList(); - public void addEmail(Email email) { - emails.add(email); - } - @Getter private List phoneNumbers = new LinkedList(); - public void addPhoneNumber(Telephone number) { - phoneNumbers.add(number); - } - @Getter private List
addresses = new LinkedList
(); - public void addAddress(Address address) { - addresses.add(address); - } - - @Getter private List URLs = new LinkedList(); - public void addURL(URI url) { - URLs.add(url); - } - - @Getter private List notes = new LinkedList(); - public void addNote(String note) { - notes.add(note); - } - /* VCard methods */ @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()); + public void parseEntity(InputStream is) throws IOException, VCardException { + VCard vcard = Ezvcard.parse(is).first(); + if (vcard == null) + return; - // 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), - new GroupRegistry(), - propertyFactoryRegistry, - new ParameterFactoryRegistry() - ); - - VCard vcard; - try { - vcard = builder.build(); - if (vcard == null) - return; - } catch(Exception ex) { - Log.e(TAG, "VCard parser exception", ex); - throw new ParserException("VCard parser crashed", -1, ex); - } - - Uid uid = (Uid)vcard.getProperty(Id.UID); - if (uid != null) - this.uid = uid.getValue(); - else { + Uid uid = vcard.getUid(); + if (uid == null) { Log.w(TAG, "Received VCONTACT without UID, generating new one"); - this.uid = UUID.randomUUID().toString(); + uid = new Uid(UUID.randomUUID().toString()); } + this.uid = uid.getValue(); - Starred starred = (Starred)vcard.getExtendedProperty(Starred.PROPERTY_NAME); + RawProperty starred = vcard.getExtendedProperty(PROPERTY_STARRED); this.starred = starred != null && starred.getValue().equals("1"); - Fn fn = (Fn)vcard.getProperty(Id.FN); - displayName = (fn != null) ? fn.getValue() : null; + FormattedName fn = vcard.getFormattedName(); + if (fn != null) + displayName = fn.getValue(); - Nickname nickname = (Nickname)vcard.getProperty(Id.NICKNAME); - if (nickname != null) - nickNames = nickname.getNames(); - - N n = (N)vcard.getProperty(Id.N); + StructuredName n = vcard.getStructuredName(); if (n != null) { prefix = StringUtils.join(n.getPrefixes(), " "); - givenName = n.getGivenName(); - middleName = StringUtils.join(n.getAdditionalNames(), " "); - familyName = n.getFamilyName(); + givenName = n.getGiven(); + middleName = StringUtils.join(n.getAdditional(), " "); + familyName = n.getFamily(); suffix = StringUtils.join(n.getSuffixes(), " "); } - PhoneticFirstName phoneticFirstName = (PhoneticFirstName)vcard.getExtendedProperty(PhoneticFirstName.PROPERTY_NAME); + 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(); - 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); + phoneNumbers = vcard.getTelephoneNumbers(); + emails = vcard.getEmails(); - for (Property p : vcard.getProperties(Id.TEL)) - phoneNumbers.add((Telephone)p); - - for (Property p : vcard.getProperties(Id.ADR)) - addresses.add((Address)p); - - Photo photo = (Photo)vcard.getProperty(Id.PHOTO); - if (photo != null) - this.photo = photo.getBinary(); - - for (Property p : vcard.getProperties(Id.BDAY)) - birthDay = ((BDay)p).getDate(); - - for (Property p : vcard.getProperties(Id.URL)) - URLs.add(((Url)p).getUri()); - - for (Property p : vcard.getProperties(Id.NOTE)) - notes.add(((Note)p).getValue()); - } - - - @Override - public String toEntity() throws IOException, ValidationException { - VCard vcard = new VCard(); - List properties = vcard.getProperties(); - - properties.add(Version.VERSION_4_0); - - try { - if (uid != null) - properties.add(new Uid(new URI(uid))); - } catch (URISyntaxException e) { - Log.e(TAG, "Couldn't write UID to VCard"); + List photos = vcard.getPhotos(); + if (!photos.isEmpty()) { + Photo photo = photos.get(0); + this.photo = photo.getData(); + if (this.photo == null) { + try { + URL url = new URL(photo.getUrl()); + @Cleanup InputStream in = url.openStream(); + this.photo = IOUtils.toByteArray(in); + } catch(IOException ex) { + Log.w(TAG, "Couldn't fetch photo from referenced URL", ex); + } + } } + Nickname nicknames = vcard.getNickname(); + if (nicknames != null && nicknames.getValues() != null) + nickName = StringUtils.join(nicknames.getValues(), ", "); + + List notes = new LinkedList(); + for (Note note : vcard.getNotes()) + notes.add(note.getValue()); + if (!notes.isEmpty()) + note = StringUtils.join(notes, "\n---\n"); + + addresses = vcard.getAddresses(); + + for (Url url : vcard.getUrls()) { + URL = url.getValue(); + break; + } + + birthDay = vcard.getBirthday(); + anniversary = vcard.getAnniversary(); + } + + + @Override + public String toEntity() throws IOException { + VCard vcard = new VCard(); + + if (uid != null) + vcard.setUid(new Uid(uid)); + if (starred) - properties.add(new Starred("1")); + vcard.setExtendedProperty(PROPERTY_STARRED, "1"); if (displayName != null) - properties.add(new Fn(displayName)); - - if (nickNames != null) - properties.add(new Nickname(nickNames)); + vcard.setFormattedName(displayName); - if (photo != null) - properties.add(new Photo(photo, new Type("image/jpeg"))); - - if (birthDay != null) - properties.add(new BDay(birthDay)); - - if (familyName != null || middleName != null || givenName != null) - properties.add(new N(familyName, givenName, StringUtils.split(middleName), - StringUtils.split(prefix), StringUtils.split(suffix))); + if (familyName != null || middleName != null || givenName != 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); + } if (phoneticGivenName != null) - properties.add(new PhoneticFirstName(phoneticGivenName)); + vcard.addExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME, phoneticGivenName); if (phoneticMiddleName != null) - properties.add(new PhoneticMiddleName(phoneticMiddleName)); + vcard.addExtendedProperty(PROPERTY_PHONETIC_MIDDLE_NAME, phoneticMiddleName); if (phoneticFamilyName != null) - properties.add(new PhoneticLastName(phoneticFamilyName)); + vcard.addExtendedProperty(PROPERTY_PHONETIC_LAST_NAME, phoneticFamilyName); + + for (Telephone phoneNumber : phoneNumbers) + vcard.addTelephoneNumber(phoneNumber); + + for (Email email : emails) + vcard.addEmail(email); - if (!emails.isEmpty()) - properties.addAll(emails); + if (photo != null) + vcard.addPhoto(new Photo(photo, ImageType.JPEG)); - if (!addresses.isEmpty()) - properties.addAll(addresses); + if (nickName != null) + vcard.setNickname(nickName); - if (!phoneNumbers.isEmpty()) - properties.addAll(phoneNumbers); + if (note != null) + vcard.addNote(note); - for (URI uri : URLs) - properties.add(new Url(uri)); + for (Address address : addresses) + vcard.addAddress(address); - for (String note : notes) - properties.add(new Note(note)); + if (URL != null) + vcard.addUrl(URL); - StringWriter writer = new StringWriter(); - new VCardOutputter(true).output(vcard, writer); - return writer.toString(); - } - - - @Override - public void validate() throws ValidationException { - super.validate(); + if (anniversary != null) + vcard.setAnniversary(anniversary); + if (birthDay != null) + vcard.setBirthday(birthDay); + + vcard.setProdId("DAVdroid/" + Constants.APP_VERSION); + vcard.setRevision(Revision.now()); + return Ezvcard + .write(vcard) + .version(VCardVersion.V3_0) + .go(); } } diff --git a/src/at/bitfire/davdroid/resource/Event.java b/src/at/bitfire/davdroid/resource/Event.java index 6f746912..3a70730b 100644 --- a/src/at/bitfire/davdroid/resource/Event.java +++ b/src/at/bitfire/davdroid/resource/Event.java @@ -29,7 +29,6 @@ import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory; import net.fortuna.ical4j.model.Property; import net.fortuna.ical4j.model.PropertyList; import net.fortuna.ical4j.model.TimeZoneRegistry; -import net.fortuna.ical4j.model.ValidationException; import net.fortuna.ical4j.model.component.VAlarm; import net.fortuna.ical4j.model.component.VEvent; import net.fortuna.ical4j.model.component.VTimeZone; @@ -112,8 +111,6 @@ public class Event extends Resource { if (ical == null) return; - Log.d(TAG, "Parsing iCal: " + ical.toString()); - // event ComponentList events = ical.getComponents(Component.VEVENT); if (events == null || events.isEmpty()) @@ -229,7 +226,7 @@ public class Event extends Resource { public long getDtStartInMillis() { - return dtStart.getDate().getTime(); + return (dtStart != null && dtStart.getDate() != null) ? dtStart.getDate().getTime() : 0; } public String getDtStartTzID() { @@ -331,16 +328,6 @@ public class Event extends Resource { date.setTimeZone(tzRegistry.getTimeZone(localTZ)); } - - @Override - public void validate() throws ValidationException { - super.validate(); - - if (dtStart == null) - throw new ValidationException("dtStart must not be empty"); - } - - public static String TimezoneDefToTzId(String timezoneDef) { try { CalendarBuilder builder = new CalendarBuilder(); diff --git a/src/at/bitfire/davdroid/resource/LocalAddressBook.java b/src/at/bitfire/davdroid/resource/LocalAddressBook.java index 4ee0a742..eb4d6742 100644 --- a/src/at/bitfire/davdroid/resource/LocalAddressBook.java +++ b/src/at/bitfire/davdroid/resource/LocalAddressBook.java @@ -7,19 +7,14 @@ ******************************************************************************/ package at.bitfire.davdroid.resource; -import java.net.URI; -import java.net.URISyntaxException; -import java.text.ParseException; import java.util.LinkedList; import java.util.List; - -import net.fortuna.ical4j.model.Date; -import net.fortuna.ical4j.vcard.Parameter.Id; -import net.fortuna.ical4j.vcard.parameter.Type; -import net.fortuna.ical4j.vcard.property.Address; -import net.fortuna.ical4j.vcard.property.Telephone; +import java.util.Locale; +import java.util.Set; +import java.util.UUID; import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.WordUtils; import android.accounts.Account; import android.accounts.AccountManager; @@ -42,8 +37,15 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.Website; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.RawContacts; -import android.util.Log; import at.bitfire.davdroid.Constants; +import ezvcard.parameter.AddressType; +import ezvcard.parameter.EmailType; +import ezvcard.parameter.TelephoneType; +import ezvcard.property.Address; +import ezvcard.property.Anniversary; +import ezvcard.property.Birthday; +import ezvcard.property.DateOrTimeProperty; +import ezvcard.property.Telephone; public class LocalAddressBook extends LocalCollection { @@ -153,102 +155,101 @@ public class LocalAddressBook extends LocalCollection { c.setPhoneticFamilyName(cursor.getString(8)); } - // nick names - cursor = providerClient.query(dataURI(), new String[] { Nickname.NAME }, - Nickname.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), Nickname.CONTENT_ITEM_TYPE }, null); - List nickNames = new LinkedList(); - while (cursor != null && cursor.moveToNext()) - nickNames.add(cursor.getString(0)); - if (!nickNames.isEmpty()) - c.setNickNames(nickNames.toArray(new String[0])); - - // email addresses - cursor = providerClient.query(dataURI(), new String[] { Email.TYPE, Email.ADDRESS }, - Email.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), Email.CONTENT_ITEM_TYPE }, null); - while (cursor != null && cursor.moveToNext()) { - net.fortuna.ical4j.vcard.property.Email email = new net.fortuna.ical4j.vcard.property.Email(cursor.getString(1)); - switch (cursor.getInt(0)) { - case Email.TYPE_HOME: - email.getParameters().add(Type.HOME); - break; - case Email.TYPE_WORK: - email.getParameters().add(Type.WORK); - break; - } - c.addEmail(email); - } - // phone numbers - cursor = providerClient.query(dataURI(), new String[] { Phone.TYPE, Phone.NUMBER }, + cursor = providerClient.query(dataURI(), new String[] { Phone.TYPE, Phone.LABEL, Phone.NUMBER }, Phone.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", new String[] { String.valueOf(c.getLocalID()), Phone.CONTENT_ITEM_TYPE }, null); while (cursor != null && cursor.moveToNext()) { - Telephone number = new Telephone(cursor.getString(1)); - List types = new LinkedList(); - + ezvcard.property.Telephone number = new ezvcard.property.Telephone(cursor.getString(2)); switch (cursor.getInt(0)) { - case Phone.TYPE_FAX_HOME: - types.add("fax"); - types.add("home"); - break; - case Phone.TYPE_FAX_WORK: - types.add("fax"); - types.add("work"); - break; case Phone.TYPE_HOME: - types.add("home"); + number.addType(TelephoneType.HOME); break; case Phone.TYPE_MOBILE: - types.add("cell"); - break; - case Phone.TYPE_OTHER_FAX: - types.add("fax"); - break; - case Phone.TYPE_PAGER: - types.add("pager"); + number.addType(TelephoneType.CELL); break; case Phone.TYPE_WORK: - types.add("work"); + number.addType(TelephoneType.WORK); + break; + case Phone.TYPE_FAX_WORK: + number.addType(TelephoneType.FAX); + number.addType(TelephoneType.WORK); + break; + case Phone.TYPE_FAX_HOME: + number.addType(TelephoneType.FAX); + number.addType(TelephoneType.HOME); + break; + case Phone.TYPE_PAGER: + number.addType(TelephoneType.PAGER); + break; + case Phone.TYPE_CALLBACK: + number.addType(Contact.PHONE_TYPE_CALLBACK); + break; + case Phone.TYPE_CAR: + number.addType(TelephoneType.CAR); + break; + case Phone.TYPE_COMPANY_MAIN: + number.addType(Contact.PHONE_TYPE_COMPANY_MAIN); + break; + case Phone.TYPE_ISDN: + number.addType(TelephoneType.ISDN); + break; + case Phone.TYPE_MAIN: + number.addType(TelephoneType.PREF); + break; + case Phone.TYPE_OTHER_FAX: + number.addType(TelephoneType.FAX); + break; + case Phone.TYPE_RADIO: + number.addType(Contact.PHONE_TYPE_RADIO); + break; + case Phone.TYPE_TELEX: + number.addType(TelephoneType.TEXTPHONE); + break; + case Phone.TYPE_TTY_TDD: + number.addType(TelephoneType.TEXT); break; case Phone.TYPE_WORK_MOBILE: - types.add("cell"); - types.add("work"); + number.addType(TelephoneType.CELL); + number.addType(TelephoneType.WORK); break; case Phone.TYPE_WORK_PAGER: - types.add("pager"); - types.add("work"); + number.addType(TelephoneType.PAGER); + number.addType(TelephoneType.WORK); break; + case Phone.TYPE_ASSISTANT: + number.addType(Contact.PHONE_TYPE_ASSISTANT); + break; + case Phone.TYPE_MMS: + number.addType(Contact.PHONE_TYPE_MMS); + break; + case Phone.TYPE_CUSTOM: + number.addType(TelephoneType.get(labelToXName(cursor.getString(1)))); } - number.getParameters().add(new Type(types.toArray(new String[0]))); - c.addPhoneNumber(number); + c.getPhoneNumbers().add(number); } - // postal addresses - cursor = providerClient.query(dataURI(), new String[] { - /* 0 */ StructuredPostal.FORMATTED_ADDRESS, StructuredPostal.TYPE, - /* 2 */ StructuredPostal.STREET, StructuredPostal.POBOX, StructuredPostal.NEIGHBORHOOD, - /* 5 */ StructuredPostal.CITY, StructuredPostal.REGION, StructuredPostal.POSTCODE, - /* 8 */ StructuredPostal.COUNTRY - }, StructuredPostal.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), StructuredPostal.CONTENT_ITEM_TYPE }, null); + // email addresses + cursor = providerClient.query(dataURI(), new String[] { Email.TYPE, Email.ADDRESS, Email.LABEL }, + Email.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", + new String[] { String.valueOf(c.getLocalID()), Email.CONTENT_ITEM_TYPE }, null); while (cursor != null && cursor.moveToNext()) { - Type[] types = new Type[] {}; - switch (cursor.getInt(1)) { - case StructuredPostal.TYPE_HOME: - types = new Type[] { Type.HOME }; + ezvcard.property.Email email = new ezvcard.property.Email(cursor.getString(1)); + switch (cursor.getInt(0)) { + case Email.TYPE_HOME: + email.addType(EmailType.HOME); break; - case StructuredPostal.TYPE_WORK: - types = new Type[] { Type.WORK }; + case Email.TYPE_WORK: + email.addType(EmailType.WORK); + break; + case Email.TYPE_MOBILE: + email.addType(Contact.EMAIL_TYPE_MOBILE); + break; + case Email.TYPE_CUSTOM: + email.addType(EmailType.get(labelToXName(cursor.getString(2)))); break; } - Address address = new Address( - cursor.getString(3), cursor.getString(4), cursor.getString(2), - cursor.getString(5), cursor.getString(6), cursor.getString(7), - cursor.getString(8), types - ); - c.addAddress(address); + c.getEmails().add(email); } // photo @@ -258,39 +259,74 @@ public class LocalAddressBook extends LocalCollection { if (cursor != null && cursor.moveToNext()) c.setPhoto(cursor.getBlob(0)); + // nick name (max. 1) + cursor = providerClient.query(dataURI(), new String[] { Nickname.NAME }, + Nickname.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", + new String[] { String.valueOf(c.getLocalID()), Nickname.CONTENT_ITEM_TYPE }, null); + if (cursor != null && cursor.moveToNext()) + c.setNickName(cursor.getString(0)); + + // note (max. 1) + cursor = providerClient.query(dataURI(), new String[] { Note.NOTE }, + Website.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", + new String[] { String.valueOf(c.getLocalID()), Note.CONTENT_ITEM_TYPE }, null); + if (cursor != null && cursor.moveToNext()) + c.setNote(cursor.getString(0)); + + // postal addresses + cursor = providerClient.query(dataURI(), new String[] { + /* 0 */ StructuredPostal.FORMATTED_ADDRESS, StructuredPostal.TYPE, StructuredPostal.LABEL, + /* 3 */ StructuredPostal.STREET, StructuredPostal.POBOX, StructuredPostal.NEIGHBORHOOD, + /* 6 */ StructuredPostal.CITY, StructuredPostal.REGION, StructuredPostal.POSTCODE, + /* 9 */ StructuredPostal.COUNTRY + }, StructuredPostal.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", + new String[] { String.valueOf(c.getLocalID()), StructuredPostal.CONTENT_ITEM_TYPE }, null); + while (cursor != null && cursor.moveToNext()) { + Address address = new Address(); + + address.setLabel(cursor.getString(0)); + switch (cursor.getInt(1)) { + case StructuredPostal.TYPE_HOME: + address.addType(AddressType.HOME); + break; + case StructuredPostal.TYPE_WORK: + address.addType(AddressType.WORK); + break; + case StructuredPostal.TYPE_CUSTOM: + address.addType(AddressType.get(labelToXName(cursor.getString(2)))); + break; + } + address.setStreetAddress(cursor.getString(3)); + address.setPoBox(cursor.getString(4)); + address.setExtendedAddress(cursor.getString(5)); + address.setLocality(cursor.getString(6)); + address.setRegion(cursor.getString(7)); + address.setPostalCode(cursor.getString(8)); + address.setCountry(cursor.getString(9)); + c.getAddresses().add(address); + } + + // URL + cursor = providerClient.query(dataURI(), new String[] { Website.URL }, + Website.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", + new String[] { String.valueOf(c.getLocalID()), Website.CONTENT_ITEM_TYPE }, null); + if (cursor != null && cursor.moveToNext()) + c.setURL(cursor.getString(0)); + // events (birthday) cursor = providerClient.query(dataURI(), new String[] { CommonDataKinds.Event.TYPE, CommonDataKinds.Event.START_DATE }, Photo.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", new String[] { String.valueOf(c.getLocalID()), CommonDataKinds.Event.CONTENT_ITEM_TYPE }, null); while (cursor != null && cursor.moveToNext()) - try { - switch (cursor.getInt(0)) { - case CommonDataKinds.Event.TYPE_BIRTHDAY: - c.setBirthDay(new Date(cursor.getString(1))); - break; - } - } catch (ParseException e) { - Log.w(TAG, "Ignoring event with unknown date format: " + cursor.getString(0)); + switch (cursor.getInt(0)) { + case CommonDataKinds.Event.TYPE_ANNIVERSARY: + c.setAnniversary(new Anniversary(cursor.getString(1))); + break; + case CommonDataKinds.Event.TYPE_BIRTHDAY: + c.setBirthDay(new Birthday(cursor.getString(1))); + break; } - // URLs - cursor = providerClient.query(dataURI(), new String[] { Website.URL }, - Website.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), Website.CONTENT_ITEM_TYPE }, null); - while (cursor != null && cursor.moveToNext()) - try { - c.addURL(new URI(cursor.getString(0))); - } catch (URISyntaxException ex) { - Log.w(TAG, "Found invalid contact URL in database: " + ex.toString()); - } - - // notes - cursor = providerClient.query(dataURI(), new String[] { Note.NOTE }, - Website.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), Note.CONTENT_ITEM_TYPE }, null); - while (cursor != null && cursor.moveToNext()) - c.addNote(new String(cursor.getString(0))); - c.populated = true; return; } @@ -321,13 +357,32 @@ public class LocalAddressBook extends LocalCollection { return ".vcf"; } + @Override + protected String randomUID() { + return UUID.randomUUID().toString(); + } + protected Uri dataURI() { return syncAdapterURI(Data.CONTENT_URI); } + protected String labelToXName(String label) { + String xName = "X-" + label.replaceAll(" ","_").replaceAll("[^\\p{L}\\p{Nd}\\-_]", "").toUpperCase(Locale.US); + return xName; + } + private Builder newDataInsertBuilder(long raw_contact_id, Integer backrefIdx) { return newDataInsertBuilder(dataURI(), Data.RAW_CONTACT_ID, raw_contact_id, backrefIdx); } + + protected String xNameToLabel(String xname) { + // "x-my_property" + // 1. ensure lower case -> "x-my_property" + // 2. remove x- from beginning -> "my_property" + // 3. replace "_" by " " -> "my property" + // 4. capitalize -> "My Property" + return WordUtils.capitalize(StringUtils.removeStart(xname.toLowerCase(Locale.US), "x-").replaceAll("_"," ")); + } /* content builder methods */ @@ -352,30 +407,42 @@ public class LocalAddressBook extends LocalCollection { pendingOperations.add(buildStructuredName(newDataInsertBuilder(localID, backrefIdx), contact).build()); - if (contact.getNickNames() != null) - for (String nick : contact.getNickNames()) - pendingOperations.add(buildNickName(newDataInsertBuilder(localID, backrefIdx), nick).build()); - - for (net.fortuna.ical4j.vcard.property.Email email : contact.getEmails()) - pendingOperations.add(buildEmail(newDataInsertBuilder(localID, backrefIdx), email).build()); - for (Telephone number : contact.getPhoneNumbers()) pendingOperations.add(buildPhoneNumber(newDataInsertBuilder(localID, backrefIdx), number).build()); - for (Address address : contact.getAddresses()) - pendingOperations.add(buildAddress(newDataInsertBuilder(localID, backrefIdx), address).build()); + for (ezvcard.property.Email email : contact.getEmails()) + pendingOperations.add(buildEmail(newDataInsertBuilder(localID, backrefIdx), email).build()); if (contact.getPhoto() != null) pendingOperations.add(buildPhoto(newDataInsertBuilder(localID, backrefIdx), contact.getPhoto()).build()); + // TODO organization + // TODO im + + if (contact.getNickName() != null) + pendingOperations.add(buildNickName(newDataInsertBuilder(localID, backrefIdx), contact.getNickName()).build()); + + if (contact.getNote() != null) + pendingOperations.add(buildNote(newDataInsertBuilder(localID, backrefIdx), contact.getNote()).build()); + + for (Address address : contact.getAddresses()) + pendingOperations.add(buildAddress(newDataInsertBuilder(localID, backrefIdx), address).build()); + + // TODO group membership + + if (contact.getURL() != null) + pendingOperations.add(buildURL(newDataInsertBuilder(localID, backrefIdx), contact.getURL()).build()); + + // events + if (contact.getAnniversary() != null) + pendingOperations.add(buildEvent(newDataInsertBuilder(localID, backrefIdx), + contact.getAnniversary(), CommonDataKinds.Event.TYPE_ANNIVERSARY).build()); if (contact.getBirthDay() != null) - pendingOperations.add(buildBirthDay(newDataInsertBuilder(localID, backrefIdx), contact.getBirthDay()).build()); + pendingOperations.add(buildEvent(newDataInsertBuilder(localID, backrefIdx), + contact.getBirthDay(), CommonDataKinds.Event.TYPE_BIRTHDAY).build()); - for (URI uri : contact.getURLs()) - pendingOperations.add(buildURL(newDataInsertBuilder(localID, backrefIdx), uri).build()); - - for (String note : contact.getNotes()) - pendingOperations.add(buildNote(newDataInsertBuilder(localID, backrefIdx), note).build()); + // TODO relation + // TODO SIP address } @Override @@ -400,139 +467,103 @@ public class LocalAddressBook extends LocalCollection { .withValue(StructuredName.PHONETIC_FAMILY_NAME, contact.getPhoneticFamilyName()); } - protected Builder buildNickName(Builder builder, String nickName) { - return builder - .withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE) - .withValue(Nickname.NAME, nickName); - } - - protected Builder buildEmail(Builder builder, net.fortuna.ical4j.vcard.property.Email email) { + protected Builder buildPhoneNumber(Builder builder, Telephone number) { + int typeCode = Phone.TYPE_OTHER; + String typeLabel = null; + Set types = number.getTypes(); + // 1 Android type <-> 2 VCard types: fax, cell, pager + if (types.contains(TelephoneType.FAX)) { + if (types.contains(TelephoneType.HOME)) + typeCode = Phone.TYPE_FAX_HOME; + else if (types.contains(TelephoneType.WORK)) + typeCode = Phone.TYPE_FAX_WORK; + else + typeCode = Phone.TYPE_OTHER_FAX; + } else if (types.contains(TelephoneType.CELL)) { + if (types.contains(TelephoneType.WORK)) + typeCode = Phone.TYPE_WORK_MOBILE; + else + typeCode = Phone.TYPE_MOBILE; + } else if (types.contains(TelephoneType.PAGER)) { + if (types.contains(TelephoneType.WORK)) + typeCode = Phone.TYPE_WORK_PAGER; + else + typeCode = Phone.TYPE_PAGER; + // types with 1:1 translation + } else if (types.contains(TelephoneType.HOME)) { + typeCode = Phone.TYPE_HOME; + } else if (types.contains(TelephoneType.WORK)) { + typeCode = Phone.TYPE_WORK; + } else if (types.contains(Contact.PHONE_TYPE_CALLBACK)) { + typeCode = Phone.TYPE_CALLBACK; + } else if (types.contains(TelephoneType.CAR)) { + typeCode = Phone.TYPE_CAR; + } else if (types.contains(Contact.PHONE_TYPE_COMPANY_MAIN)) { + typeCode = Phone.TYPE_COMPANY_MAIN; + } else if (types.contains(TelephoneType.ISDN)) { + typeCode = Phone.TYPE_ISDN; + } else if (types.contains(TelephoneType.PREF)) { + typeCode = Phone.TYPE_MAIN; + } else if (types.contains(Contact.PHONE_TYPE_RADIO)) { + typeCode = Phone.TYPE_RADIO; + } else if (types.contains(TelephoneType.TEXTPHONE)) { + typeCode = Phone.TYPE_TELEX; + } else if (types.contains(TelephoneType.TEXT)) { + typeCode = Phone.TYPE_TTY_TDD; + } else if (types.contains(Contact.PHONE_TYPE_ASSISTANT)) { + typeCode = Phone.TYPE_ASSISTANT; + } else if (types.contains(Contact.PHONE_TYPE_MMS)) { + typeCode = Phone.TYPE_MMS; + } else if (!types.isEmpty()) { + TelephoneType type = types.iterator().next(); + typeCode = Phone.TYPE_CUSTOM; + typeLabel = xNameToLabel(type.getValue()); + } + builder = builder - .withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE) - .withValue(Email.ADDRESS, email.getValue()); - - int type = 0; - - Type emailType = (Type)email.getParameter(Id.TYPE); - if (emailType == Type.HOME) - type = Email.TYPE_HOME; - else if (emailType == Type.WORK) - type = Email.TYPE_WORK; - - if (type != 0) - builder = builder.withValue(Email.TYPE, type); - + .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE) + .withValue(Phone.NUMBER, number.getText()) + .withValue(Phone.TYPE, typeCode); + if (typeLabel != null) + builder = builder.withValue(Phone.LABEL, typeLabel); return builder; } - protected Builder buildPhoneNumber(Builder builder, Telephone number) { - boolean fax = false, - cell = false, - pager = false, - home = false, - work = false; + protected Builder buildEmail(Builder builder, ezvcard.property.Email email) { + int typeCode = Email.TYPE_OTHER; + String typeLabel = null; - Type phoneType = (Type)number.getParameter(Id.TYPE); - if (phoneType != null) - for (String strType : phoneType.getTypes()) - if (strType.equalsIgnoreCase("fax")) - fax = true; - else if (strType.equalsIgnoreCase("cell")) - cell = true; - else if (strType.equalsIgnoreCase("pager")) - pager = true; - else if (strType.equalsIgnoreCase("home")) - home = true; - else if (strType.equalsIgnoreCase("work")) - work = true; + for (EmailType type : email.getTypes()) + if (type == EmailType.HOME) + typeCode = Email.TYPE_HOME; + else if (type == EmailType.WORK) + typeCode = Email.TYPE_WORK; + else if (type == Contact.EMAIL_TYPE_MOBILE) + typeCode = Email.TYPE_MOBILE; + else { + typeCode = Email.TYPE_CUSTOM; + typeLabel = xNameToLabel(type.getValue()); + } - int type = Phone.TYPE_OTHER; - if (fax) { - if (home) - type = Phone.TYPE_FAX_HOME; - else if (work) - type = Phone.TYPE_FAX_WORK; - else - type = Phone.TYPE_OTHER_FAX; - - } else if (cell) { - if (work) - type = Phone.TYPE_WORK_MOBILE; - else - type = Phone.TYPE_MOBILE; - - } else if (pager) { - if (work) - type = Phone.TYPE_WORK_PAGER; - else - type = Phone.TYPE_PAGER; - - } else if (home) { - type = Phone.TYPE_HOME; - } else if (work) { - type = Phone.TYPE_WORK; - } - - return builder - .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE) - .withValue(Phone.NUMBER, number.getValue()) - .withValue(Phone.TYPE, type); + builder = builder + .withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE) + .withValue(Email.ADDRESS, email.getValue()) + .withValue(Email.TYPE, typeCode); + if (typeLabel != null) + builder = builder.withValue(Email.LABEL, typeLabel); + return builder; } - protected Builder buildAddress(Builder builder, Address address) { - /* street po.box (extended) - * region - * postal code city - * country - */ - String lineStreet = StringUtils.join(new String[] { address.getStreet(), address.getPoBox(), address.getExtended() }, " "), - lineLocality = StringUtils.join(new String[] { address.getPostcode(), address.getLocality() }, " "); - - List lines = new LinkedList(); - if (lineStreet != null) - lines.add(lineStreet); - if (address.getRegion() != null && !address.getRegion().isEmpty()) - lines.add(address.getRegion()); - if (lineLocality != null) - lines.add(lineLocality); - - int typeCode = StructuredPostal.TYPE_OTHER; - Type type = (Type)address.getParameter(Id.TYPE); - if (type == Type.HOME) - typeCode = StructuredPostal.TYPE_HOME; - else if (type == Type.WORK) - typeCode = StructuredPostal.TYPE_WORK; - - return builder - .withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE) - .withValue(StructuredPostal.FORMATTED_ADDRESS, StringUtils.join(lines, "\n")) - .withValue(StructuredPostal.TYPE, typeCode) - .withValue(StructuredPostal.STREET, address.getStreet()) - .withValue(StructuredPostal.POBOX, address.getPoBox()) - .withValue(StructuredPostal.NEIGHBORHOOD, address.getExtended()) - .withValue(StructuredPostal.CITY, address.getLocality()) - .withValue(StructuredPostal.REGION, address.getRegion()) - .withValue(StructuredPostal.POSTCODE, address.getPostcode()) - .withValue(StructuredPostal.COUNTRY, address.getCountry()); - } - protected Builder buildPhoto(Builder builder, byte[] photo) { return builder .withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE) .withValue(Photo.PHOTO, photo); } - protected Builder buildBirthDay(Builder builder, Date birthDay) { + protected Builder buildNickName(Builder builder, String nickName) { return builder - .withValue(Data.MIMETYPE, CommonDataKinds.Event.CONTENT_ITEM_TYPE) - .withValue(CommonDataKinds.Event.TYPE, CommonDataKinds.Event.TYPE_BIRTHDAY) - .withValue(CommonDataKinds.Event.START_DATE, birthDay.toString()); - } - - protected Builder buildURL(Builder builder, URI uri) { - return builder - .withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE) - .withValue(Website.URL, uri.toString()); + .withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE) + .withValue(Nickname.NAME, nickName); } protected Builder buildNote(Builder builder, String note) { @@ -540,4 +571,67 @@ public class LocalAddressBook extends LocalCollection { .withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE) .withValue(Note.NOTE, note); } + + protected Builder buildAddress(Builder builder, Address address) { + /* street po.box (extended) + * region + * postal code city + * country + */ + String formattedAddress = address.getLabel(); + if (formattedAddress == null || formattedAddress.isEmpty()) { + String lineStreet = StringUtils.join(new String[] { address.getStreetAddress(), address.getPoBox(), address.getExtendedAddress() }, " "), + lineLocality = StringUtils.join(new String[] { address.getPostalCode(), address.getLocality() }, " "); + + List lines = new LinkedList(); + if (lineStreet != null) + lines.add(lineStreet); + if (address.getRegion() != null && !address.getRegion().isEmpty()) + lines.add(address.getRegion()); + if (lineLocality != null) + lines.add(lineLocality); + + formattedAddress = StringUtils.join(lines, "\n"); + } + + int typeCode = StructuredPostal.TYPE_OTHER; + String typeLabel = null; + for (AddressType type : address.getTypes()) + if (type == AddressType.HOME) + typeCode = StructuredPostal.TYPE_HOME; + else if (type == AddressType.WORK) + typeCode = StructuredPostal.TYPE_WORK; + else { + typeCode = StructuredPostal.TYPE_CUSTOM; + typeLabel = xNameToLabel(type.getValue()); + } + + builder = builder + .withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE) + .withValue(StructuredPostal.FORMATTED_ADDRESS, formattedAddress) + .withValue(StructuredPostal.TYPE, typeCode) + .withValue(StructuredPostal.STREET, address.getStreetAddress()) + .withValue(StructuredPostal.POBOX, address.getPoBox()) + .withValue(StructuredPostal.NEIGHBORHOOD, address.getExtendedAddress()) + .withValue(StructuredPostal.CITY, address.getLocality()) + .withValue(StructuredPostal.REGION, address.getRegion()) + .withValue(StructuredPostal.POSTCODE, address.getPostalCode()) + .withValue(StructuredPostal.COUNTRY, address.getCountry()); + if (typeLabel != null) + builder = builder.withValue(StructuredPostal.LABEL, typeLabel); + return builder; + } + + protected Builder buildURL(Builder builder, String url) { + return builder + .withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE) + .withValue(Website.URL, url); + } + + protected Builder buildEvent(Builder builder, DateOrTimeProperty date, int type) { + return builder + .withValue(Data.MIMETYPE, CommonDataKinds.Event.CONTENT_ITEM_TYPE) + .withValue(CommonDataKinds.Event.TYPE, type) + .withValue(CommonDataKinds.Event.START_DATE, date.getText()); + } } diff --git a/src/at/bitfire/davdroid/resource/LocalCalendar.java b/src/at/bitfire/davdroid/resource/LocalCalendar.java index 05096e44..f8df4c80 100644 --- a/src/at/bitfire/davdroid/resource/LocalCalendar.java +++ b/src/at/bitfire/davdroid/resource/LocalCalendar.java @@ -60,6 +60,7 @@ import android.provider.CalendarContract.Events; import android.provider.CalendarContract.Reminders; import android.provider.ContactsContract; import android.util.Log; +import at.bitfire.davdroid.syncadapter.DavSyncAdapter; import at.bitfire.davdroid.syncadapter.ServerInfo; public class LocalCalendar extends LocalCollection { @@ -404,6 +405,11 @@ public class LocalCalendar extends LocalCollection { return ".ics"; } + @Override + protected String randomUID() { + return DavSyncAdapter.generateUID(); + } + protected static Uri calendarsURI(Account account) { return Calendars.CONTENT_URI.buildUpon().appendQueryParameter(Calendars.ACCOUNT_NAME, account.name) .appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type) diff --git a/src/at/bitfire/davdroid/resource/LocalCollection.java b/src/at/bitfire/davdroid/resource/LocalCollection.java index 6ba23b0d..d93c171b 100644 --- a/src/at/bitfire/davdroid/resource/LocalCollection.java +++ b/src/at/bitfire/davdroid/resource/LocalCollection.java @@ -22,7 +22,13 @@ import android.net.Uri; import android.os.RemoteException; import android.provider.CalendarContract; import android.util.Log; -import at.bitfire.davdroid.syncadapter.DavSyncAdapter; + +/* TODO: move Android <-> iCal/VCard code to adapter class for better maintenance / testing + * RemoteCollection<> LocalCollection<> + * RemoteResource <-- ResourceAdapter ----> LocalResource + * - RemoteContact <-- - ContactAdapter --> - LocalContact + * - RemoteEvent <-- - EventAdapter ----> - LocalEvent + */ public abstract class LocalCollection { private static final String TAG = "davdroid.LocalCollection"; @@ -100,12 +106,12 @@ public abstract class LocalCollection { where, null, null); LinkedList fresh = new LinkedList(); while (cursor != null && cursor.moveToNext()) { - String uid = DavSyncAdapter.generateUID(), + String uid = randomUID(), resourceName = uid.replace("@", "_") + fileExtension(); T resource = findById(cursor.getLong(0), resourceName, null, true); resource.setUid(uid); - // new record: set generated resource name in database + // new record: set generated UID + remote file name in database pendingOperations.add(ContentProviderOperation .newUpdate(ContentUris.withAppendedId(entriesURI(), resource.getLocalID())) .withValue(entryColumnRemoteName(), resourceName) @@ -124,9 +130,7 @@ public abstract class LocalCollection { // create/update/delete - public void add(Resource resource) throws ValidationException { - resource.validate(); - + public void add(Resource resource) { int idx = pendingOperations.size(); pendingOperations.add( buildEntry(ContentProviderOperation.newInsert(entriesURI()), resource) @@ -138,7 +142,6 @@ public abstract class LocalCollection { public void updateByRemoteName(Resource remoteResource) throws RemoteException, ValidationException { T localResource = findByRemoteName(remoteResource.getName()); - remoteResource.validate(); pendingOperations.add( buildEntry(ContentProviderOperation.newUpdate(ContentUris.withAppendedId(entriesURI(), localResource.getLocalID())), remoteResource) @@ -178,6 +181,8 @@ public abstract class LocalCollection { protected abstract String fileExtension(); + protected abstract String randomUID(); + protected Uri syncAdapterURI(Uri baseURI) { return baseURI.buildUpon() .appendQueryParameter(entryColumnAccountType(), account.type) diff --git a/src/at/bitfire/davdroid/resource/RemoteCollection.java b/src/at/bitfire/davdroid/resource/RemoteCollection.java index 24c1e2be..d5752658 100644 --- a/src/at/bitfire/davdroid/resource/RemoteCollection.java +++ b/src/at/bitfire/davdroid/resource/RemoteCollection.java @@ -20,6 +20,7 @@ import net.fortuna.ical4j.model.ValidationException; import org.apache.http.HttpException; +import ezvcard.VCardException; import android.util.Log; import at.bitfire.davdroid.webdav.HttpPropfind; import at.bitfire.davdroid.webdav.InvalidDavResponseException; @@ -91,7 +92,9 @@ public abstract class RemoteCollection { } else Log.e(TAG, "Ignoring entity without content"); } catch (ParserException ex) { - Log.e(TAG, "Ignoring unparseable entity in multi-response", ex); + Log.e(TAG, "Ignoring unparseable iCal in multi-response", ex); + } catch (VCardException ex) { + Log.e(TAG, "Ignoring unparseable vCard in multi-response", ex); } } else @@ -99,7 +102,9 @@ public abstract class RemoteCollection { return foundResources.toArray(new Resource[0]); } catch (ParserException ex) { - Log.w(TAG, "Couldn't parse single multi-get entity", ex); + Log.e(TAG, "Couldn't parse iCal from GET", ex); + } catch (VCardException ex) { + Log.e(TAG, "Couldn't parse vCard from GET", ex); } return new Resource[0]; @@ -108,7 +113,7 @@ public abstract class RemoteCollection { /* internal member operations */ - public Resource get(Resource resources) throws IOException, HttpException, ParserException { + public Resource get(Resource resources) throws IOException, HttpException, ParserException, VCardException { WebDavResource member = new WebDavResource(collection, resources.getName()); member.get(); resources.parseEntity(member.getContent()); diff --git a/src/at/bitfire/davdroid/resource/Resource.java b/src/at/bitfire/davdroid/resource/Resource.java index c9192f5f..e4b839ff 100644 --- a/src/at/bitfire/davdroid/resource/Resource.java +++ b/src/at/bitfire/davdroid/resource/Resource.java @@ -15,6 +15,7 @@ import lombok.Setter; import lombok.ToString; import net.fortuna.ical4j.data.ParserException; import net.fortuna.ical4j.model.ValidationException; +import ezvcard.VCardException; @ToString public abstract class Resource { @@ -35,11 +36,6 @@ public abstract class Resource { this.localID = localID; } - - public abstract void parseEntity(InputStream entity) throws IOException, ParserException; + public abstract void parseEntity(InputStream entity) throws IOException, ParserException, VCardException; public abstract String toEntity() throws IOException, ValidationException; - - public void validate() throws ValidationException { - if (name == null) throw new ValidationException("File name must be set"); - } } diff --git a/src/at/bitfire/davdroid/syncadapter/SyncManager.java b/src/at/bitfire/davdroid/syncadapter/SyncManager.java index 1b8de1e2..a48c7b49 100644 --- a/src/at/bitfire/davdroid/syncadapter/SyncManager.java +++ b/src/at/bitfire/davdroid/syncadapter/SyncManager.java @@ -140,11 +140,7 @@ public class SyncManager { if (!resourcesToAdd.isEmpty()) { for (Resource res : dav.multiGet(resourcesToAdd.toArray(new Resource[0]))) { Log.i(TAG, "Adding " + res.getName()); - try { - local.add(res); - } catch (ValidationException ex) { - Log.w(TAG, "Ignoring invalid remote resource: " + res.getName(), ex); - } + local.add(res); if (++syncResult.stats.numInserts % MAX_UPDATES_BEFORE_COMMIT == 0) // avoid TransactionTooLargeException local.commit(); diff --git a/test/src/at/bitfire/davdroid/test/ContactTest.java b/test/src/at/bitfire/davdroid/test/ContactTest.java index 8139338b..f006ae9c 100644 --- a/test/src/at/bitfire/davdroid/test/ContactTest.java +++ b/test/src/at/bitfire/davdroid/test/ContactTest.java @@ -36,7 +36,7 @@ public class ContactTest extends InstrumentationTestCase { Assert.assertEquals("Gump", c.getFamilyName()); Assert.assertEquals(2, c.getPhoneNumbers().size()); - Assert.assertEquals("(111) 555-1212", c.getPhoneNumbers().get(0).getValue()); + Assert.assertEquals("(111) 555-1212", c.getPhoneNumbers().get(0).getText()); Assert.assertEquals(1, c.getEmails().size()); Assert.assertEquals("forrestgump@example.com", c.getEmails().get(0).getValue());