Add more vCard fields

* support for vCard organization (company / job title)
* support for vCard IMPP addresses (closes #105)
* refactoring
pull/2/head
rfc2822 11 years ago
parent 87b71b0b38
commit 8c79c64d75

@ -20,21 +20,35 @@
maxOccurs="1" />
<DataKind kind="phone">
<Type type="fax_home" />
<Type type="fax_work" />
<Type type="home" />
<Type type="mobile" />
<Type type="other_fax" />
<Type type="pager" />
<Type type="work" />
<Type type="work_mobile" />
<Type type="work_pager" />
</DataKind>
<Type type="mobile" />
<Type type="home" />
<Type type="work" />
<Type type="fax_work" />
<Type type="fax_home" />
<Type type="pager" />
<Type type="other" />
<Type type="custom"/>
<Type type="callback" />
<Type type="car" />
<Type type="company_main" />
<Type type="isdn" />
<Type type="main" />
<Type type="other_fax" />
<Type type="radio" />
<Type type="telex" />
<Type type="tty_tdd" />
<Type type="work_mobile"/>
<Type type="work_pager" />
<Type type="assistant" />
<Type type="mms" />
</DataKind>
<DataKind kind="email">
<Type type="home" />
<Type type="work" />
<Type type="other" />
<Type type="home" />
<Type type="work" />
<Type type="other" />
<Type type="mobile" />
<Type type="custom" />
</DataKind>
<DataKind kind="postal" needsStructured="true" >
@ -56,10 +70,8 @@
<DataKind
dateWithTime="false"
kind="event">
<Type
maxOccurs="1"
type="birthday"
yearOptional="false" />
<Type maxOccurs="1" type="birthday" yearOptional="false" />
<Type type="anniversary" />
</DataKind>
</EditSchema>
</ContactsAccountType>

@ -35,11 +35,14 @@ import ezvcard.property.Anniversary;
import ezvcard.property.Birthday;
import ezvcard.property.Email;
import ezvcard.property.FormattedName;
import ezvcard.property.Impp;
import ezvcard.property.Nickname;
import ezvcard.property.Note;
import ezvcard.property.Organization;
import ezvcard.property.Photo;
import ezvcard.property.RawProperty;
import ezvcard.property.Revision;
import ezvcard.property.Role;
import ezvcard.property.StructuredName;
import ezvcard.property.Telephone;
import ezvcard.property.Uid;
@ -71,12 +74,20 @@ public class Contact extends Resource {
@Getter @Setter private String prefix, givenName, middleName, familyName, suffix;
@Getter @Setter private String phoneticGivenName, phoneticMiddleName, phoneticFamilyName;
@Getter @Setter private String note, URL;
@Getter @Setter private String organization, role;
@Getter @Setter private byte[] photo;
@Getter @Setter private Anniversary anniversary;
@Getter @Setter private Birthday birthDay;
@Getter private List<Email> emails = new LinkedList<Email>();
@Getter private List<Telephone> phoneNumbers = new LinkedList<Telephone>();
@Getter private List<Address> addresses = new LinkedList<Address>();
@Getter private List<Impp> impps = new LinkedList<Impp>();
/* instance methods */
public Contact(String name, String ETag) {
super(name, ETag);
@ -87,12 +98,11 @@ public class Contact extends Resource {
this.localID = localID;
}
/* multiple-record fields */
@Getter private List<Email> emails = new LinkedList<Email>();
@Getter private List<Telephone> phoneNumbers = new LinkedList<Telephone>();
@Getter private List<Address> addresses = new LinkedList<Address>();
@Override
public void initialize() {
uid = UUID.randomUUID().toString();
name = uid + ".vcf";
}
/* VCard methods */
@ -155,6 +165,17 @@ public class Contact extends Resource {
}
}
if (vcard.getOrganization() != null) {
List<String> organizations = vcard.getOrganization().getValues();
if (!organizations.isEmpty())
organization = organizations.get(0);
}
List<Role> roles = vcard.getRoles();
if (!roles.isEmpty())
role = roles.get(0).getValue();
impps = vcard.getImpps();
Nickname nicknames = vcard.getNickname();
if (nicknames != null && nicknames.getValues() != null)
nickName = StringUtils.join(nicknames.getValues(), ", ");
@ -221,17 +242,28 @@ public class Contact extends Resource {
if (photo != null)
vcard.addPhoto(new Photo(photo, ImageType.JPEG));
if (organization != null) {
Organization org = new Organization();
org.addValue(organization);
vcard.addOrganization(org);
}
if (role != null)
vcard.addRole(role);
for (Impp impp : impps)
vcard.addImpp(impp);
if (nickName != null)
if (nickName != null && !nickName.isEmpty())
vcard.setNickname(nickName);
if (note != null)
if (note != null && !note.isEmpty())
vcard.addNote(note);
for (Address address : addresses)
vcard.addAddress(address);
if (URL != null)
if (URL != null && !URL.isEmpty())
vcard.addUrl(URL);
if (anniversary != null)

@ -101,6 +101,12 @@ public class Event extends Resource {
this(name, ETag);
this.localID = localID;
}
@Override
public void initialize() {
uid = DavSyncAdapter.generateUID();
name = uid.replace("@", "_") + ".ics";
}
@Override

@ -7,11 +7,11 @@
******************************************************************************/
package at.bitfire.davdroid.resource;
import java.text.SimpleDateFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
@ -28,8 +28,10 @@ import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
@ -40,16 +42,18 @@ import android.provider.ContactsContract.RawContacts;
import at.bitfire.davdroid.Constants;
import ezvcard.parameter.AddressType;
import ezvcard.parameter.EmailType;
import ezvcard.parameter.ImppType;
import ezvcard.parameter.TelephoneType;
import ezvcard.property.Address;
import ezvcard.property.Anniversary;
import ezvcard.property.Birthday;
import ezvcard.property.DateOrTimeProperty;
import ezvcard.property.Impp;
import ezvcard.property.Telephone;
public class LocalAddressBook extends LocalCollection<Contact> {
private final static String TAG = "davdroid.LocalAddressBook";
//private final static String TAG = "davdroid.LocalAddressBook";
protected AccountManager accountManager;
@ -101,25 +105,6 @@ public class LocalAddressBook extends LocalCollection<Contact> {
/* content provider (= database) querying */
@Override
public Contact findById(long localID, String remoteName, String eTag, boolean populate) throws RemoteException {
Contact c = new Contact(localID, remoteName, eTag);
if (populate)
populate(c);
return c;
}
@Override
public Contact findByRemoteName(String remoteName) throws RemoteException {
Cursor cursor = providerClient.query(entriesURI(),
new String[] { RawContacts._ID, entryColumnRemoteName(), entryColumnETag() },
entryColumnRemoteName() + "=?", new String[] { remoteName }, null);
if (cursor != null && cursor.moveToNext())
return new Contact(cursor.getLong(0), cursor.getString(1), cursor.getString(2));
else
return null;
}
@Override
public void populate(Resource res) throws RemoteException {
@ -254,11 +239,77 @@ public class LocalAddressBook extends LocalCollection<Contact> {
// photo
cursor = providerClient.query(dataURI(), new String[] { Photo.PHOTO },
Photo.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?",
new String[] { String.valueOf(c.getLocalID()), Photo.CONTENT_ITEM_TYPE }, null);
Photo.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?",
new String[] { String.valueOf(c.getLocalID()), Photo.CONTENT_ITEM_TYPE }, null);
if (cursor != null && cursor.moveToNext())
c.setPhoto(cursor.getBlob(0));
// organization
cursor = providerClient.query(dataURI(), new String[] { Organization.COMPANY, Organization.TITLE },
Photo.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?",
new String[] { String.valueOf(c.getLocalID()), Organization.CONTENT_ITEM_TYPE }, null);
if (cursor != null && cursor.moveToNext()) {
c.setOrganization(cursor.getString(0));
c.setRole(cursor.getString(1));
}
// IMPPs
cursor = providerClient.query(dataURI(), new String[] { Im.DATA, Im.TYPE, Im.LABEL, Im.PROTOCOL, Im.CUSTOM_PROTOCOL },
Photo.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?",
new String[] { String.valueOf(c.getLocalID()), Im.CONTENT_ITEM_TYPE }, null);
while (cursor != null && cursor.moveToNext()) {
String handle = cursor.getString(0);
Impp impp = null;
switch (cursor.getInt(3)) {
case Im.PROTOCOL_AIM:
impp = Impp.aim(handle);
break;
case Im.PROTOCOL_MSN:
impp = Impp.msn(handle);
break;
case Im.PROTOCOL_YAHOO:
impp = Impp.yahoo(handle);
break;
case Im.PROTOCOL_SKYPE:
impp = Impp.skype(handle);
break;
case Im.PROTOCOL_QQ:
impp = new Impp("qq", handle);
break;
case Im.PROTOCOL_GOOGLE_TALK:
impp = new Impp("google-talk", handle);
break;
case Im.PROTOCOL_ICQ:
impp = Impp.icq(handle);
break;
case Im.PROTOCOL_JABBER:
impp = Impp.xmpp(handle);
break;
case Im.PROTOCOL_NETMEETING:
impp = new Impp("netmeeting", handle);
break;
case Im.PROTOCOL_CUSTOM:
impp = new Impp(cursor.getString(4), handle);
break;
}
if (impp != null) {
switch (cursor.getInt(1)) {
case Im.TYPE_HOME:
impp.addType(ImppType.HOME);
break;
case Im.TYPE_WORK:
impp.addType(ImppType.WORK);
break;
case Im.TYPE_CUSTOM:
impp.addType(ImppType.get(labelToXName(cursor.getString(2))));
break;
}
c.getImpps().add(impp);
}
}
// nick name (max. 1)
cursor = providerClient.query(dataURI(), new String[] { Nickname.NAME },
Nickname.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?",
@ -348,20 +399,17 @@ public class LocalAddressBook extends LocalCollection<Contact> {
.withYieldAllowed(true)
.build());
}
/* private helper methods */
@Override
protected String fileExtension() {
return ".vcf";
}
/* create/update/delete */
@Override
protected String randomUID() {
return UUID.randomUUID().toString();
public Contact newResource(long localID, String resourceName, String eTag) {
return new Contact(localID, resourceName, eTag);
}
/* private helper methods */
protected Uri dataURI() {
return syncAdapterURI(Data.CONTENT_URI);
}
@ -416,8 +464,11 @@ public class LocalAddressBook extends LocalCollection<Contact> {
if (contact.getPhoto() != null)
pendingOperations.add(buildPhoto(newDataInsertBuilder(localID, backrefIdx), contact.getPhoto()).build());
// TODO organization
// TODO im
if (contact.getOrganization() != null || contact.getRole() != null)
pendingOperations.add(buildOrganization(newDataInsertBuilder(localID, backrefIdx), contact.getOrganization(), contact.getRole()).build());
for (Impp impp : contact.getImpps())
pendingOperations.add(buildIMPP(newDataInsertBuilder(localID, backrefIdx), impp).build());
if (contact.getNickName() != null)
pendingOperations.add(buildNickName(newDataInsertBuilder(localID, backrefIdx), contact.getNickName()).build());
@ -530,7 +581,7 @@ public class LocalAddressBook extends LocalCollection<Contact> {
}
protected Builder buildEmail(Builder builder, ezvcard.property.Email email) {
int typeCode = Email.TYPE_OTHER;
int typeCode = 0;
String typeLabel = null;
for (EmailType type : email.getTypes())
@ -540,10 +591,14 @@ public class LocalAddressBook extends LocalCollection<Contact> {
typeCode = Email.TYPE_WORK;
else if (type == Contact.EMAIL_TYPE_MOBILE)
typeCode = Email.TYPE_MOBILE;
if (typeCode == 0) {
if (email.getTypes().isEmpty())
typeCode = Email.TYPE_OTHER;
else {
typeCode = Email.TYPE_CUSTOM;
typeLabel = xNameToLabel(type.getValue());
typeLabel = xNameToLabel(email.getTypes().iterator().next().getValue());
}
}
builder = builder
.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
@ -560,6 +615,69 @@ public class LocalAddressBook extends LocalCollection<Contact> {
.withValue(Photo.PHOTO, photo);
}
protected Builder buildOrganization(Builder builder, String organization, String role) {
return builder
.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE)
.withValue(Organization.COMPANY, organization)
.withValue(Organization.TITLE, role);
}
protected Builder buildIMPP(Builder builder, Impp impp) {
int typeCode = 0;
String typeLabel = null;
for (ImppType type : impp.getTypes())
if (type == ImppType.HOME)
typeCode = Im.TYPE_HOME;
else if (type == ImppType.WORK || type == ImppType.BUSINESS)
typeCode = Im.TYPE_WORK;
if (typeCode == 0)
if (impp.getTypes().isEmpty())
typeCode = Im.TYPE_OTHER;
else {
typeCode = Im.TYPE_CUSTOM;
typeLabel = xNameToLabel(impp.getTypes().iterator().next().getValue());
}
int protocolCode;
String protocolLabel = null;
if (impp.isAim())
protocolCode = Im.PROTOCOL_AIM;
else if (impp.isMsn())
protocolCode = Im.PROTOCOL_MSN;
else if (impp.isYahoo())
protocolCode = Im.PROTOCOL_YAHOO;
else if (impp.isSkype())
protocolCode = Im.PROTOCOL_SKYPE;
else if (impp.getProtocol().equalsIgnoreCase("qq"))
protocolCode = Im.PROTOCOL_QQ;
else if (impp.getProtocol().equalsIgnoreCase("google-talk"))
protocolCode = Im.PROTOCOL_GOOGLE_TALK;
else if (impp.isIcq())
protocolCode = Im.PROTOCOL_ICQ;
else if (impp.isXmpp())
protocolCode = Im.PROTOCOL_JABBER;
else if (impp.getProtocol().equalsIgnoreCase("netmeeting"))
protocolCode = Im.PROTOCOL_NETMEETING;
else {
String protocol = impp.getProtocol();
if (protocol == null)
protocol = "unknown";
protocolCode = Im.PROTOCOL_CUSTOM;
protocolLabel = protocol;
}
builder = builder
.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE)
.withValue(Im.DATA, impp.getHandle())
.withValue(Im.TYPE, typeCode)
.withValue(Im.PROTOCOL, protocolCode);
if (typeLabel != null)
builder = builder.withValue(Im.LABEL, typeLabel);
if (protocolLabel != null)
builder = builder.withValue(Im.CUSTOM_PROTOCOL, protocolLabel);
return builder;
}
protected Builder buildNickName(Builder builder, String nickName) {
return builder
.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE)
@ -594,16 +712,19 @@ public class LocalAddressBook extends LocalCollection<Contact> {
formattedAddress = StringUtils.join(lines, "\n");
}
int typeCode = StructuredPostal.TYPE_OTHER;
int typeCode = 0;
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;
if (typeCode == 0)
if (address.getTypes().isEmpty())
typeCode = StructuredPostal.TYPE_OTHER;
else {
typeCode = StructuredPostal.TYPE_CUSTOM;
typeLabel = xNameToLabel(type.getValue());
typeLabel = xNameToLabel(address.getTypes().iterator().next().getValue());
}
builder = builder
@ -629,9 +750,10 @@ public class LocalAddressBook extends LocalCollection<Contact> {
}
protected Builder buildEvent(Builder builder, DateOrTimeProperty date, int type) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
return builder
.withValue(Data.MIMETYPE, CommonDataKinds.Event.CONTENT_ITEM_TYPE)
.withValue(CommonDataKinds.Event.TYPE, type)
.withValue(CommonDataKinds.Event.START_DATE, date.getText());
.withValue(CommonDataKinds.Event.START_DATE, formatter.format(date.getDate()));
}
}

@ -60,7 +60,6 @@ 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> {
@ -167,26 +166,6 @@ public class LocalCalendar extends LocalCollection<Event> {
/* content provider (= database) querying */
@Override
public Event findById(long localID, String remoteName, String eTag, boolean populate) throws RemoteException {
Event e = new Event(localID, remoteName, eTag);
if (populate)
populate(e);
return e;
}
@Override
public Event findByRemoteName(String remoteName) throws RemoteException {
Cursor cursor = providerClient.query(entriesURI(),
new String[] { entryColumnID(), entryColumnRemoteName(), entryColumnETag() },
Events.CALENDAR_ID + "=? AND " + entryColumnRemoteName() + "=?",
new String[] { String.valueOf(id), remoteName }, null);
if (cursor != null && cursor.moveToNext())
return new Event(cursor.getLong(0), cursor.getString(1), cursor.getString(2));
else
return null;
}
@Override
public void populate(Resource resource) throws RemoteException {
@ -398,17 +377,14 @@ public class LocalCalendar extends LocalCollection<Event> {
}
/* private helper methods */
/* create/update/delete */
@Override
protected String fileExtension() {
return ".ics";
public Event newResource(long localID, String resourceName, String eTag) {
return new Event(localID, resourceName, eTag);
}
@Override
protected String randomUID() {
return DavSyncAdapter.generateUID();
}
/* private helper methods */
protected static Uri calendarsURI(Account account) {
return Calendars.CONTENT_URI.buildUpon().appendQueryParameter(Calendars.ACCOUNT_NAME, account.name)

@ -80,7 +80,7 @@ public abstract class LocalCollection<T extends Resource> {
where, null, null);
LinkedList<T> dirty = new LinkedList<T>();
while (cursor != null && cursor.moveToNext())
dirty.add(findById(cursor.getLong(0), cursor.getString(1), cursor.getString(2), true));
dirty.add(findById(cursor.getLong(0), true));
return dirty.toArray(new Resource[0]);
}
@ -93,7 +93,7 @@ public abstract class LocalCollection<T extends Resource> {
where, null, null);
LinkedList<T> deleted = new LinkedList<T>();
while (cursor != null && cursor.moveToNext())
deleted.add(findById(cursor.getLong(0), cursor.getString(1), cursor.getString(2), false));
deleted.add(findById(cursor.getLong(0), false));
return deleted.toArray(new Resource[0]);
}
@ -106,15 +106,17 @@ public abstract class LocalCollection<T extends Resource> {
where, null, null);
LinkedList<T> fresh = new LinkedList<T>();
while (cursor != null && cursor.moveToNext()) {
String uid = randomUID(),
resourceName = uid.replace("@", "_") + fileExtension();
T resource = findById(cursor.getLong(0), resourceName, null, true);
resource.setUid(uid);
T resource = findById(cursor.getLong(0), true);
/*String uid = randomUID(),
resourceName = uid.replace("@", "_") + fileExtension();
resource.setUid(uid);*/
resource.initialize();
// new record: set generated UID + remote file name in database
pendingOperations.add(ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(entriesURI(), resource.getLocalID()))
.withValue(entryColumnRemoteName(), resourceName)
.withValue(entryColumnUID(), resource.getUid())
.withValue(entryColumnRemoteName(), resource.getName())
.build());
fresh.add(resource);
@ -122,14 +124,39 @@ public abstract class LocalCollection<T extends Resource> {
return fresh.toArray(new Resource[0]);
}
abstract public T findById(long localID, String resourceName, String eTag, boolean populate) throws RemoteException;
abstract public T findByRemoteName(String name) throws RemoteException;
public T findById(long localID, boolean populate) throws RemoteException {
Cursor cursor = providerClient.query(ContentUris.withAppendedId(entriesURI(), localID),
new String[] { entryColumnRemoteName(), entryColumnETag() }, null, null, null);
if (cursor != null && cursor.moveToNext()) {
T resource = newResource(localID, cursor.getString(0), cursor.getString(1));
if (populate)
populate(resource);
return resource;
} else
return null;
}
public T findByRemoteName(String remoteName, boolean populate) throws RemoteException {
Cursor cursor = providerClient.query(entriesURI(),
new String[] { entryColumnID(), entryColumnRemoteName(), entryColumnETag() },
entryColumnRemoteName() + "=?", new String[] { remoteName }, null);
if (cursor != null && cursor.moveToNext()) {
T resource = newResource(cursor.getLong(0), cursor.getString(1), cursor.getString(2));
if (populate)
populate(resource);
return resource;
} else
return null;
}
public abstract void populate(Resource record) throws RemoteException;
// create/update/delete
abstract public T newResource(long localID, String resourceName, String eTag);
public void add(Resource resource) {
int idx = pendingOperations.size();
pendingOperations.add(
@ -141,7 +168,7 @@ public abstract class LocalCollection<T extends Resource> {
}
public void updateByRemoteName(Resource remoteResource) throws RemoteException, ValidationException {
T localResource = findByRemoteName(remoteResource.getName());
T localResource = findByRemoteName(remoteResource.getName(), false);
pendingOperations.add(
buildEntry(ContentProviderOperation.newUpdate(ContentUris.withAppendedId(entriesURI(), localResource.getLocalID())), remoteResource)
@ -179,10 +206,6 @@ public abstract class LocalCollection<T extends Resource> {
// helpers
protected abstract String fileExtension();
protected abstract String randomUID();
protected Uri syncAdapterURI(Uri baseURI) {
return baseURI.buildUpon()
.appendQueryParameter(entryColumnAccountType(), account.type)

@ -36,6 +36,9 @@ public abstract class Resource {
this.localID = localID;
}
// sets resource name and UID
public abstract void initialize();
public abstract void parseEntity(InputStream entity) throws IOException, ParserException, VCardException;
public abstract String toEntity() throws IOException, ValidationException;
}

@ -128,7 +128,7 @@ public class SyncManager {
return;
for (Resource remoteResource : remoteResources) {
Resource localResource = local.findByRemoteName(remoteResource.getName());
Resource localResource = local.findByRemoteName(remoteResource.getName(), true);
if (localResource == null)
resourcesToAdd.add(remoteResource);
else if (localResource.getETag() == null || !localResource.getETag().equals(remoteResource.getETag()))

Loading…
Cancel
Save