summaryrefslogtreecommitdiffstats
path: root/core/java/android/pim
diff options
context:
space:
mode:
authorDaisuke Miyakawa <dmiyakawa@google.com>2009-10-08 19:57:36 -0700
committerDaisuke Miyakawa <dmiyakawa@google.com>2009-10-08 19:57:36 -0700
commit5a016488da5ff6c1ecdff0b1febd836cc1711531 (patch)
treec17d5060edea4607b46cee52f641cf4e7ae2be4b /core/java/android/pim
parentb1fdb1d33600f0b2f199068254900b240572e472 (diff)
parent5c3e687965a49e4e54196a049337544a6eed61d9 (diff)
downloadframeworks_base-5a016488da5ff6c1ecdff0b1febd836cc1711531.zip
frameworks_base-5a016488da5ff6c1ecdff0b1febd836cc1711531.tar.gz
frameworks_base-5a016488da5ff6c1ecdff0b1febd836cc1711531.tar.bz2
Merge commit 'refs/changes/08/29008/8' of ssh://dmiyakawa@android-git.corp.google.com:29418/platform/frameworks/base into cleanup_import
Conflicts: core/java/android/pim/vcard/ContactStruct.java
Diffstat (limited to 'core/java/android/pim')
-rw-r--r--core/java/android/pim/vcard/ContactStruct.java628
-rw-r--r--core/java/android/pim/vcard/VCardConfig.java16
-rw-r--r--core/java/android/pim/vcard/VCardParser_V21.java48
-rw-r--r--core/java/android/pim/vcard/VCardParser_V30.java23
-rw-r--r--core/java/android/pim/vcard/VCardUtils.java38
5 files changed, 414 insertions, 339 deletions
diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/ContactStruct.java
index 36e5e23..edd8121 100644
--- a/core/java/android/pim/vcard/ContactStruct.java
+++ b/core/java/android/pim/vcard/ContactStruct.java
@@ -38,6 +38,7 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
import android.telephony.PhoneNumberUtils;
+import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.util.Log;
@@ -46,7 +47,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -55,11 +55,11 @@ import java.util.Map;
*/
public class ContactStruct {
private static final String LOG_TAG = "vcard.ContactStruct";
-
+
// Key: the name shown in VCard. e.g. "X-AIM", "X-ICQ"
// Value: the result of {@link Contacts.ContactMethods#encodePredefinedImProtocol}
private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
-
+
static {
sImMap.put(Constants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
sImMap.put(Constants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
@@ -90,7 +90,7 @@ public class ContactStruct {
@Override
public boolean equals(Object obj) {
- if (obj instanceof PhoneData) {
+ if (!(obj instanceof PhoneData)) {
return false;
}
PhoneData phoneData = (PhoneData)obj;
@@ -125,7 +125,7 @@ public class ContactStruct {
@Override
public boolean equals(Object obj) {
- if (obj instanceof EmailData) {
+ if (!(obj instanceof EmailData)) {
return false;
}
EmailData emailData = (EmailData)obj;
@@ -202,7 +202,7 @@ public class ContactStruct {
@Override
public boolean equals(Object obj) {
- if (obj instanceof PostalData) {
+ if (!(obj instanceof PostalData)) {
return false;
}
PostalData postalData = (PostalData)obj;
@@ -256,35 +256,44 @@ public class ContactStruct {
*/
static public class OrganizationData {
public final int type;
- public final String companyName;
- // can be changed in some VCard format.
- public String positionName;
+ // non-final is Intended: we may change the values since this info is separated into
+ // two parts in vCard: "ORG" + "TITLE".
+ public String companyName;
+ public String departmentName;
+ public String titleName;
// isPrimary is changable only when there's no appropriate one existing in
// the original VCard.
public boolean isPrimary;
- public OrganizationData(int type, String companyName, String positionName,
+ public OrganizationData(int type,
+ String companyName,
+ String departmentName,
+ String titleName,
boolean isPrimary) {
this.type = type;
this.companyName = companyName;
- this.positionName = positionName;
+ this.departmentName = departmentName;
+ this.titleName = titleName;
this.isPrimary = isPrimary;
}
@Override
public boolean equals(Object obj) {
- if (obj instanceof OrganizationData) {
+ if (!(obj instanceof OrganizationData)) {
return false;
}
OrganizationData organization = (OrganizationData)obj;
- return (type == organization.type && companyName.equals(organization.companyName) &&
- positionName.equals(organization.positionName) &&
+ return (type == organization.type &&
+ TextUtils.equals(companyName, organization.companyName) &&
+ TextUtils.equals(departmentName, organization.departmentName) &&
+ TextUtils.equals(titleName, organization.titleName) &&
isPrimary == organization.isPrimary);
}
-
+
@Override
public String toString() {
- return String.format("type: %d, company: %s, position: %s, isPrimary: %s",
- type, companyName, positionName, isPrimary);
+ return String.format(
+ "type: %d, company: %s, department: %s, title: %s, isPrimary: %s",
+ type, companyName, departmentName, titleName, isPrimary);
}
}
@@ -304,7 +313,7 @@ public class ContactStruct {
@Override
public boolean equals(Object obj) {
- if (obj instanceof ImData) {
+ if (!(obj instanceof ImData)) {
return false;
}
ImData imData = (ImData)obj;
@@ -327,11 +336,32 @@ public class ContactStruct {
public final int type;
public final String formatName; // used when type is not defined in ContactsContract.
public final byte[] photoBytes;
+ public final boolean isPrimary;
- public PhotoData(int type, String formatName, byte[] photoBytes) {
+ public PhotoData(int type, String formatName, byte[] photoBytes, boolean isPrimary) {
this.type = type;
this.formatName = formatName;
this.photoBytes = photoBytes;
+ this.isPrimary = isPrimary;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PhotoData)) {
+ return false;
+ }
+ PhotoData photoData = (PhotoData)obj;
+ return (type == photoData.type &&
+ (formatName == null ? (photoData.formatName == null) :
+ formatName.equals(photoData.formatName)) &&
+ (Arrays.equals(photoBytes, photoData.photoBytes)) &&
+ (isPrimary == photoData.isPrimary));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("type: %d, format: %s: size: %d, isPrimary: %s",
+ type, formatName, photoBytes.length, isPrimary);
}
}
@@ -421,15 +451,6 @@ public class ContactStruct {
private final int mVCardType;
private final Account mAccount;
- // Each Column of four properties has ISPRIMARY field
- // (See android.provider.Contacts)
- // If false even after the parsing loop, we choose the first entry as a "primary"
- // entry.
- private boolean mPrefIsSet_Address;
- private boolean mPrefIsSet_Phone;
- private boolean mPrefIsSet_Email;
- private boolean mPrefIsSet_Organization;
-
public ContactStruct() {
this(VCardConfig.VCARD_TYPE_V21_GENERIC);
}
@@ -454,12 +475,14 @@ public class ContactStruct {
String phoneticGivenName,
String pheneticFamilyName,
String phoneticMiddleName,
+ List<String> nicknameList,
List<byte[]> photoBytesList,
- List<String> notes,
+ List<String> noteList,
List<PhoneData> phoneList,
List<EmailData> emailList,
List<PostalData> postalList,
List<OrganizationData> organizationList,
+ List<ImData> imList,
List<PhotoData> photoList,
List<String> websiteList) {
this(VCardConfig.VCARD_TYPE_DEFAULT);
@@ -470,159 +493,16 @@ public class ContactStruct {
mPhoneticGivenName = givenName;
mPhoneticFamilyName = familyName;
mPhoneticMiddleName = middleName;
+ mNickNameList = nicknameList;
+ mNoteList = noteList;
mEmailList = emailList;
mPostalList = postalList;
mOrganizationList = organizationList;
+ mImList = imList;
mPhotoList = photoList;
mWebsiteList = websiteList;
}
- // All getter methods should be used carefully, since they may change
- // in the future as of 2009-09-24, on which I cannot be sure this structure
- // is completely consolidated.
- // When we are sure we will no longer change them, we'll be happy to
- // make it complete public (withouth @hide tag)
- //
- // Also note that these getter methods should be used only after
- // all properties being pushed into this object. If not, incorrect
- // value will "be stored in the local cache and" be returned to you.
-
- /**
- * @hide
- */
- public String getFamilyName() {
- return mFamilyName;
- }
-
- /**
- * @hide
- */
- public String getGivenName() {
- return mGivenName;
- }
-
- /**
- * @hide
- */
- public String getMiddleName() {
- return mMiddleName;
- }
-
- /**
- * @hide
- */
- public String getPrefix() {
- return mPrefix;
- }
-
- /**
- * @hide
- */
- public String getSuffix() {
- return mSuffix;
- }
-
- /**
- * @hide
- */
- public String getFullName() {
- return mFullName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticFamilyName() {
- return mPhoneticFamilyName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticGivenName() {
- return mPhoneticGivenName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticMiddleName() {
- return mPhoneticMiddleName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticFullName() {
- return mPhoneticFullName;
- }
-
- /**
- * @hide
- */
- public final List<String> getNickNameList() {
- return mNickNameList;
- }
-
- /**
- * @hide
- */
- public String getDisplayName() {
- if (mDisplayName == null) {
- constructDisplayName();
- }
- return mDisplayName;
- }
-
- /**
- * @hide
- */
- public String getBirthday() {
- return mBirthday;
- }
-
- /**
- * @hide
- */
- public final List<PhotoData> getPhotoList() {
- return mPhotoList;
- }
-
- /**
- * @hide
- */
- public final List<String> getNotes() {
- return mNoteList;
- }
-
- /**
- * @hide
- */
- public final List<PhoneData> getPhoneList() {
- return mPhoneList;
- }
-
- /**
- * @hide
- */
- public final List<EmailData> getEmailList() {
- return mEmailList;
- }
-
- /**
- * @hide
- */
- public final List<PostalData> getPostalList() {
- return mPostalList;
- }
-
- /**
- * @hide
- */
- public final List<OrganizationData> getOrganizationList() {
- return mOrganizationList;
- }
-
/**
* Add a phone info to phoneList.
* @param data phone number
@@ -643,10 +523,24 @@ public class ContactStruct {
}
}
- PhoneData phoneData = new PhoneData(type,
- PhoneNumberUtils.formatNumber(builder.toString()),
- label, isPrimary);
-
+ final String formattedPhoneNumber;
+ {
+ final String rawPhoneNumber = builder.toString();
+ if (VCardConfig.isJapaneseDevice(mVCardType)) {
+ // As of 2009-10-07, there's no formatNumber() which accepts
+ // the second argument and returns String directly.
+ final SpannableStringBuilder tmpBuilder =
+ new SpannableStringBuilder(rawPhoneNumber);
+ PhoneNumberUtils.formatNumber(tmpBuilder, PhoneNumberUtils.FORMAT_JAPAN);
+ formattedPhoneNumber = tmpBuilder.toString();
+ } else {
+ // There's no information available on vCard side. Depend on the default
+ // behavior, which may cause problem in the future when the additional format
+ // rule is supported (e.g. PhoneNumberUtils.FORMAT_KLINGON)
+ formattedPhoneNumber = PhoneNumberUtils.formatNumber(rawPhoneNumber);
+ }
+ }
+ PhoneData phoneData = new PhoneData(type, formattedPhoneNumber, label, isPrimary);
mPhoneList.add(phoneData);
}
@@ -666,19 +560,116 @@ public class ContactStruct {
private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){
if (mPostalList == null) {
- mPostalList = new ArrayList<PostalData>();
+ mPostalList = new ArrayList<PostalData>(0);
}
mPostalList.add(new PostalData(type, propValueList, label, isPrimary));
}
- private void addOrganization(int type, final String companyName,
- final String positionName, boolean isPrimary) {
+ /**
+ * Should be called via {@link #handleOrgValue(int, List, boolean)} or
+ * {@link #handleTitleValue(String)}.
+ */
+ private void addNewOrganization(int type, final String companyName,
+ final String departmentName,
+ final String titleName, boolean isPrimary) {
if (mOrganizationList == null) {
mOrganizationList = new ArrayList<OrganizationData>();
}
- mOrganizationList.add(new OrganizationData(type, companyName, positionName, isPrimary));
+ mOrganizationList.add(new OrganizationData(type, companyName,
+ departmentName, titleName, isPrimary));
}
+
+ private static final List<String> sEmptyList = new ArrayList<String>(0);
+ /**
+ * Set "ORG" related values to the appropriate data. If there's more than one
+ * OrganizationData objects, this input data are attached to the last one which does not
+ * have valid values (not including empty but only null). If there's no
+ * OrganizationData object, a new OrganizationData is created, whose title is set to null.
+ */
+ private void handleOrgValue(final int type, List<String> orgList, boolean isPrimary) {
+ if (orgList == null) {
+ orgList = sEmptyList;
+ }
+ final String companyName;
+ final String departmentName;
+ final int size = orgList.size();
+ switch (size) {
+ case 0: {
+ companyName = "";
+ departmentName = null;
+ break;
+ }
+ case 1: {
+ companyName = orgList.get(0);
+ departmentName = null;
+ break;
+ }
+ default: { // More than 1.
+ companyName = orgList.get(0);
+ // We're not sure which is the correct string for department.
+ // In order to keep all the data, concatinate the rest of elements.
+ StringBuilder builder = new StringBuilder();
+ for (int i = 1; i < size; i++) {
+ if (i > 1) {
+ builder.append(' ');
+ }
+ builder.append(orgList.get(i));
+ }
+ departmentName = builder.toString();
+ }
+ }
+ if (mOrganizationList == null) {
+ // Create new first organization entry, with "null" title which may be
+ // added via handleTitleValue().
+ addNewOrganization(type, companyName, departmentName, null, isPrimary);
+ return;
+ }
+ for (OrganizationData organizationData : mOrganizationList) {
+ // Not use TextUtils.isEmpty() since ORG was set but the elements might be empty.
+ // e.g. "ORG;PREF:;" -> Both companyName and departmentName become empty but not null.
+ if (organizationData.companyName == null &&
+ organizationData.departmentName == null) {
+ // Probably the "TITLE" property comes before the "ORG" property via
+ // handleTitleLine().
+ organizationData.companyName = companyName;
+ organizationData.departmentName = departmentName;
+ organizationData.isPrimary = isPrimary;
+ return;
+ }
+ }
+ // No OrganizatioData is available. Create another one, with "null" title, which may be
+ // added via handleTitleValue().
+ addNewOrganization(type, companyName, departmentName, null, isPrimary);
+ }
+
+ private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK;
+
+ /**
+ * Set "title" value to the appropriate data. If there's more than one
+ * OrganizationData objects, this input is attached to the last one which does not
+ * have valid title value (not including empty but only null). If there's no
+ * OrganizationData object, a new OrganizationData is created, whose company name is
+ * set to null.
+ */
+ private void handleTitleValue(final String title) {
+ if (mOrganizationList == null) {
+ // Create new first organization entry, with "null" other info, which may be
+ // added via handleOrgValue().
+ addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+ return;
+ }
+ for (OrganizationData organizationData : mOrganizationList) {
+ if (organizationData.titleName == null) {
+ organizationData.titleName = title;
+ return;
+ }
+ }
+ // No Organization is available. Create another one, with "null" other info, which may be
+ // added via handleOrgValue().
+ addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+ }
+
private void addIm(int type, String data, String label, boolean isPrimary) {
if (mImList == null) {
mImList = new ArrayList<ImData>();
@@ -693,43 +684,14 @@ public class ContactStruct {
mNoteList.add(note);
}
- private void addPhotoBytes(String formatName, byte[] photoBytes) {
+ private void addPhotoBytes(String formatName, byte[] photoBytes, boolean isPrimary) {
if (mPhotoList == null) {
mPhotoList = new ArrayList<PhotoData>(1);
}
- final PhotoData photoData = new PhotoData(0, null, photoBytes);
+ final PhotoData photoData = new PhotoData(0, null, photoBytes, isPrimary);
mPhotoList.add(photoData);
}
- /**
- * Set "position" value to the appropriate data. If there's more than one
- * OrganizationData objects, the value is set to the last one. If there's no
- * OrganizationData object, a new OrganizationData is created, whose company name is
- * empty.
- *
- * TODO: incomplete logic. fix this:
- *
- * e.g. This assumes ORG comes earlier, but TITLE may come earlier like this, though we do not
- * know how to handle it in general cases...
- * ----
- * TITLE:Software Engineer
- * ORG:Google
- * ----
- */
- private void setPosition(String positionValue) {
- if (mOrganizationList == null) {
- mOrganizationList = new ArrayList<OrganizationData>();
- }
- int size = mOrganizationList.size();
- if (size == 0) {
- addOrganization(ContactsContract.CommonDataKinds.Organization.TYPE_OTHER,
- "", null, false);
- size = 1;
- }
- OrganizationData lastData = mOrganizationList.get(size - 1);
- lastData.positionName = positionValue;
- }
-
@SuppressWarnings("fallthrough")
private void handleNProperty(List<String> elems) {
// Family, Given, Middle, Prefix, Suffix. (1 - 5)
@@ -813,7 +775,14 @@ public class ContactStruct {
} else if (propName.equals("SOUND")) {
Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_X_IRMC_N)) {
- handlePhoneticNameFromSound(propValueList);
+ // As of 2009-10-08, Parser side does not split a property value into separated
+ // values using ';' (in other words, propValueList.size() == 1),
+ // which is correct behavior from the view of vCard 2.1.
+ // But we want it to be separated, so do the separation here.
+ final List<String> phoneticNameList =
+ VCardUtils.constructListFromValue(propValue,
+ VCardConfig.isV30(mVCardType));
+ handlePhoneticNameFromSound(phoneticNameList);
} else {
// Ignore this field since Android cannot understand what it is.
}
@@ -836,9 +805,7 @@ public class ContactStruct {
if (typeCollection != null) {
for (String typeString : typeCollection) {
typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Address) {
- // Only first "PREF" is considered.
- mPrefIsSet_Address = true;
+ if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
isPrimary = true;
} else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
type = StructuredPostal.TYPE_HOME;
@@ -879,9 +846,7 @@ public class ContactStruct {
if (typeCollection != null) {
for (String typeString : typeCollection) {
typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Email) {
- // Only first "PREF" is considered.
- mPrefIsSet_Email = true;
+ if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
isPrimary = true;
} else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
type = Email.TYPE_HOME;
@@ -907,48 +872,44 @@ public class ContactStruct {
addEmail(type, propValue, label, isPrimary);
} else if (propName.equals("ORG")) {
// vCard specification does not specify other types.
- int type = Organization.TYPE_WORK;
+ final int type = Organization.TYPE_WORK;
boolean isPrimary = false;
-
Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Organization) {
- // vCard specification officially does not have PREF in ORG.
- // This is just for safety.
- mPrefIsSet_Organization = true;
+ if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
isPrimary = true;
}
}
}
-
- StringBuilder builder = new StringBuilder();
- for (Iterator<String> iter = propValueList.iterator(); iter.hasNext();) {
- builder.append(iter.next());
- if (iter.hasNext()) {
- builder.append(' ');
- }
- }
- addOrganization(type, builder.toString(), "", isPrimary);
+ handleOrgValue(type, propValueList, isPrimary);
} else if (propName.equals("TITLE")) {
- setPosition(propValue);
+ handleTitleValue(propValue);
} else if (propName.equals("ROLE")) {
- setPosition(propValue);
+ // This conflicts with TITLE. Ignore for now...
+ // handleTitleValue(propValue);
} else if (propName.equals("PHOTO") || propName.equals("LOGO")) {
- String formatName = null;
- Collection<String> typeCollection = paramMap.get("TYPE");
- if (typeCollection != null) {
- formatName = typeCollection.iterator().next();
- }
Collection<String> paramMapValue = paramMap.get("VALUE");
if (paramMapValue != null && paramMapValue.contains("URL")) {
// Currently we do not have appropriate example for testing this case.
} else {
- addPhotoBytes(formatName, propBytes);
+ final Collection<String> typeCollection = paramMap.get("TYPE");
+ String formatName = null;
+ boolean isPrimary = false;
+ if (typeCollection != null) {
+ for (String typeValue : typeCollection) {
+ if (Constants.ATTR_TYPE_PREF.equals(typeValue)) {
+ isPrimary = true;
+ } else if (formatName == null){
+ formatName = typeValue;
+ }
+ }
+ }
+ addPhotoBytes(formatName, propBytes, isPrimary);
}
} else if (propName.equals("TEL")) {
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
- Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection);
+ final Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ final Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection);
final int type;
final String label;
if (typeObject instanceof Integer) {
@@ -960,9 +921,7 @@ public class ContactStruct {
}
final boolean isPrimary;
- if (!mPrefIsSet_Phone && typeCollection != null &&
- typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
- mPrefIsSet_Phone = true;
+ if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
isPrimary = true;
} else {
isPrimary = false;
@@ -975,9 +934,7 @@ public class ContactStruct {
int type = Phone.TYPE_OTHER;
final String label = null;
final boolean isPrimary;
- if (!mPrefIsSet_Phone && typeCollection != null &&
- typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
- mPrefIsSet_Phone = true;
+ if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
isPrimary = true;
} else {
isPrimary = false;
@@ -1048,7 +1005,10 @@ public class ContactStruct {
* Construct the display name. The constructed data must not be null.
*/
private void constructDisplayName() {
- if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
+ // FullName (created via "FN" or "NAME" field) is prefered.
+ if (!TextUtils.isEmpty(mFullName)) {
+ mDisplayName = mFullName;
+ } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
StringBuilder builder = new StringBuilder();
List<String> nameList;
switch (VCardConfig.getNameOrderType(mVCardType)) {
@@ -1079,8 +1039,6 @@ public class ContactStruct {
}
}
mDisplayName = builder.toString();
- } else if (!TextUtils.isEmpty(mFullName)) {
- mDisplayName = mFullName;
} else if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
TextUtils.isEmpty(mPhoneticGivenName))) {
mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
@@ -1103,25 +1061,10 @@ public class ContactStruct {
*/
public void consolidateFields() {
constructDisplayName();
-
+
if (mPhoneticFullName != null) {
mPhoneticFullName = mPhoneticFullName.trim();
}
-
- // If there is no "PREF", we choose the first entries as primary.
- if (!mPrefIsSet_Phone && mPhoneList != null && mPhoneList.size() > 0) {
- mPhoneList.get(0).isPrimary = true;
- }
-
- if (!mPrefIsSet_Address && mPostalList != null && mPostalList.size() > 0) {
- mPostalList.get(0).isPrimary = true;
- }
- if (!mPrefIsSet_Email && mEmailList != null && mEmailList.size() > 0) {
- mEmailList.get(0).isPrimary = true;
- }
- if (!mPrefIsSet_Organization && mOrganizationList != null && mOrganizationList.size() > 0) {
- mOrganizationList.get(0).isPrimary = true;
- }
}
// From GoogleSource.java in Contacts app.
@@ -1181,22 +1124,16 @@ public class ContactStruct {
}
if (mNickNameList != null && mNickNameList.size() > 0) {
- boolean first = true;
for (String nickName : mNickNameList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Nickname.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
-
builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT);
builder.withValue(Nickname.NAME, nickName);
- if (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
- first = false;
- }
operationList.add(builder.build());
}
}
-
+
if (mPhoneList != null) {
for (PhoneData phoneData : mPhoneList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -1209,30 +1146,34 @@ public class ContactStruct {
}
builder.withValue(Phone.NUMBER, phoneData.data);
if (phoneData.isPrimary) {
- builder.withValue(Data.IS_PRIMARY, 1);
+ builder.withValue(Phone.IS_PRIMARY, 1);
}
operationList.add(builder.build());
}
}
-
+
if (mOrganizationList != null) {
- boolean first = true;
for (OrganizationData organizationData : mOrganizationList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Organization.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
-
- // Currently, we do not use TYPE_CUSTOM.
builder.withValue(Organization.TYPE, organizationData.type);
- builder.withValue(Organization.COMPANY, organizationData.companyName);
- builder.withValue(Organization.TITLE, organizationData.positionName);
- if (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
+ if (organizationData.companyName != null) {
+ builder.withValue(Organization.COMPANY, organizationData.companyName);
+ }
+ if (organizationData.departmentName != null) {
+ builder.withValue(Organization.DEPARTMENT, organizationData.departmentName);
+ }
+ if (organizationData.titleName != null) {
+ builder.withValue(Organization.TITLE, organizationData.titleName);
+ }
+ if (organizationData.isPrimary) {
+ builder.withValue(Organization.IS_PRIMARY, 1);
}
operationList.add(builder.build());
}
}
-
+
if (mEmailList != null) {
for (EmailData emailData : mEmailList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -1265,7 +1206,6 @@ public class ContactStruct {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Im.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
-
builder.withValue(Im.TYPE, imData.type);
if (imData.type == Im.TYPE_CUSTOM) {
builder.withValue(Im.LABEL, imData.label);
@@ -1282,22 +1222,19 @@ public class ContactStruct {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Note.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
-
builder.withValue(Note.NOTE, note);
operationList.add(builder.build());
}
}
-
+
if (mPhotoList != null) {
- boolean first = true;
for (PhotoData photoData : mPhotoList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Photo.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
builder.withValue(Photo.PHOTO, photoData.photoBytes);
- if (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
- first = false;
+ if (photoData.isPrimary) {
+ builder.withValue(Photo.IS_PRIMARY, 1);
}
operationList.add(builder.build());
}
@@ -1310,12 +1247,12 @@ public class ContactStruct {
builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE);
builder.withValue(Website.URL, website);
// There's no information about the type of URL in vCard.
- // We use TYPE_HOME for safety.
- builder.withValue(Website.TYPE, Website.TYPE_HOME);
+ // We use TYPE_HOMEPAGE for safety.
+ builder.withValue(Website.TYPE, Website.TYPE_HOMEPAGE);
operationList.add(builder.build());
}
}
-
+
if (!TextUtils.isEmpty(mBirthday)) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Event.RAW_CONTACT_ID, 0);
@@ -1364,4 +1301,99 @@ public class ContactStruct {
return "";
}
}
+
+ // All getter methods should be used carefully, since they may change
+ // in the future as of 2009-10-05, on which I cannot be sure this structure
+ // is completely consolidated.
+ //
+ // Also note that these getter methods should be used only after
+ // all properties being pushed into this object. If not, incorrect
+ // value will "be stored in the local cache and" be returned to you.
+
+ public String getFamilyName() {
+ return mFamilyName;
+ }
+
+ public String getGivenName() {
+ return mGivenName;
+ }
+
+ public String getMiddleName() {
+ return mMiddleName;
+ }
+
+ public String getPrefix() {
+ return mPrefix;
+ }
+
+ public String getSuffix() {
+ return mSuffix;
+ }
+
+ public String getFullName() {
+ return mFullName;
+ }
+
+ public String getPhoneticFamilyName() {
+ return mPhoneticFamilyName;
+ }
+
+ public String getPhoneticGivenName() {
+ return mPhoneticGivenName;
+ }
+
+ public String getPhoneticMiddleName() {
+ return mPhoneticMiddleName;
+ }
+
+ public String getPhoneticFullName() {
+ return mPhoneticFullName;
+ }
+
+ public final List<String> getNickNameList() {
+ return mNickNameList;
+ }
+
+ public String getBirthday() {
+ return mBirthday;
+ }
+
+ public final List<String> getNotes() {
+ return mNoteList;
+ }
+
+ public final List<PhoneData> getPhoneList() {
+ return mPhoneList;
+ }
+
+ public final List<EmailData> getEmailList() {
+ return mEmailList;
+ }
+
+ public final List<PostalData> getPostalList() {
+ return mPostalList;
+ }
+
+ public final List<OrganizationData> getOrganizationList() {
+ return mOrganizationList;
+ }
+
+ public final List<ImData> getImList() {
+ return mImList;
+ }
+
+ public final List<PhotoData> getPhotoList() {
+ return mPhotoList;
+ }
+
+ public final List<String> getWebsiteList() {
+ return mWebsiteList;
+ }
+
+ public String getDisplayName() {
+ if (mDisplayName == null) {
+ constructDisplayName();
+ }
+ return mDisplayName;
+ }
}
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
index 68cd0df..e606440 100644
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ b/core/java/android/pim/vcard/VCardConfig.java
@@ -41,8 +41,8 @@ public class VCardConfig {
// TODO: make the other codes use this flag
public static final boolean IGNORE_CASE_EXCEPT_VALUE = true;
- private static final int FLAG_V21 = 0;
- private static final int FLAG_V30 = 1;
+ public static final int FLAG_V21 = 0;
+ public static final int FLAG_V30 = 1;
// 0x2 is reserved for the future use ...
@@ -105,8 +105,16 @@ public class VCardConfig {
* behavior around this flag in the future. Do not use this flag without any reason.
*/
public static final int FLAG_USE_QP_TO_PRIMARY_PROPERTIES = 0x10000000;
-
- // VCard types
+
+ // Note: if you really want to add additional flag(s) incompatible with the main source tree,
+ // please use flags from 0x0001000 to 0x00080000.
+ //
+ // If we notice we cannot manage flags in just one integer, we'll change this interface and
+ // create/use a complete class, not just an integer, with similar API (but imcompatible).
+ //
+ // Again, please be aware that this API is intentionally hidden ~= unstable!
+
+ //// The followings are VCard types available from importer/exporter. ////
/**
* General vCard format with the version 2.1. Uses UTF-8 for the charset.
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index 974fca8..a72b96f 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -144,10 +144,14 @@ public class VCardParser_V21 extends VCardParser {
}
}
- protected String getVersion() {
+ protected int getVersion() {
+ return VCardConfig.FLAG_V21;
+ }
+
+ protected String getVersionString() {
return "2.1";
}
-
+
/**
* @return true when the propertyName is a valid property name.
*/
@@ -356,7 +360,7 @@ public class VCardParser_V21 extends VCardParser {
* / [groups "."] "ADR" [params] ":" addressparts CRLF
* / [groups "."] "ORG" [params] ":" orgparts CRLF
* / [groups "."] "N" [params] ":" nameparts CRLF
- * / [groups "."] "AGENT" [params] ":" vcard CRLF
+ * / [groups "."] "AGENT" [params] ":" vcard CRLF
*/
protected boolean parseItem() throws IOException, VCardException {
mEncoding = sDefaultEncoding;
@@ -392,9 +396,10 @@ public class VCardParser_V21 extends VCardParser {
} else {
throw new VCardException("Unknown BEGIN type: " + propertyValue);
}
- } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersion())) {
+ } else if (propertyName.equals("VERSION") &&
+ !propertyValue.equals(getVersionString())) {
throw new VCardVersionException("Incompatible version: " +
- propertyValue + " != " + getVersion());
+ propertyValue + " != " + getVersionString());
}
start = System.currentTimeMillis();
handlePropertyValue(propertyName, propertyValue);
@@ -761,32 +766,11 @@ public class VCardParser_V21 extends VCardParser {
}
if (mBuilder != null) {
- StringBuilder builder = new StringBuilder();
- ArrayList<String> list = new ArrayList<String>();
- int length = propertyValue.length();
- for (int i = 0; i < length; i++) {
- char ch = propertyValue.charAt(i);
- if (ch == '\\' && i < length - 1) {
- char nextCh = propertyValue.charAt(i + 1);
- String unescapedString = maybeUnescapeCharacter(nextCh);
- if (unescapedString != null) {
- builder.append(unescapedString);
- i++;
- } else {
- builder.append(ch);
- }
- } else if (ch == ';') {
- list.add(builder.toString());
- builder = new StringBuilder();
- } else {
- builder.append(ch);
- }
- }
- list.add(builder.toString());
- mBuilder.propertyValues(list);
+ mBuilder.propertyValues(VCardUtils.constructListFromValue(
+ propertyValue, (getVersion() == VCardConfig.FLAG_V30)));
}
}
-
+
/**
* vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all.
*
@@ -819,12 +803,16 @@ public class VCardParser_V21 extends VCardParser {
protected String maybeUnescapeText(String text) {
return text;
}
-
+
/**
* Returns unescaped String if the character should be unescaped. Return null otherwise.
* e.g. In vCard 2.1, "\;" should be unescaped into ";" while "\x" should not be.
*/
protected String maybeUnescapeCharacter(char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ public static String unescapeCharacter(char ch) {
// Original vCard 2.1 specification does not allow transformation
// "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous implementation of
// this class allowed them, so keep it as is.
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 384649a..8267ff5 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -48,12 +48,17 @@ public class VCardParser_V30 extends VCardParser_V21 {
private String mPreviousLine;
private boolean mEmittedAgentWarning = false;
-
+
@Override
- protected String getVersion() {
+ protected int getVersion() {
+ return VCardConfig.FLAG_V30;
+ }
+
+ @Override
+ protected String getVersionString() {
return Constants.VERSION_V30;
}
-
+
@Override
protected boolean isValidPropertyName(String propertyName) {
if (!(sAcceptablePropsWithParam.contains(propertyName) ||
@@ -284,6 +289,10 @@ public class VCardParser_V30 extends VCardParser_V21 {
*/
@Override
protected String maybeUnescapeText(String text) {
+ return unescapeText(text);
+ }
+
+ public static String unescapeText(String text) {
StringBuilder builder = new StringBuilder();
int length = text.length();
for (int i = 0; i < length; i++) {
@@ -299,15 +308,19 @@ public class VCardParser_V30 extends VCardParser_V21 {
builder.append(ch);
}
}
- return builder.toString();
+ return builder.toString();
}
@Override
protected String maybeUnescapeCharacter(char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ public static String unescapeCharacter(char ch) {
if (ch == 'n' || ch == 'N') {
return "\n";
} else {
return String.valueOf(ch);
- }
+ }
}
}
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index 4f50103..bfd4a4e 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -22,9 +22,11 @@ import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.text.TextUtils;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -187,7 +189,10 @@ public class VCardUtils {
}
builder.withValue(StructuredPostal.POBOX, postalData.pobox);
- // Extended address is dropped since there's no relevant entry in ContactsContract.
+ // TODO: Japanese phone seems to use this field for expressing all the address including
+ // region, city, etc. Not sure we're ok to store them into NEIGHBORHOOD, while it would be
+ // better than dropping them all.
+ builder.withValue(StructuredPostal.NEIGHBORHOOD, postalData.extendedAddress);
builder.withValue(StructuredPostal.STREET, postalData.street);
builder.withValue(StructuredPostal.CITY, postalData.localty);
builder.withValue(StructuredPostal.REGION, postalData.region);
@@ -282,7 +287,36 @@ public class VCardUtils {
}
return builder.toString();
}
-
+
+ public static List<String> constructListFromValue(final String value,
+ final boolean isV30) {
+ final List<String> list = new ArrayList<String>();
+ StringBuilder builder = new StringBuilder();
+ int length = value.length();
+ for (int i = 0; i < length; i++) {
+ char ch = value.charAt(i);
+ if (ch == '\\' && i < length - 1) {
+ char nextCh = value.charAt(i + 1);
+ final String unescapedString =
+ (isV30 ? VCardParser_V30.unescapeCharacter(nextCh) :
+ VCardParser_V21.unescapeCharacter(nextCh));
+ if (unescapedString != null) {
+ builder.append(unescapedString);
+ i++;
+ } else {
+ builder.append(ch);
+ }
+ } else if (ch == ';') {
+ list.add(builder.toString());
+ builder = new StringBuilder();
+ } else {
+ builder.append(ch);
+ }
+ }
+ list.add(builder.toString());
+ return list;
+ }
+
public static boolean containsOnlyPrintableAscii(String str) {
if (TextUtils.isEmpty(str)) {
return true;