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:
parent
725c815e32
commit
87b71b0b38
BIN
libs/ez-vcard-0.9.0.jar
Normal file
BIN
libs/ez-vcard-0.9.0.jar
Normal file
Binary file not shown.
Binary file not shown.
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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());
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user