1
0
mirror of https://github.com/etesync/android synced 2024-11-22 16:08:13 +00:00

VCard 3 support

* switch to ez-vcard instead if iCal4j-vcard
* generate VCard 3 by default (fixes #67)
* support for more phone number/email/address types, including "custom" types
* limit to 1 URL and 1 note (Android limit)
* support for anniversaries
This commit is contained in:
rfc2822 2013-12-04 14:04:18 +01:00
parent 725c815e32
commit 87b71b0b38
15 changed files with 523 additions and 643 deletions

BIN
libs/ez-vcard-0.9.0.jar Normal file

Binary file not shown.

Binary file not shown.

View File

@ -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<Property> {
@Override
public PhoneticFirstName createProperty(List<Parameter> params, String value) {
return new PhoneticFirstName(value);
}
@Override
public PhoneticFirstName createProperty(Group group, List<Parameter> params, String value) {
return new PhoneticFirstName(value);
}
}
}

View File

@ -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<Property> {
@Override
public PhoneticLastName createProperty(List<Parameter> params, String value) {
return new PhoneticLastName(value);
}
@Override
public PhoneticLastName createProperty(Group group, List<Parameter> params, String value) {
return new PhoneticLastName(value);
}
}
}

View File

@ -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<Property> {
@Override
public PhoneticMiddleName createProperty(List<Parameter> params, String value) {
return new PhoneticMiddleName(value);
}
@Override
public PhoneticMiddleName createProperty(Group group, List<Parameter> params, String value) {
return new PhoneticMiddleName(value);
}
}
}

View File

@ -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<Property> {
@Override
public Starred createProperty(List<Parameter> params, String value) {
return new Starred(value);
}
@Override
public Starred createProperty(Group group, List<Parameter> params, String value) {
return new Starred(value);
}
}
}

View File

@ -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<Email> emails = new LinkedList<Email>();
public void addEmail(Email email) {
emails.add(email);
}
@Getter private List<Telephone> phoneNumbers = new LinkedList<Telephone>();
public void addPhoneNumber(Telephone number) {
phoneNumbers.add(number);
}
@Getter private List<Address> addresses = new LinkedList<Address>();
public void addAddress(Address address) {
addresses.add(address);
}
@Getter private List<URI> URLs = new LinkedList<URI>();
public void addURL(URI url) {
URLs.add(url);
}
@Getter private List<String> notes = new LinkedList<String>();
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<Property> 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<Photo> 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<String> notes = new LinkedList<String>();
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();
}
}

View File

@ -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();

View File

@ -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<Contact> {
@ -153,102 +155,101 @@ public class LocalAddressBook extends LocalCollection<Contact> {
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<String> nickNames = new LinkedList<String>();
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<String> types = new LinkedList<String>();
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<Contact> {
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<Contact> {
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<Contact> {
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<Contact> {
.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<TelephoneType> 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<String> lines = new LinkedList<String>();
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<Contact> {
.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<String> lines = new LinkedList<String>();
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());
}
}

View File

@ -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<Event> {
@ -404,6 +405,11 @@ public class LocalCalendar extends LocalCollection<Event> {
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)

View File

@ -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<T extends Resource> {
private static final String TAG = "davdroid.LocalCollection";
@ -100,12 +106,12 @@ public abstract class LocalCollection<T extends Resource> {
where, null, null);
LinkedList<T> fresh = new LinkedList<T>();
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<T extends Resource> {
// 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<T extends Resource> {
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<T extends Resource> {
protected abstract String fileExtension();
protected abstract String randomUID();
protected Uri syncAdapterURI(Uri baseURI) {
return baseURI.buildUpon()
.appendQueryParameter(entryColumnAccountType(), account.type)

View File

@ -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<T extends Resource> {
} 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<T extends Resource> {
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<T extends Resource> {
/* 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());

View File

@ -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");
}
}

View File

@ -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();

View File

@ -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());