summaryrefslogtreecommitdiffstats
path: root/core/java/android/pim/vcard
diff options
context:
space:
mode:
authorDaisuke Miyakawa <dmiyakawa@google.com>2009-09-29 12:43:19 -0700
committerDaisuke Miyakawa <dmiyakawa@google.com>2009-09-29 13:59:01 -0700
commita2ba9a199962eb1b14ac3e0aa0fbc4c557298c96 (patch)
tree17d7da6973dc5c00194f966f8665b98ba1c8fc98 /core/java/android/pim/vcard
parentf993fefc3b14ece3f0f3eac6bb3d241bffb3cdfb (diff)
downloadframeworks_base-a2ba9a199962eb1b14ac3e0aa0fbc4c557298c96.zip
frameworks_base-a2ba9a199962eb1b14ac3e0aa0fbc4c557298c96.tar.gz
frameworks_base-a2ba9a199962eb1b14ac3e0aa0fbc4c557298c96.tar.bz2
Clean up vCard code.
With this change, the vCard composer does not emit any information about CHARSET until it is really needed for parsing it (e.g. when non-ascii chacacters are included in some value). This "may" be effective toward external vCard importers which are not able to understand non-ascii characters and CHARSET info itself. Also now vCard composer does not use Quoted Printable until it is really needed (e.g. until when the composer find non-"7bit" characters are included in some value. "7bit" is defined in vCard 2.1, which is "<7bit us-ascii printable chars, excluding CR LF>". The vCard composer detects duplicate phone numbers, email addresses, and remove them in default. The duplication would come from aggregation done by the new ContactsProvider introduced in Eclair. Even when two Accounts have two exactly same addresses, we cannot ask ContactsProvider to exclude them since current implementation of ContactsProvider2#queryEntities() does not acceps Contacts.CONTENT_URI but only RawContacts.CONTENT_URI, which inevitably returns same email addresses with different Accounts (ACCOUNT_NAME and ACCOUNT_TYPE should be different. If not, the original contact itself should have the duplication). The vCard composer now detects null returned from queryEntities(). The null value is potentially harmful for vCard file, since it forces the (old) vCard composer to emit meaningless line like "BDAY:". Internal Issue number: 2150768, 2151954, 2154235
Diffstat (limited to 'core/java/android/pim/vcard')
-rw-r--r--core/java/android/pim/vcard/ContactStruct.java4
-rw-r--r--core/java/android/pim/vcard/VCardComposer.java578
-rw-r--r--core/java/android/pim/vcard/VCardUtils.java28
3 files changed, 376 insertions, 234 deletions
diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/ContactStruct.java
index 35224c7..915f09e 100644
--- a/core/java/android/pim/vcard/ContactStruct.java
+++ b/core/java/android/pim/vcard/ContactStruct.java
@@ -1051,8 +1051,8 @@ public class ContactStruct {
List<String> nameList;
switch (VCardConfig.getNameOrderType(mVCardType)) {
case VCardConfig.NAME_ORDER_JAPANESE:
- if (VCardUtils.containsOnlyAscii(mFamilyName) &&
- VCardUtils.containsOnlyAscii(mGivenName)) {
+ if (VCardUtils.containsOnlyPrintableAscii(mFamilyName) &&
+ VCardUtils.containsOnlyPrintableAscii(mGivenName)) {
nameList = Arrays.asList(mPrefix, mGivenName, mMiddleName, mFamilyName, mSuffix);
} else {
nameList = Arrays.asList(mPrefix, mFamilyName, mMiddleName, mGivenName, mSuffix);
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 40b4fc4..b0376c4 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -56,8 +56,10 @@ import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* <p>
@@ -234,9 +236,8 @@ public class VCardComposer {
private static final String VCARD_PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
// Android specific properties
- private static final String VCARD_PROPERTY_X_PHONETIC_NAME = "X-PHONETIC-NAME";
+ // TODO: ues extra MIME-TYPE instead of adding this kind of inflexible fields
private static final String VCARD_PROPERTY_X_NICKNAME = "X-NICKNAME";
- // TODO: add properties like X-LATITUDE
// Property for call log entry
private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
@@ -396,7 +397,7 @@ public class VCardComposer {
}
boolean needCharset = false;
- if (!(VCardUtils.containsOnlyAscii(phoneName))) {
+ if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
needCharset = true;
}
appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, phoneName, needCharset, false);
@@ -596,7 +597,7 @@ public class VCardComposer {
if (TextUtils.isEmpty(name)) {
name = mCursor.getString(NUMBER_COLUMN_INDEX);
}
- final boolean needCharset = !(VCardUtils.containsOnlyAscii(name));
+ final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, name, needCharset, false);
appendVCardLine(builder, VCARD_PROPERTY_NAME, name, needCharset, false);
@@ -740,213 +741,220 @@ public class VCardComposer {
final Map<String, List<ContentValues>> contentValuesListMap) {
final List<ContentValues> contentValuesList = contentValuesListMap
.get(StructuredName.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
+ if (contentValuesList != null && contentValuesList.size() > 0) {
appendStructuredNamesInternal(builder, contentValuesList);
} else if (mIsDoCoMo) {
appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
+ } else if (mIsV30) {
+ // vCard 3.0 requires "N" and "FN" properties.
+ appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
+ appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
}
}
private void appendStructuredNamesInternal(final StringBuilder builder,
final List<ContentValues> contentValuesList) {
+ // For safety, we'll emit just one value around StructuredName, as external importers
+ // may get confused with multiple "N", "FN", etc. properties, though it is valid in
+ // vCard spec.
+ ContentValues primaryContentValues = null;
for (ContentValues contentValues : contentValuesList) {
- final String familyName = contentValues
- .getAsString(StructuredName.FAMILY_NAME);
- final String middleName = contentValues
- .getAsString(StructuredName.MIDDLE_NAME);
- final String givenName = contentValues
- .getAsString(StructuredName.GIVEN_NAME);
- final String prefix = contentValues
- .getAsString(StructuredName.PREFIX);
- final String suffix = contentValues
- .getAsString(StructuredName.SUFFIX);
- final String displayName = contentValues
- .getAsString(StructuredName.DISPLAY_NAME);
-
- // For now, some primary element is not encoded into Quoted-Printable, which is not
- // valid in vCard spec strictly. In the future, we may have to have some flag to
- // enable composer to encode these primary field into Quoted-Printable.
- if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
- final String encodedFamily = escapeCharacters(familyName);
- final String encodedGiven = escapeCharacters(givenName);
- final String encodedMiddle = escapeCharacters(middleName);
- final String encodedPrefix = escapeCharacters(prefix);
- final String encodedSuffix = escapeCharacters(suffix);
-
- // N property. This order is specified by vCard spec and does not depend on countries.
- builder.append(VCARD_PROPERTY_NAME);
- if (!(VCardUtils.containsOnlyAscii(familyName) &&
- VCardUtils.containsOnlyAscii(givenName) &&
- VCardUtils.containsOnlyAscii(middleName) &&
- VCardUtils.containsOnlyAscii(prefix) &&
- VCardUtils.containsOnlyAscii(suffix))) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedFamily);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedGiven);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedMiddle);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedPrefix);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedSuffix);
- builder.append(VCARD_COL_SEPARATOR);
-
- final String encodedFullname = VCardUtils.constructNameFromElements(
- VCardConfig.getNameOrderType(mVCardType),
- encodedFamily, encodedMiddle, encodedGiven, encodedPrefix, encodedSuffix);
+ Integer isSuperPrimary = contentValues.getAsInteger(StructuredName.IS_SUPER_PRIMARY);
+ if (isSuperPrimary != null && isSuperPrimary != 0) {
+ // We choose "super primary" ContentValues.
+ primaryContentValues = contentValues;
+ break;
+ } else if (primaryContentValues == null && contentValues != null) {
+ // We choose the first ContentValues if "super primary" ContentValues does not exist.
+ primaryContentValues = contentValues;
+ }
+ }
- // FN property
- builder.append(VCARD_PROPERTY_FULL_NAME);
- if (!VCardUtils.containsOnlyAscii(encodedFullname)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedFullname);
- builder.append(VCARD_COL_SEPARATOR);
- } else if (!TextUtils.isEmpty(displayName)) {
- builder.append(VCARD_PROPERTY_NAME);
+ if (primaryContentValues == null) {
+ Log.e(LOG_TAG, "All ContentValues given from database is empty.");
+ primaryContentValues = new ContentValues();
+ }
+
+ final String familyName = primaryContentValues
+ .getAsString(StructuredName.FAMILY_NAME);
+ final String middleName = primaryContentValues
+ .getAsString(StructuredName.MIDDLE_NAME);
+ final String givenName = primaryContentValues
+ .getAsString(StructuredName.GIVEN_NAME);
+ final String prefix = primaryContentValues
+ .getAsString(StructuredName.PREFIX);
+ final String suffix = primaryContentValues
+ .getAsString(StructuredName.SUFFIX);
+ final String displayName = primaryContentValues
+ .getAsString(StructuredName.DISPLAY_NAME);
+
+ // For now, some primary element is not encoded into Quoted-Printable, which is not
+ // valid in vCard spec strictly. In the future, we may have to have some flag to
+ // enable composer to encode these primary field into Quoted-Printable.
+ if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
+ final String encodedFamily = escapeCharacters(familyName);
+ final String encodedGiven = escapeCharacters(givenName);
+ final String encodedMiddle = escapeCharacters(middleName);
+ final String encodedPrefix = escapeCharacters(prefix);
+ final String encodedSuffix = escapeCharacters(suffix);
+
+ // N property. This order is specified by vCard spec and does not depend on countries.
+ builder.append(VCARD_PROPERTY_NAME);
+ if (!(VCardUtils.containsOnlyPrintableAscii(familyName) &&
+ VCardUtils.containsOnlyPrintableAscii(givenName) &&
+ VCardUtils.containsOnlyPrintableAscii(middleName) &&
+ VCardUtils.containsOnlyPrintableAscii(prefix) &&
+ VCardUtils.containsOnlyPrintableAscii(suffix))) {
builder.append(VCARD_ATTR_SEPARATOR);
builder.append(mVCardAttributeCharset);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(escapeCharacters(displayName));
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- } else if (mIsDoCoMo) {
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
}
- String phoneticFamilyName = contentValues
- .getAsString(StructuredName.PHONETIC_FAMILY_NAME);
- String phoneticMiddleName = contentValues
- .getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
- String phoneticGivenName = contentValues
- .getAsString(StructuredName.PHONETIC_GIVEN_NAME);
- if (!(TextUtils.isEmpty(phoneticFamilyName)
- && TextUtils.isEmpty(phoneticMiddleName) && TextUtils
- .isEmpty(phoneticGivenName))) { // if not empty
- if (mIsJapaneseMobilePhone) {
- phoneticFamilyName = VCardUtils
- .toHalfWidthString(phoneticFamilyName);
- phoneticMiddleName = VCardUtils
- .toHalfWidthString(phoneticMiddleName);
- phoneticGivenName = VCardUtils
- .toHalfWidthString(phoneticGivenName);
- }
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(encodedFamily);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(encodedGiven);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(encodedMiddle);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(encodedPrefix);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(encodedSuffix);
+ builder.append(VCARD_COL_SEPARATOR);
- if (mIsV30) {
- final String sortString = VCardUtils
- .constructNameFromElements(mVCardType,
- phoneticFamilyName, phoneticMiddleName,
- phoneticGivenName);
- builder.append(VCARD_PROPERTY_SORT_STRING);
-
- if (!VCardUtils.containsOnlyAscii(sortString)) {
- // Strictly, adding charset information is NOT valid in
- // VCard 3.0,
- // but we'll add this info since parser side may be able to
- // use the charset via
- // this attribute field.
- //
- // e.g. Japanese mobile phones use Shift_Jis while RFC 2426
- // recommends
- // UTF-8. By adding this field, parsers may be able to know
- // this text
- // is NOT UTF-8 but Shift_Jis.
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
+ final String encodedFullname = VCardUtils.constructNameFromElements(
+ VCardConfig.getNameOrderType(mVCardType),
+ encodedFamily, encodedMiddle, encodedGiven, encodedPrefix, encodedSuffix);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(sortString);
- builder.append(VCARD_COL_SEPARATOR);
- } else {
- // Note: There is no appropriate property for expressing
- // phonetic name in
- // VCard 2.1, while there is in VCard 3.0 (SORT-STRING).
- // We chose to use DoCoMo's way since it is supported by a
- // lot of
- // Japanese mobile phones.
+ // FN property
+ builder.append(VCARD_PROPERTY_FULL_NAME);
+ if (!VCardUtils.containsOnlyPrintableAscii(encodedFullname)) {
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(mVCardAttributeCharset);
+ }
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(encodedFullname);
+ builder.append(VCARD_COL_SEPARATOR);
+ } else if (!TextUtils.isEmpty(displayName)) {
+ builder.append(VCARD_PROPERTY_NAME);
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(mVCardAttributeCharset);
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(escapeCharacters(displayName));
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_COL_SEPARATOR);
+ } else if (mIsDoCoMo) {
+ appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
+ } else if (mIsV30) {
+ appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
+ appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
+ }
+
+ String phoneticFamilyName = primaryContentValues
+ .getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+ String phoneticMiddleName = primaryContentValues
+ .getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+ String phoneticGivenName = primaryContentValues
+ .getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+ if (!(TextUtils.isEmpty(phoneticFamilyName)
+ && TextUtils.isEmpty(phoneticMiddleName) &&
+ TextUtils.isEmpty(phoneticGivenName))) { // if not empty
+ if (mIsJapaneseMobilePhone) {
+ phoneticFamilyName = VCardUtils
+ .toHalfWidthString(phoneticFamilyName);
+ phoneticMiddleName = VCardUtils
+ .toHalfWidthString(phoneticMiddleName);
+ phoneticGivenName = VCardUtils
+ .toHalfWidthString(phoneticGivenName);
+ }
+
+ if (mIsV30) {
+ final String sortString = VCardUtils
+ .constructNameFromElements(mVCardType,
+ phoneticFamilyName, phoneticMiddleName,
+ phoneticGivenName);
+ builder.append(VCARD_PROPERTY_SORT_STRING);
+
+ if (!VCardUtils.containsOnlyPrintableAscii(sortString)) {
+ // Strictly, adding charset information is NOT valid in
+ // VCard 3.0,
+ // but we'll add this info since parser side may be able to
+ // use the charset via
+ // this attribute field.
//
- // TODO: should use Quoted-Pritable?
- builder.append(VCARD_PROPERTY_SOUND);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(Constants.ATTR_TYPE_X_IRMC_N);
+ // e.g. Japanese mobile phones use Shift_Jis while RFC 2426
+ // recommends
+ // UTF-8. By adding this field, parsers may be able to know
+ // this text
+ // is NOT UTF-8 but Shift_Jis.
builder.append(VCARD_ATTR_SEPARATOR);
-
- if (!(VCardUtils.containsOnlyAscii(phoneticFamilyName) &&
- VCardUtils.containsOnlyAscii(phoneticMiddleName) &&
- VCardUtils.containsOnlyAscii(phoneticGivenName))) {
- builder.append(mVCardAttributeCharset);
- builder.append(VCARD_DATA_SEPARATOR);
- }
-
- builder.append(escapeCharacters(phoneticFamilyName));
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(escapeCharacters(phoneticMiddleName));
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(escapeCharacters(phoneticGivenName));
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
-
- if (mUsesAndroidProperty) {
- final String phoneticName = VCardUtils
- .constructNameFromElements(mVCardType,
- phoneticFamilyName, phoneticMiddleName,
- phoneticGivenName);
- builder.append(VCARD_PROPERTY_X_PHONETIC_NAME);
-
- if (!VCardUtils.containsOnlyAscii(phoneticName)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- // TODO: may need to make the text quoted-printable.
- builder.append(phoneticName);
- builder.append(VCARD_COL_SEPARATOR);
- }
+ builder.append(mVCardAttributeCharset);
}
- } else if (mIsDoCoMo) {
+
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(sortString);
+ builder.append(VCARD_COL_SEPARATOR);
+ } else {
+ // Note: There is no appropriate property for expressing
+ // phonetic name in
+ // VCard 2.1, while there is in VCard 3.0 (SORT-STRING).
+ // We chose to use DoCoMo's way since it is supported by a
+ // lot of Japanese mobile phones.
+ //
+ // TODO: should use Quoted-Pritable?
builder.append(VCARD_PROPERTY_SOUND);
builder.append(VCARD_ATTR_SEPARATOR);
builder.append(Constants.ATTR_TYPE_X_IRMC_N);
- builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(VCARD_ATTR_SEPARATOR);
+
+ if (!(VCardUtils.containsOnlyPrintableAscii(phoneticFamilyName) &&
+ VCardUtils.containsOnlyPrintableAscii(phoneticMiddleName) &&
+ VCardUtils.containsOnlyPrintableAscii(phoneticGivenName))) {
+ builder.append(mVCardAttributeCharset);
+ builder.append(VCARD_DATA_SEPARATOR);
+ }
+
+ builder.append(escapeCharacters(phoneticFamilyName));
builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(escapeCharacters(phoneticMiddleName));
builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(escapeCharacters(phoneticGivenName));
builder.append(VCARD_ITEM_SEPARATOR);
builder.append(VCARD_ITEM_SEPARATOR);
builder.append(VCARD_COL_SEPARATOR);
}
+ } else if (mIsDoCoMo) {
+ builder.append(VCARD_PROPERTY_SOUND);
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(Constants.ATTR_TYPE_X_IRMC_N);
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_COL_SEPARATOR);
+ }
- if (mUsesDefactProperty) {
- if (!TextUtils.isEmpty(phoneticGivenName)) {
- builder.append(VCARD_PROPERTY_X_PHONETIC_FIRST_NAME);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(phoneticGivenName);
- builder.append(VCARD_COL_SEPARATOR);
- }
- if (!TextUtils.isEmpty(phoneticMiddleName)) {
- builder.append(VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(phoneticMiddleName);
- builder.append(VCARD_COL_SEPARATOR);
- }
- if (!TextUtils.isEmpty(phoneticFamilyName)) {
- builder.append(VCARD_PROPERTY_X_PHONETIC_LAST_NAME);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(phoneticFamilyName);
- builder.append(VCARD_COL_SEPARATOR);
- }
+ if (mUsesDefactProperty) {
+ if (!TextUtils.isEmpty(phoneticGivenName)) {
+ builder.append(VCARD_PROPERTY_X_PHONETIC_FIRST_NAME);
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(phoneticGivenName);
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+ if (!TextUtils.isEmpty(phoneticMiddleName)) {
+ builder.append(VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME);
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(phoneticMiddleName);
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+ if (!TextUtils.isEmpty(phoneticFamilyName)) {
+ builder.append(VCARD_PROPERTY_X_PHONETIC_LAST_NAME);
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(phoneticFamilyName);
+ builder.append(VCARD_COL_SEPARATOR);
}
}
}
@@ -974,7 +982,7 @@ public class VCardComposer {
}
builder.append(propertyNickname);
- if (!VCardUtils.containsOnlyAscii(propertyNickname)) {
+ if (!VCardUtils.containsOnlyPrintableAscii(propertyNickname)) {
// Strictly, this is not valid in vCard 3.0. See above.
builder.append(VCARD_ATTR_SEPARATOR);
builder.append(mVCardAttributeCharset);
@@ -991,16 +999,31 @@ public class VCardComposer {
final Map<String, List<ContentValues>> contentValuesListMap) {
final List<ContentValues> contentValuesList = contentValuesListMap
.get(Phone.CONTENT_ITEM_TYPE);
+ boolean phoneLineExists = false;
if (contentValuesList != null) {
+ Set<String> phoneSet = new HashSet<String>();
for (ContentValues contentValues : contentValuesList) {
- Integer phoneType = contentValues.getAsInteger(Phone.TYPE);
- int phoneTypeAsPrimitive =
- (phoneType == null ? Phone.TYPE_HOME : phoneType);
- appendVCardTelephoneLine(builder, phoneTypeAsPrimitive,
- contentValues.getAsString(Phone.LABEL),
- contentValues.getAsString(Phone.NUMBER));
+ final Integer typeAsObject = contentValues.getAsInteger(Phone.TYPE);
+ final String label = contentValues.getAsString(Phone.LABEL);
+ String phoneNumber = contentValues.getAsString(Phone.NUMBER);
+ if (phoneNumber != null) {
+ phoneNumber = phoneNumber.trim();
+ }
+ if (TextUtils.isEmpty(phoneNumber)) {
+ continue;
+ }
+ phoneLineExists = true;
+ int type = (typeAsObject != null ? typeAsObject : Phone.TYPE_HOME);
+ // TODO: Premature, since this allows two phone numbers which are
+ // same from the view of phone number format (e.g. "100" v.s. "1-0-0")
+ if (!phoneSet.contains(phoneNumber)) {
+ phoneSet.add(phoneNumber);
+ appendVCardTelephoneLine(builder, type, label, phoneNumber);
+ }
}
- } else if (mIsDoCoMo) {
+ }
+
+ if (!phoneLineExists && mIsDoCoMo) {
appendVCardTelephoneLine(builder, Phone.TYPE_HOME, "", "");
}
}
@@ -1009,14 +1032,31 @@ public class VCardComposer {
final Map<String, List<ContentValues>> contentValuesListMap) {
final List<ContentValues> contentValuesList = contentValuesListMap
.get(Email.CONTENT_ITEM_TYPE);
+ boolean emailAddressExists = false;
if (contentValuesList != null) {
+ Set<String> addressSet = new HashSet<String>();
for (ContentValues contentValues : contentValuesList) {
- appendVCardEmailLine(builder, contentValues
- .getAsInteger(Email.TYPE), contentValues
- .getAsString(Email.LABEL), contentValues
- .getAsString(Email.DATA));
+ Integer typeAsObject = contentValues.getAsInteger(Email.TYPE);
+ final int type = (typeAsObject != null ?
+ typeAsObject : Email.TYPE_OTHER);
+ final String label = contentValues.getAsString(Email.LABEL);
+ String emailAddress = contentValues.getAsString(Email.DATA);
+ if (emailAddress != null) {
+ emailAddress = emailAddress.trim();
+ }
+ if (TextUtils.isEmpty(emailAddress)) {
+ continue;
+ }
+ emailAddressExists = true;
+ // Do not allow completely same email address line emitted into each file.
+ if (!addressSet.contains(emailAddress)) {
+ addressSet.add(emailAddress);
+ appendVCardEmailLine(builder, type, label, emailAddress);
+ }
}
- } else if (mIsDoCoMo) {
+ }
+
+ if (!emailAddressExists && mIsDoCoMo) {
appendVCardEmailLine(builder, Email.TYPE_HOME, "", "");
}
}
@@ -1087,7 +1127,9 @@ public class VCardComposer {
for (ContentValues contentValues : contentValuesList) {
final Integer type = contentValues.getAsInteger(StructuredPostal.TYPE);
final String label = contentValues.getAsString(StructuredPostal.LABEL);
- appendVCardPostalLine(builder, type, label, contentValues);
+ if (type != null) {
+ appendVCardPostalLine(builder, type, label, contentValues);
+ }
}
}
@@ -1099,6 +1141,12 @@ public class VCardComposer {
for (ContentValues contentValues : contentValuesList) {
Integer protocol = contentValues.getAsInteger(Im.PROTOCOL);
String data = contentValues.getAsString(Im.DATA);
+ if (data != null) {
+ data = data.trim();
+ }
+ if (TextUtils.isEmpty(data)) {
+ continue;
+ }
if (protocol != null && protocol == Im.PROTOCOL_GOOGLE_TALK) {
if (VCardConfig.usesAndroidSpecificProperty(mVCardType)) {
@@ -1116,8 +1164,13 @@ public class VCardComposer {
.get(Website.CONTENT_ITEM_TYPE);
if (contentValuesList != null) {
for (ContentValues contentValues : contentValuesList) {
- final String website = contentValues.getAsString(Website.URL);
- appendVCardLine(builder, VCARD_PROPERTY_URL, website);
+ String website = contentValues.getAsString(Website.URL);
+ if (website != null) {
+ website = website.trim();
+ }
+ if (!TextUtils.isEmpty(website)) {
+ appendVCardLine(builder, VCARD_PROPERTY_URL, website);
+ }
}
}
}
@@ -1130,8 +1183,13 @@ public class VCardComposer {
// Theoretically, there must be only one birthday for each vCard data and
// we are afraid of some parse error occuring in some devices, so
// we emit only one birthday entry for now.
- final String birthday = contentValuesList.get(0).getAsString(Birthday.BIRTHDAY);
- appendVCardLine(builder, VCARD_PROPERTY_BIRTHDAY, birthday);
+ String birthday = contentValuesList.get(0).getAsString(Birthday.BIRTHDAY);
+ if (birthday != null) {
+ birthday = birthday.trim();
+ }
+ if (!TextUtils.isEmpty(birthday)) {
+ appendVCardLine(builder, VCARD_PROPERTY_BIRTHDAY, birthday);
+ }
}
}
@@ -1141,14 +1199,29 @@ public class VCardComposer {
.get(Organization.CONTENT_ITEM_TYPE);
if (contentValuesList != null) {
for (ContentValues contentValues : contentValuesList) {
- final String company = contentValues
+ String company = contentValues
.getAsString(Organization.COMPANY);
- final String title = contentValues
+ if (company != null) {
+ company = company.trim();
+ }
+ String title = contentValues
.getAsString(Organization.TITLE);
- appendVCardLine(builder, VCARD_PROPERTY_ORG, company, true,
- mUsesQuotedPrintable);
- appendVCardLine(builder, VCARD_PROPERTY_TITLE, title, true,
- mUsesQuotedPrintable);
+ if (title != null) {
+ title = title.trim();
+ }
+
+ if (!TextUtils.isEmpty(company)) {
+ appendVCardLine(builder, VCARD_PROPERTY_ORG, company,
+ !VCardUtils.containsOnlyPrintableAscii(company),
+ (mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(company)));
+ }
+ if (!TextUtils.isEmpty(title)) {
+ appendVCardLine(builder, VCARD_PROPERTY_TITLE, title,
+ !VCardUtils.containsOnlyPrintableAscii(title),
+ (mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
+ }
}
}
}
@@ -1201,7 +1274,10 @@ public class VCardComposer {
StringBuilder noteBuilder = new StringBuilder();
boolean first = true;
for (ContentValues contentValues : contentValuesList) {
- final String note = contentValues.getAsString(Note.NOTE);
+ String note = contentValues.getAsString(Note.NOTE);
+ if (note == null) {
+ note = "";
+ }
if (note.length() > 0) {
if (first) {
first = false;
@@ -1211,14 +1287,27 @@ public class VCardComposer {
noteBuilder.append(note);
}
}
- appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteBuilder.toString(),
- true, mUsesQuotedPrintable);
+ final String noteStr = noteBuilder.toString();
+ // This means we scan noteStr completely twice, which is redundant.
+ // But for now, we assume this is not so time-consuming..
+ final boolean shouldAppendCharsetInfo =
+ !VCardUtils.containsOnlyPrintableAscii(noteStr);
+ final boolean reallyUseQuotedPrintable =
+ (mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
+ appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
+ shouldAppendCharsetInfo, reallyUseQuotedPrintable);
} else {
for (ContentValues contentValues : contentValuesList) {
- final String note = contentValues.getAsString(Note.NOTE);
- if (!TextUtils.isEmpty(note)) {
- appendVCardLine(builder, VCARD_PROPERTY_NOTE, note, true,
- mUsesQuotedPrintable);
+ final String noteStr = contentValues.getAsString(Note.NOTE);
+ if (!TextUtils.isEmpty(noteStr)) {
+ final boolean shouldAppendCharsetInfo =
+ !VCardUtils.containsOnlyPrintableAscii(noteStr);
+ final boolean reallyUseQuotedPrintable =
+ (mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
+ appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
+ shouldAppendCharsetInfo, reallyUseQuotedPrintable);
}
}
}
@@ -1339,15 +1428,31 @@ public class VCardComposer {
builder.append(VCARD_PROPERTY_ADR);
builder.append(VCARD_ATTR_SEPARATOR);
+ // Note: Not sure why we need to emit "empty" line even when actual data does not exist.
+ // There may be some reason or may not be any. We keep safer side.
+ // TODO: investigate this.
boolean dataExists = false;
String[] dataArray = VCardUtils.getVCardPostalElements(contentValues);
+ boolean actuallyUseQuotedPrintable = false;
+ boolean shouldAppendCharset = false;
+ for (String data : dataArray) {
+ if (!TextUtils.isEmpty(data)) {
+ dataExists = true;
+ if (!shouldAppendCharset && !VCardUtils.containsOnlyPrintableAscii(data)) {
+ shouldAppendCharset = true;
+ }
+ if (mUsesQuotedPrintable && !VCardUtils.containsOnlyNonCrLfPrintableAscii(data)) {
+ actuallyUseQuotedPrintable = true;
+ break;
+ }
+ }
+ }
+
int length = dataArray.length;
- final boolean useQuotedPrintable = mUsesQuotedPrintable;
for (int i = 0; i < length; i++) {
String data = dataArray[i];
if (!TextUtils.isEmpty(data)) {
- dataExists = true;
- if (useQuotedPrintable) {
+ if (actuallyUseQuotedPrintable) {
dataArray[i] = encodeQuotedPrintable(data);
} else {
dataArray[i] = escapeCharacters(data);
@@ -1394,25 +1499,40 @@ public class VCardComposer {
}
}
- if (typeAsString != null) {
- appendTypeAttribute(builder, typeAsString);
- }
+ // Attribute(s).
- if (dataExists) {
- // Strictly, vCard 3.0 does not allow exporters to emit charset information,
- // but we will add it since the information should be useful for importers,
- //
- // Assume no parser does not emit error with this attribute in vCard 3.0.
+ {
+ boolean shouldAppendAttrSeparator = false;
if (typeAsString != null) {
- builder.append(VCARD_ATTR_SEPARATOR);
+ appendTypeAttribute(builder, typeAsString);
+ shouldAppendAttrSeparator = true;
}
- builder.append(mVCardAttributeCharset);
- if (useQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
+ if (dataExists) {
+ if (shouldAppendCharset) {
+ // Strictly, vCard 3.0 does not allow exporters to emit charset information,
+ // but we will add it since the information should be useful for importers,
+ //
+ // Assume no parser does not emit error with this attribute in vCard 3.0.
+ if (shouldAppendAttrSeparator) {
+ builder.append(VCARD_ATTR_SEPARATOR);
+ }
+ builder.append(mVCardAttributeCharset);
+ shouldAppendAttrSeparator = true;
+ }
+
+ if (actuallyUseQuotedPrintable) {
+ if (shouldAppendAttrSeparator) {
+ builder.append(VCARD_ATTR_SEPARATOR);
+ }
+ builder.append(VCARD_ATTR_ENCODING_QP);
+ shouldAppendAttrSeparator = true;
+ }
}
}
+
+ // Property values.
+
builder.append(VCARD_DATA_SEPARATOR);
if (dataExists) {
// The elements in dataArray are already encoded to quoted printable
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index ffceade..4f50103 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -283,7 +283,7 @@ public class VCardUtils {
return builder.toString();
}
- public static boolean containsOnlyAscii(String str) {
+ public static boolean containsOnlyPrintableAscii(String str) {
if (TextUtils.isEmpty(str)) {
return true;
}
@@ -299,13 +299,35 @@ public class VCardUtils {
}
return true;
}
-
+
+ /**
+ * This is useful when checking the string should be encoded into quoted-printable
+ * or not, which is required by vCard 2.1.
+ * See the definition of "7bit" in vCard 2.1 spec for more information.
+ */
+ public static boolean containsOnlyNonCrLfPrintableAscii(String str) {
+ if (TextUtils.isEmpty(str)) {
+ return true;
+ }
+
+ final int length = str.length();
+ final int asciiFirst = 0x20;
+ final int asciiLast = 0x126;
+ for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
+ int c = str.codePointAt(i);
+ if (c < asciiFirst || asciiLast < c || c == '\n' || c == '\r') {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* This is useful since vCard 3.0 often requires the ("X-") properties and groups
* should contain only alphabets, digits, and hyphen.
*
* Note: It is already known some devices (wrongly) outputs properties with characters
- * which should not be in the field. One example is "X-GOOGLE TALK". We appreciate
+ * which should not be in the field. One example is "X-GOOGLE TALK". We accept
* such kind of input but must never output it unless the target is very specific
* to the device which is able to parse the malformed input.
*/