am ac3e2159: Merge change Icaf78440 into eclair-mr2
Merge commit 'ac3e2159b41b825874cf574cf59225d4fbddfbcb' into eclair-mr2-plus-aosp
* commit 'ac3e2159b41b825874cf574cf59225d4fbddfbcb':
Split vCard composer into two parts: VCardComposer and VCardBuilder.
diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java
new file mode 100644
index 0000000..eb40a3f
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardBuilder.java
@@ -0,0 +1,1874 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.pim.vcard;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+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.Relation;
+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.TextUtils;
+import android.util.CharsetUtils;
+import android.util.Log;
+
+import org.apache.commons.codec.binary.Base64;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The class which lets users create their own vCard String.
+ */
+public class VCardBuilder {
+ private static final String LOG_TAG = "VCardBuilder";
+
+ // If you add the other element, please check all the columns are able to be
+ // converted to String.
+ //
+ // e.g. BLOB is not what we can handle here now.
+ private static final Set<String> sAllowedAndroidPropertySet =
+ Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+ Nickname.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE,
+ Relation.CONTENT_ITEM_TYPE)));
+
+ public static final int DEFAULT_PHONE_TYPE = Phone.TYPE_HOME;
+ public static final int DEFAULT_POSTAL_TYPE = StructuredPostal.TYPE_HOME;
+ public static final int DEFAULT_EMAIL_TYPE = Email.TYPE_OTHER;
+
+ private static final String VCARD_DATA_VCARD = "VCARD";
+ private static final String VCARD_DATA_PUBLIC = "PUBLIC";
+
+ private static final String VCARD_PARAM_SEPARATOR = ";";
+ private static final String VCARD_END_OF_LINE = "\r\n";
+ private static final String VCARD_DATA_SEPARATOR = ":";
+ private static final String VCARD_ITEM_SEPARATOR = ";";
+ private static final String VCARD_WS = " ";
+ private static final String VCARD_PARAM_EQUAL = "=";
+
+ private static final String VCARD_PARAM_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
+
+ private static final String VCARD_PARAM_ENCODING_BASE64_V21 = "ENCODING=BASE64";
+ private static final String VCARD_PARAM_ENCODING_BASE64_V30 = "ENCODING=b";
+
+ private static final String SHIFT_JIS = "SHIFT_JIS";
+ private static final String UTF_8 = "UTF-8";
+
+ private final int mVCardType;
+
+ private final boolean mIsV30;
+ private final boolean mIsJapaneseMobilePhone;
+ private final boolean mOnlyOneNoteFieldIsAvailable;
+ private final boolean mIsDoCoMo;
+ private final boolean mUsesQuotedPrintable;
+ private final boolean mUsesAndroidProperty;
+ private final boolean mUsesDefactProperty;
+ private final boolean mUsesUtf8;
+ private final boolean mUsesShiftJis;
+ private final boolean mAppendTypeParamName;
+ private final boolean mRefrainsQPToPrimaryProperties;
+ private final boolean mNeedsToConvertPhoneticString;
+
+ private final boolean mShouldAppendCharsetParam;
+
+ private final String mCharsetString;
+ private final String mVCardCharsetParameter;
+
+ private StringBuilder mBuilder;
+ private boolean mEndAppended;
+
+ public VCardBuilder(final int vcardType) {
+ mVCardType = vcardType;
+
+ mIsV30 = VCardConfig.isV30(vcardType);
+ mUsesQuotedPrintable = VCardConfig.usesQuotedPrintable(vcardType);
+ mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
+ mIsJapaneseMobilePhone = VCardConfig.needsToConvertPhoneticString(vcardType);
+ mOnlyOneNoteFieldIsAvailable = VCardConfig.onlyOneNoteFieldIsAvailable(vcardType);
+ mUsesAndroidProperty = VCardConfig.usesAndroidSpecificProperty(vcardType);
+ mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
+ mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
+ mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
+ mRefrainsQPToPrimaryProperties = VCardConfig.refrainsQPToPrimaryProperties(vcardType);
+ mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType);
+ mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType);
+
+ mShouldAppendCharsetParam = !(mIsV30 && mUsesUtf8);
+
+ if (mIsDoCoMo) {
+ String charset;
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ mCharsetString = charset;
+ // Do not use mCharsetString bellow since it is different from "SHIFT_JIS" but
+ // may be "DOCOMO_SHIFT_JIS" or something like that (internal expression used in
+ // Android, not shown to the public).
+ mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
+ } else if (mUsesShiftJis) {
+ String charset;
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ mCharsetString = charset;
+ mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
+ } else {
+ mCharsetString = UTF_8;
+ mVCardCharsetParameter = "CHARSET=" + UTF_8;
+ }
+ clear();
+ }
+
+ public void clear() {
+ mBuilder = new StringBuilder();
+ mEndAppended = false;
+ appendLine(VCardConstants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
+ if (mIsV30) {
+ appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V30);
+ } else {
+ appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V21);
+ }
+ }
+
+ private boolean containsNonEmptyName(final ContentValues contentValues) {
+ 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 phoneticFamilyName =
+ contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+ final String phoneticMiddleName =
+ contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+ final String phoneticGivenName =
+ contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+ final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
+ return !(TextUtils.isEmpty(familyName) && TextUtils.isEmpty(middleName) &&
+ TextUtils.isEmpty(givenName) && TextUtils.isEmpty(prefix) &&
+ TextUtils.isEmpty(suffix) && TextUtils.isEmpty(phoneticFamilyName) &&
+ TextUtils.isEmpty(phoneticMiddleName) && TextUtils.isEmpty(phoneticGivenName) &&
+ TextUtils.isEmpty(displayName));
+ }
+
+ private ContentValues getPrimaryContentValue(final List<ContentValues> contentValuesList) {
+ ContentValues primaryContentValues = null;
+ ContentValues subprimaryContentValues = null;
+ for (ContentValues contentValues : contentValuesList) {
+ if (contentValues == null){
+ continue;
+ }
+ 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) {
+ // We choose the first "primary" ContentValues
+ // if "super primary" ContentValues does not exist.
+ final Integer isPrimary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
+ if (isPrimary != null && isPrimary > 0 &&
+ containsNonEmptyName(contentValues)) {
+ primaryContentValues = contentValues;
+ // Do not break, since there may be ContentValues with "super primary"
+ // afterword.
+ } else if (subprimaryContentValues == null &&
+ containsNonEmptyName(contentValues)) {
+ subprimaryContentValues = contentValues;
+ }
+ }
+ }
+
+ if (primaryContentValues == null) {
+ if (subprimaryContentValues != null) {
+ // We choose the first ContentValues if any "primary" ContentValues does not exist.
+ primaryContentValues = subprimaryContentValues;
+ } else {
+ Log.e(LOG_TAG, "All ContentValues given from database is empty.");
+ primaryContentValues = new ContentValues();
+ }
+ }
+
+ return primaryContentValues;
+ }
+
+ /**
+ * 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.
+ */
+ public VCardBuilder appendNameProperties(final List<ContentValues> contentValuesList) {
+ if (contentValuesList == null || contentValuesList.isEmpty()) {
+ if (mIsDoCoMo) {
+ appendLine(VCardConstants.PROPERTY_N, "");
+ } else if (mIsV30) {
+ // vCard 3.0 requires "N" and "FN" properties.
+ appendLine(VCardConstants.PROPERTY_N, "");
+ appendLine(VCardConstants.PROPERTY_FN, "");
+ }
+ return this;
+ }
+
+ final ContentValues contentValues = getPrimaryContentValue(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);
+
+ if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
+ final boolean reallyAppendCharsetParameterToName =
+ shouldAppendCharsetParam(familyName, givenName, middleName, prefix, suffix);
+ final boolean reallyUseQuotedPrintableToName =
+ (!mRefrainsQPToPrimaryProperties &&
+ !(VCardUtils.containsOnlyNonCrLfPrintableAscii(familyName) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(givenName) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(middleName) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(prefix) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(suffix)));
+
+ final String formattedName;
+ if (!TextUtils.isEmpty(displayName)) {
+ formattedName = displayName;
+ } else {
+ formattedName = VCardUtils.constructNameFromElements(
+ VCardConfig.getNameOrderType(mVCardType),
+ familyName, middleName, givenName, prefix, suffix);
+ }
+ final boolean reallyAppendCharsetParameterToFN =
+ shouldAppendCharsetParam(formattedName);
+ final boolean reallyUseQuotedPrintableToFN =
+ !mRefrainsQPToPrimaryProperties &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedName);
+
+ final String encodedFamily;
+ final String encodedGiven;
+ final String encodedMiddle;
+ final String encodedPrefix;
+ final String encodedSuffix;
+ if (reallyUseQuotedPrintableToName) {
+ encodedFamily = encodeQuotedPrintable(familyName);
+ encodedGiven = encodeQuotedPrintable(givenName);
+ encodedMiddle = encodeQuotedPrintable(middleName);
+ encodedPrefix = encodeQuotedPrintable(prefix);
+ encodedSuffix = encodeQuotedPrintable(suffix);
+ } else {
+ encodedFamily = escapeCharacters(familyName);
+ encodedGiven = escapeCharacters(givenName);
+ encodedMiddle = escapeCharacters(middleName);
+ encodedPrefix = escapeCharacters(prefix);
+ encodedSuffix = escapeCharacters(suffix);
+ }
+
+ final String encodedFormattedname =
+ (reallyUseQuotedPrintableToFN ?
+ encodeQuotedPrintable(formattedName) : escapeCharacters(formattedName));
+
+ mBuilder.append(VCardConstants.PROPERTY_N);
+ if (mIsDoCoMo) {
+ if (reallyAppendCharsetParameterToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ // DoCoMo phones require that all the elements in the "family name" field.
+ mBuilder.append(formattedName);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ } else {
+ if (reallyAppendCharsetParameterToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedFamily);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedGiven);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedMiddle);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedPrefix);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedSuffix);
+ }
+ mBuilder.append(VCARD_END_OF_LINE);
+
+ // FN property
+ mBuilder.append(VCardConstants.PROPERTY_FN);
+ if (reallyAppendCharsetParameterToFN) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToFN) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedFormattedname);
+ mBuilder.append(VCARD_END_OF_LINE);
+ } else if (!TextUtils.isEmpty(displayName)) {
+ final boolean reallyUseQuotedPrintableToDisplayName =
+ (!mRefrainsQPToPrimaryProperties &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(displayName));
+ final String encodedDisplayName =
+ reallyUseQuotedPrintableToDisplayName ?
+ encodeQuotedPrintable(displayName) :
+ escapeCharacters(displayName);
+
+ mBuilder.append(VCardConstants.PROPERTY_N);
+ if (shouldAppendCharsetParam(displayName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToDisplayName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedDisplayName);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_END_OF_LINE);
+ mBuilder.append(VCardConstants.PROPERTY_FN);
+
+ // Note: "CHARSET" param is not allowed in vCard 3.0, but we may add it
+ // when it would be useful for external importers, assuming no external
+ // importer allows this vioration.
+ if (shouldAppendCharsetParam(displayName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedDisplayName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ } else if (mIsV30) {
+ // vCard 3.0 specification requires these fields.
+ appendLine(VCardConstants.PROPERTY_N, "");
+ appendLine(VCardConstants.PROPERTY_FN, "");
+ } else if (mIsDoCoMo) {
+ appendLine(VCardConstants.PROPERTY_N, "");
+ }
+
+ appendPhoneticNameFields(contentValues);
+ return this;
+ }
+
+ private void appendPhoneticNameFields(final ContentValues contentValues) {
+ final String phoneticFamilyName;
+ final String phoneticMiddleName;
+ final String phoneticGivenName;
+ {
+ final String tmpPhoneticFamilyName =
+ contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+ final String tmpPhoneticMiddleName =
+ contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+ final String tmpPhoneticGivenName =
+ contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+ if (mNeedsToConvertPhoneticString) {
+ phoneticFamilyName = VCardUtils.toHalfWidthString(tmpPhoneticFamilyName);
+ phoneticMiddleName = VCardUtils.toHalfWidthString(tmpPhoneticMiddleName);
+ phoneticGivenName = VCardUtils.toHalfWidthString(tmpPhoneticGivenName);
+ } else {
+ phoneticFamilyName = tmpPhoneticFamilyName;
+ phoneticMiddleName = tmpPhoneticMiddleName;
+ phoneticGivenName = tmpPhoneticGivenName;
+ }
+ }
+
+ if (TextUtils.isEmpty(phoneticFamilyName)
+ && TextUtils.isEmpty(phoneticMiddleName)
+ && TextUtils.isEmpty(phoneticGivenName)) {
+ if (mIsDoCoMo) {
+ mBuilder.append(VCardConstants.PROPERTY_SOUND);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ return;
+ }
+
+ // Try to emit the field(s) related to phonetic name.
+ if (mIsV30) {
+ final String sortString = VCardUtils
+ .constructNameFromElements(mVCardType,
+ phoneticFamilyName, phoneticMiddleName, phoneticGivenName);
+ mBuilder.append(VCardConstants.PROPERTY_SORT_STRING);
+ if (shouldAppendCharsetParam(sortString)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(escapeCharacters(sortString));
+ mBuilder.append(VCARD_END_OF_LINE);
+ } else if (mIsJapaneseMobilePhone) {
+ // 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 when the device is Japanese one
+ // since it is supported by
+ // a lot of Japanese mobile phones. This is "X-" property, so
+ // any parser hopefully would not get confused with this.
+ //
+ // Also, DoCoMo's specification requires vCard composer to use just the first
+ // column.
+ // i.e.
+ // o SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
+ // x SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
+ mBuilder.append(VCardConstants.PROPERTY_SOUND);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
+
+ boolean reallyUseQuotedPrintable =
+ (!mRefrainsQPToPrimaryProperties
+ && !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticFamilyName)
+ && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticMiddleName)
+ && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticGivenName)));
+
+ final String encodedPhoneticFamilyName;
+ final String encodedPhoneticMiddleName;
+ final String encodedPhoneticGivenName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
+ encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
+ encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
+ } else {
+ encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
+ encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
+ encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
+ }
+
+ if (shouldAppendCharsetParam(encodedPhoneticFamilyName,
+ encodedPhoneticMiddleName, encodedPhoneticGivenName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ {
+ boolean first = true;
+ if (!TextUtils.isEmpty(encodedPhoneticFamilyName)) {
+ mBuilder.append(encodedPhoneticFamilyName);
+ first = false;
+ }
+ if (!TextUtils.isEmpty(encodedPhoneticMiddleName)) {
+ if (first) {
+ first = false;
+ } else {
+ mBuilder.append(' ');
+ }
+ mBuilder.append(encodedPhoneticMiddleName);
+ }
+ if (!TextUtils.isEmpty(encodedPhoneticGivenName)) {
+ if (!first) {
+ mBuilder.append(' ');
+ }
+ mBuilder.append(encodedPhoneticGivenName);
+ }
+ }
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ if (mUsesDefactProperty) {
+ if (!TextUtils.isEmpty(phoneticGivenName)) {
+ final boolean reallyUseQuotedPrintable =
+ (mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName));
+ final String encodedPhoneticGivenName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
+ } else {
+ encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
+ }
+ mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME);
+ if (shouldAppendCharsetParam(phoneticGivenName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedPhoneticGivenName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ if (!TextUtils.isEmpty(phoneticMiddleName)) {
+ final boolean reallyUseQuotedPrintable =
+ (mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName));
+ final String encodedPhoneticMiddleName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
+ } else {
+ encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
+ }
+ mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME);
+ if (shouldAppendCharsetParam(phoneticMiddleName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedPhoneticMiddleName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ if (!TextUtils.isEmpty(phoneticFamilyName)) {
+ final boolean reallyUseQuotedPrintable =
+ (mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName));
+ final String encodedPhoneticFamilyName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
+ } else {
+ encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
+ }
+ mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME);
+ if (shouldAppendCharsetParam(phoneticFamilyName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedPhoneticFamilyName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ }
+ }
+
+ public VCardBuilder appendNickNames(final List<ContentValues> contentValuesList) {
+ final boolean useAndroidProperty;
+ if (mIsV30) {
+ useAndroidProperty = false;
+ } else if (mUsesAndroidProperty) {
+ useAndroidProperty = true;
+ } else {
+ // There's no way to add this field.
+ return this;
+ }
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ final String nickname = contentValues.getAsString(Nickname.NAME);
+ if (TextUtils.isEmpty(nickname)) {
+ continue;
+ }
+ if (useAndroidProperty) {
+ appendAndroidSpecificProperty(Nickname.CONTENT_ITEM_TYPE, contentValues);
+ } else {
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_NICKNAME, nickname);
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendPhones(final List<ContentValues> contentValuesList) {
+ boolean phoneLineExists = false;
+ if (contentValuesList != null) {
+ Set<String> phoneSet = new HashSet<String>();
+ for (ContentValues contentValues : contentValuesList) {
+ final Integer typeAsObject = contentValues.getAsInteger(Phone.TYPE);
+ final String label = contentValues.getAsString(Phone.LABEL);
+ final Integer isPrimaryAsInteger = contentValues.getAsInteger(Phone.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ String phoneNumber = contentValues.getAsString(Phone.NUMBER);
+ if (phoneNumber != null) {
+ phoneNumber = phoneNumber.trim();
+ }
+ if (TextUtils.isEmpty(phoneNumber)) {
+ continue;
+ }
+ int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
+ if (type == Phone.TYPE_PAGER) {
+ phoneLineExists = true;
+ if (!phoneSet.contains(phoneNumber)) {
+ phoneSet.add(phoneNumber);
+ appendTelLine(type, label, phoneNumber, isPrimary);
+ }
+ } else {
+ // The entry "may" have several phone numbers when the contact entry is
+ // corrupted because of its original source.
+ //
+ // e.g. I encountered the entry like the following.
+ // "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami); ..."
+ // This kind of entry is not able to be inserted via Android devices, but
+ // possible if the source of the data is already corrupted.
+ List<String> phoneNumberList = splitIfSeveralPhoneNumbersExist(phoneNumber);
+ if (phoneNumberList.isEmpty()) {
+ continue;
+ }
+ phoneLineExists = true;
+ for (String actualPhoneNumber : phoneNumberList) {
+ if (!phoneSet.contains(actualPhoneNumber)) {
+ final int format = VCardUtils.getPhoneNumberFormat(mVCardType);
+ final String formattedPhoneNumber =
+ PhoneNumberUtils.formatNumber(actualPhoneNumber, format);
+ phoneSet.add(actualPhoneNumber);
+ appendTelLine(type, label, formattedPhoneNumber, isPrimary);
+ }
+ }
+ }
+ }
+ }
+
+ if (!phoneLineExists && mIsDoCoMo) {
+ appendTelLine(Phone.TYPE_HOME, "", "", false);
+ }
+
+ return this;
+ }
+
+ private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) {
+ List<String> phoneList = new ArrayList<String>();
+
+ StringBuilder builder = new StringBuilder();
+ final int length = phoneNumber.length();
+ for (int i = 0; i < length; i++) {
+ final char ch = phoneNumber.charAt(i);
+ if (Character.isDigit(ch)) {
+ builder.append(ch);
+ } else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
+ phoneList.add(builder.toString());
+ builder = new StringBuilder();
+ }
+ }
+ if (builder.length() > 0) {
+ phoneList.add(builder.toString());
+ }
+
+ return phoneList;
+ }
+
+ public VCardBuilder appendEmails(final List<ContentValues> contentValuesList) {
+ boolean emailAddressExists = false;
+ if (contentValuesList != null) {
+ final Set<String> addressSet = new HashSet<String>();
+ for (ContentValues contentValues : contentValuesList) {
+ String emailAddress = contentValues.getAsString(Email.DATA);
+ if (emailAddress != null) {
+ emailAddress = emailAddress.trim();
+ }
+ if (TextUtils.isEmpty(emailAddress)) {
+ continue;
+ }
+ Integer typeAsObject = contentValues.getAsInteger(Email.TYPE);
+ final int type = (typeAsObject != null ?
+ typeAsObject : DEFAULT_EMAIL_TYPE);
+ final String label = contentValues.getAsString(Email.LABEL);
+ Integer isPrimaryAsInteger = contentValues.getAsInteger(Email.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ emailAddressExists = true;
+ if (!addressSet.contains(emailAddress)) {
+ addressSet.add(emailAddress);
+ appendEmailLine(type, label, emailAddress, isPrimary);
+ }
+ }
+ }
+
+ if (!emailAddressExists && mIsDoCoMo) {
+ appendEmailLine(Email.TYPE_HOME, "", "", false);
+ }
+
+ return this;
+ }
+
+ public VCardBuilder appendPostals(final List<ContentValues> contentValuesList) {
+ if (contentValuesList == null || contentValuesList.isEmpty()) {
+ if (mIsDoCoMo) {
+ mBuilder.append(VCardConstants.PROPERTY_ADR);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCardConstants.PARAM_TYPE_HOME);
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ } else {
+ if (mIsDoCoMo) {
+ appendPostalsForDoCoMo(contentValuesList);
+ } else {
+ appendPostalsForGeneric(contentValuesList);
+ }
+ }
+
+ return this;
+ }
+
+ private static final Map<Integer, Integer> sPostalTypePriorityMap;
+
+ static {
+ sPostalTypePriorityMap = new HashMap<Integer, Integer>();
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_HOME, 0);
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_WORK, 1);
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_OTHER, 2);
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_CUSTOM, 3);
+ }
+
+ /**
+ * Tries to append just one line. If there's no appropriate address
+ * information, append an empty line.
+ */
+ private void appendPostalsForDoCoMo(final List<ContentValues> contentValuesList) {
+ int currentPriority = Integer.MAX_VALUE;
+ int currentType = Integer.MAX_VALUE;
+ ContentValues currentContentValues = null;
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ final Integer typeAsInteger = contentValues.getAsInteger(StructuredPostal.TYPE);
+ final Integer priorityAsInteger = sPostalTypePriorityMap.get(typeAsInteger);
+ final int priority =
+ (priorityAsInteger != null ? priorityAsInteger : Integer.MAX_VALUE);
+ if (priority < currentPriority) {
+ currentPriority = priority;
+ currentType = typeAsInteger;
+ currentContentValues = contentValues;
+ if (priority == 0) {
+ break;
+ }
+ }
+ }
+
+ if (currentContentValues == null) {
+ Log.w(LOG_TAG, "Should not come here. Must have at least one postal data.");
+ return;
+ }
+
+ final String label = currentContentValues.getAsString(StructuredPostal.LABEL);
+ appendPostalLine(currentType, label, currentContentValues, false, true);
+ }
+
+ private void appendPostalsForGeneric(final List<ContentValues> contentValuesList) {
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ final Integer typeAsInteger = contentValues.getAsInteger(StructuredPostal.TYPE);
+ final int type = (typeAsInteger != null ?
+ typeAsInteger : DEFAULT_POSTAL_TYPE);
+ final String label = contentValues.getAsString(StructuredPostal.LABEL);
+ final Integer isPrimaryAsInteger =
+ contentValues.getAsInteger(StructuredPostal.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ appendPostalLine(type, label, contentValues, isPrimary, false);
+ }
+ }
+
+ private static class PostalStruct {
+ final boolean reallyUseQuotedPrintable;
+ final boolean appendCharset;
+ final String addressData;
+ public PostalStruct(final boolean reallyUseQuotedPrintable,
+ final boolean appendCharset, final String addressData) {
+ this.reallyUseQuotedPrintable = reallyUseQuotedPrintable;
+ this.appendCharset = appendCharset;
+ this.addressData = addressData;
+ }
+ }
+
+ /**
+ * @return null when there's no information available to construct the data.
+ */
+ private PostalStruct tryConstructPostalStruct(ContentValues contentValues) {
+ boolean reallyUseQuotedPrintable = false;
+ boolean appendCharset = false;
+
+ boolean dataArrayExists = false;
+ String[] dataArray = VCardUtils.getVCardPostalElements(contentValues);
+ for (String data : dataArray) {
+ if (!TextUtils.isEmpty(data)) {
+ dataArrayExists = true;
+ if (!appendCharset && !VCardUtils.containsOnlyPrintableAscii(data)) {
+ appendCharset = true;
+ }
+ if (mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(data)) {
+ reallyUseQuotedPrintable = true;
+ break;
+ }
+ }
+ }
+
+ if (dataArrayExists) {
+ StringBuffer addressBuffer = new StringBuffer();
+ boolean first = true;
+ for (String data : dataArray) {
+ if (first) {
+ first = false;
+ } else {
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ }
+ if (!TextUtils.isEmpty(data)) {
+ if (reallyUseQuotedPrintable) {
+ addressBuffer.append(encodeQuotedPrintable(data));
+ } else {
+ addressBuffer.append(escapeCharacters(data));
+ }
+ }
+ }
+ return new PostalStruct(reallyUseQuotedPrintable, appendCharset,
+ addressBuffer.toString());
+ }
+
+ String formattedAddress =
+ contentValues.getAsString(StructuredPostal.FORMATTED_ADDRESS);
+ if (!TextUtils.isEmpty(formattedAddress)) {
+ reallyUseQuotedPrintable =
+ !VCardUtils.containsOnlyPrintableAscii(formattedAddress);
+ appendCharset =
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedAddress);
+ if (reallyUseQuotedPrintable) {
+ formattedAddress = encodeQuotedPrintable(formattedAddress);
+ } else {
+ formattedAddress = escapeCharacters(formattedAddress);
+ }
+ // We use the second value ("Extended Address").
+ //
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal
+ // ; Code, Country Name
+ StringBuffer addressBuffer = new StringBuffer();
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(formattedAddress);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ return new PostalStruct(
+ reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ }
+ return null; // There's no data available.
+ }
+
+ public VCardBuilder appendIms(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ final Integer protocolAsObject = contentValues.getAsInteger(Im.PROTOCOL);
+ if (protocolAsObject == null) {
+ continue;
+ }
+ final String propertyName = VCardUtils.getPropertyNameForIm(protocolAsObject);
+ if (propertyName == null) {
+ continue;
+ }
+ String data = contentValues.getAsString(Im.DATA);
+ if (data != null) {
+ data = data.trim();
+ }
+ if (TextUtils.isEmpty(data)) {
+ continue;
+ }
+ final String typeAsString;
+ {
+ final Integer typeAsInteger = contentValues.getAsInteger(Im.TYPE);
+ switch (typeAsInteger != null ? typeAsInteger : Im.TYPE_OTHER) {
+ case Im.TYPE_HOME: {
+ typeAsString = VCardConstants.PARAM_TYPE_HOME;
+ break;
+ }
+ case Im.TYPE_WORK: {
+ typeAsString = VCardConstants.PARAM_TYPE_WORK;
+ break;
+ }
+ case Im.TYPE_CUSTOM: {
+ final String label = contentValues.getAsString(Im.LABEL);
+ typeAsString = (label != null ? "X-" + label : null);
+ break;
+ }
+ case Im.TYPE_OTHER: // Ignore
+ default: {
+ typeAsString = null;
+ break;
+ }
+ }
+ }
+
+ final List<String> parameterList = new ArrayList<String>();
+ if (!TextUtils.isEmpty(typeAsString)) {
+ parameterList.add(typeAsString);
+ }
+ final Integer isPrimaryAsInteger = contentValues.getAsInteger(Im.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+
+ appendLineWithCharsetAndQPDetection(propertyName, parameterList, data);
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendWebsites(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ String website = contentValues.getAsString(Website.URL);
+ if (website != null) {
+ website = website.trim();
+ }
+
+ // Note: vCard 3.0 does not allow any parameter addition toward "URL"
+ // property, while there's no document in vCard 2.1.
+ //
+ // TODO: Should we allow adding it when appropriate?
+ // (Actually, we drop some data. Using "group.X-URL-TYPE" or something
+ // may help)
+ if (!TextUtils.isEmpty(website)) {
+ appendLine(VCardConstants.PROPERTY_URL, website);
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendOrganizations(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ String company = contentValues.getAsString(Organization.COMPANY);
+ if (company != null) {
+ company = company.trim();
+ }
+ String department = contentValues.getAsString(Organization.DEPARTMENT);
+ if (department != null) {
+ department = department.trim();
+ }
+ String title = contentValues.getAsString(Organization.TITLE);
+ if (title != null) {
+ title = title.trim();
+ }
+
+ StringBuilder orgBuilder = new StringBuilder();
+ if (!TextUtils.isEmpty(company)) {
+ orgBuilder.append(company);
+ }
+ if (!TextUtils.isEmpty(department)) {
+ if (orgBuilder.length() > 0) {
+ orgBuilder.append(';');
+ }
+ orgBuilder.append(department);
+ }
+ final String orgline = orgBuilder.toString();
+ appendLine(VCardConstants.PROPERTY_ORG, orgline,
+ !VCardUtils.containsOnlyPrintableAscii(orgline),
+ (mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(orgline)));
+
+ if (!TextUtils.isEmpty(title)) {
+ appendLine(VCardConstants.PROPERTY_TITLE, title,
+ !VCardUtils.containsOnlyPrintableAscii(title),
+ (mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendPhotos(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ byte[] data = contentValues.getAsByteArray(Photo.PHOTO);
+ if (data == null) {
+ continue;
+ }
+ final String photoType;
+ // Use some heuristics for guessing the format of the image.
+ // TODO: there should be some general API for detecting the file format.
+ if (data.length >= 3 && data[0] == 'G' && data[1] == 'I'
+ && data[2] == 'F') {
+ photoType = "GIF";
+ } else if (data.length >= 4 && data[0] == (byte) 0x89
+ && data[1] == 'P' && data[2] == 'N' && data[3] == 'G') {
+ // Note: vCard 2.1 officially does not support PNG, but we may have it and
+ // using X- word like "X-PNG" may not let importers know it is PNG.
+ // So we use the String "PNG" as is...
+ photoType = "PNG";
+ } else if (data.length >= 2 && data[0] == (byte) 0xff
+ && data[1] == (byte) 0xd8) {
+ photoType = "JPEG";
+ } else {
+ Log.d(LOG_TAG, "Unknown photo type. Ignore.");
+ continue;
+ }
+ final String photoString = new String(Base64.encodeBase64(data));
+ if (!TextUtils.isEmpty(photoString)) {
+ appendPhotoLine(photoString, photoType);
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendNotes(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ if (mOnlyOneNoteFieldIsAvailable) {
+ StringBuilder noteBuilder = new StringBuilder();
+ boolean first = true;
+ for (final ContentValues contentValues : contentValuesList) {
+ String note = contentValues.getAsString(Note.NOTE);
+ if (note == null) {
+ note = "";
+ }
+ if (note.length() > 0) {
+ if (first) {
+ first = false;
+ } else {
+ noteBuilder.append('\n');
+ }
+ noteBuilder.append(note);
+ }
+ }
+ 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));
+ appendLine(VCardConstants.PROPERTY_NOTE, noteStr,
+ shouldAppendCharsetInfo, reallyUseQuotedPrintable);
+ } else {
+ for (ContentValues contentValues : contentValuesList) {
+ final String noteStr = contentValues.getAsString(Note.NOTE);
+ if (!TextUtils.isEmpty(noteStr)) {
+ final boolean shouldAppendCharsetInfo =
+ !VCardUtils.containsOnlyPrintableAscii(noteStr);
+ final boolean reallyUseQuotedPrintable =
+ (mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
+ appendLine(VCardConstants.PROPERTY_NOTE, noteStr,
+ shouldAppendCharsetInfo, reallyUseQuotedPrintable);
+ }
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendEvents(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ String primaryBirthday = null;
+ String secondaryBirthday = null;
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ final Integer eventTypeAsInteger = contentValues.getAsInteger(Event.TYPE);
+ final int eventType;
+ if (eventTypeAsInteger != null) {
+ eventType = eventTypeAsInteger;
+ } else {
+ eventType = Event.TYPE_OTHER;
+ }
+ if (eventType == Event.TYPE_BIRTHDAY) {
+ final String birthdayCandidate = contentValues.getAsString(Event.START_DATE);
+ if (birthdayCandidate == null) {
+ continue;
+ }
+ final Integer isSuperPrimaryAsInteger =
+ contentValues.getAsInteger(Event.IS_SUPER_PRIMARY);
+ final boolean isSuperPrimary = (isSuperPrimaryAsInteger != null ?
+ (isSuperPrimaryAsInteger > 0) : false);
+ if (isSuperPrimary) {
+ // "super primary" birthday should the prefered one.
+ primaryBirthday = birthdayCandidate;
+ break;
+ }
+ final Integer isPrimaryAsInteger =
+ contentValues.getAsInteger(Event.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ if (isPrimary) {
+ // We don't break here since "super primary" birthday may exist later.
+ primaryBirthday = birthdayCandidate;
+ } else if (secondaryBirthday == null) {
+ // First entry is set to the "secondary" candidate.
+ secondaryBirthday = birthdayCandidate;
+ }
+ } else if (mUsesAndroidProperty) {
+ // Event types other than Birthday is not supported by vCard.
+ appendAndroidSpecificProperty(Event.CONTENT_ITEM_TYPE, contentValues);
+ }
+ }
+ if (primaryBirthday != null) {
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_BDAY,
+ primaryBirthday.trim());
+ } else if (secondaryBirthday != null){
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_BDAY,
+ secondaryBirthday.trim());
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendRelation(final List<ContentValues> contentValuesList) {
+ if (mUsesAndroidProperty && contentValuesList != null) {
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ appendAndroidSpecificProperty(Relation.CONTENT_ITEM_TYPE, contentValues);
+ }
+ }
+ return this;
+ }
+
+ public void appendPostalLine(final int type, final String label,
+ final ContentValues contentValues,
+ final boolean isPrimary, final boolean emitLineEveryTime) {
+ final boolean reallyUseQuotedPrintable;
+ final boolean appendCharset;
+ final String addressValue;
+ {
+ PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
+ if (postalStruct == null) {
+ if (emitLineEveryTime) {
+ reallyUseQuotedPrintable = false;
+ appendCharset = false;
+ addressValue = "";
+ } else {
+ return;
+ }
+ } else {
+ reallyUseQuotedPrintable = postalStruct.reallyUseQuotedPrintable;
+ appendCharset = postalStruct.appendCharset;
+ addressValue = postalStruct.addressData;
+ }
+ }
+
+ List<String> parameterList = new ArrayList<String>();
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+ switch (type) {
+ case StructuredPostal.TYPE_HOME: {
+ parameterList.add(VCardConstants.PARAM_TYPE_HOME);
+ break;
+ }
+ case StructuredPostal.TYPE_WORK: {
+ parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+ break;
+ }
+ case StructuredPostal.TYPE_CUSTOM: {
+ if (!TextUtils.isEmpty(label)
+ && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+ // We're not sure whether the label is valid in the spec
+ // ("IANA-token" in the vCard 3.0 is unclear...)
+ // Just for safety, we add "X-" at the beggining of each label.
+ // Also checks the label obeys with vCard 3.0 spec.
+ parameterList.add("X-" + label);
+ }
+ break;
+ }
+ case StructuredPostal.TYPE_OTHER: {
+ break;
+ }
+ default: {
+ Log.e(LOG_TAG, "Unknown StructuredPostal type: " + type);
+ break;
+ }
+ }
+
+ mBuilder.append(VCardConstants.PROPERTY_ADR);
+ if (!parameterList.isEmpty()) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameters(parameterList);
+ }
+ if (appendCharset) {
+ // 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 parameter in vCard 3.0.
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(addressValue);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendEmailLine(final int type, final String label,
+ final String rawValue, final boolean isPrimary) {
+ final String typeAsString;
+ switch (type) {
+ case Email.TYPE_CUSTOM: {
+ if (VCardUtils.isMobilePhoneLabel(label)) {
+ typeAsString = VCardConstants.PARAM_TYPE_CELL;
+ } else if (!TextUtils.isEmpty(label)
+ && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+ typeAsString = "X-" + label;
+ } else {
+ typeAsString = null;
+ }
+ break;
+ }
+ case Email.TYPE_HOME: {
+ typeAsString = VCardConstants.PARAM_TYPE_HOME;
+ break;
+ }
+ case Email.TYPE_WORK: {
+ typeAsString = VCardConstants.PARAM_TYPE_WORK;
+ break;
+ }
+ case Email.TYPE_OTHER: {
+ typeAsString = null;
+ break;
+ }
+ case Email.TYPE_MOBILE: {
+ typeAsString = VCardConstants.PARAM_TYPE_CELL;
+ break;
+ }
+ default: {
+ Log.e(LOG_TAG, "Unknown Email type: " + type);
+ typeAsString = null;
+ break;
+ }
+ }
+
+ final List<String> parameterList = new ArrayList<String>();
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+ if (!TextUtils.isEmpty(typeAsString)) {
+ parameterList.add(typeAsString);
+ }
+
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_EMAIL, parameterList,
+ rawValue);
+ }
+
+ public void appendTelLine(final Integer typeAsInteger, final String label,
+ final String encodedValue, boolean isPrimary) {
+ mBuilder.append(VCardConstants.PROPERTY_TEL);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+
+ final int type;
+ if (typeAsInteger == null) {
+ type = Phone.TYPE_OTHER;
+ } else {
+ type = typeAsInteger;
+ }
+
+ ArrayList<String> parameterList = new ArrayList<String>();
+ switch (type) {
+ case Phone.TYPE_HOME: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_HOME));
+ break;
+ }
+ case Phone.TYPE_WORK: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_WORK));
+ break;
+ }
+ case Phone.TYPE_FAX_HOME: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_HOME, VCardConstants.PARAM_TYPE_FAX));
+ break;
+ }
+ case Phone.TYPE_FAX_WORK: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_WORK, VCardConstants.PARAM_TYPE_FAX));
+ break;
+ }
+ case Phone.TYPE_MOBILE: {
+ parameterList.add(VCardConstants.PARAM_TYPE_CELL);
+ break;
+ }
+ case Phone.TYPE_PAGER: {
+ if (mIsDoCoMo) {
+ // Not sure about the reason, but previous implementation had
+ // used "VOICE" instead of "PAGER"
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ } else {
+ parameterList.add(VCardConstants.PARAM_TYPE_PAGER);
+ }
+ break;
+ }
+ case Phone.TYPE_OTHER: {
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ break;
+ }
+ case Phone.TYPE_CAR: {
+ parameterList.add(VCardConstants.PARAM_TYPE_CAR);
+ break;
+ }
+ case Phone.TYPE_COMPANY_MAIN: {
+ // There's no relevant field in vCard (at least 2.1).
+ parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+ isPrimary = true;
+ break;
+ }
+ case Phone.TYPE_ISDN: {
+ parameterList.add(VCardConstants.PARAM_TYPE_ISDN);
+ break;
+ }
+ case Phone.TYPE_MAIN: {
+ isPrimary = true;
+ break;
+ }
+ case Phone.TYPE_OTHER_FAX: {
+ parameterList.add(VCardConstants.PARAM_TYPE_FAX);
+ break;
+ }
+ case Phone.TYPE_TELEX: {
+ parameterList.add(VCardConstants.PARAM_TYPE_TLX);
+ break;
+ }
+ case Phone.TYPE_WORK_MOBILE: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_WORK, VCardConstants.PARAM_TYPE_CELL));
+ break;
+ }
+ case Phone.TYPE_WORK_PAGER: {
+ parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+ // See above.
+ if (mIsDoCoMo) {
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ } else {
+ parameterList.add(VCardConstants.PARAM_TYPE_PAGER);
+ }
+ break;
+ }
+ case Phone.TYPE_MMS: {
+ parameterList.add(VCardConstants.PARAM_TYPE_MSG);
+ break;
+ }
+ case Phone.TYPE_CUSTOM: {
+ if (TextUtils.isEmpty(label)) {
+ // Just ignore the custom type.
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ } else if (VCardUtils.isMobilePhoneLabel(label)) {
+ parameterList.add(VCardConstants.PARAM_TYPE_CELL);
+ } else {
+ final String upperLabel = label.toUpperCase();
+ if (VCardUtils.isValidInV21ButUnknownToContactsPhoteType(upperLabel)) {
+ parameterList.add(upperLabel);
+ } else if (VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+ // Note: Strictly, vCard 2.1 does not allow "X-" parameter without
+ // "TYPE=" string.
+ parameterList.add("X-" + label);
+ }
+ }
+ break;
+ }
+ case Phone.TYPE_RADIO:
+ case Phone.TYPE_TTY_TDD:
+ default: {
+ break;
+ }
+ }
+
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+
+ if (parameterList.isEmpty()) {
+ appendUncommonPhoneType(mBuilder, type);
+ } else {
+ appendTypeParameters(parameterList);
+ }
+
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedValue);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ /**
+ * Appends phone type string which may not be available in some devices.
+ */
+ private void appendUncommonPhoneType(final StringBuilder builder, final Integer type) {
+ if (mIsDoCoMo) {
+ // The previous implementation for DoCoMo had been conservative
+ // about miscellaneous types.
+ builder.append(VCardConstants.PARAM_TYPE_VOICE);
+ } else {
+ String phoneType = VCardUtils.getPhoneTypeString(type);
+ if (phoneType != null) {
+ appendTypeParameter(phoneType);
+ } else {
+ Log.e(LOG_TAG, "Unknown or unsupported (by vCard) Phone type: " + type);
+ }
+ }
+ }
+
+ /**
+ * @param encodedValue Must be encoded by BASE64
+ * @param photoType
+ */
+ public void appendPhotoLine(final String encodedValue, final String photoType) {
+ StringBuilder tmpBuilder = new StringBuilder();
+ tmpBuilder.append(VCardConstants.PROPERTY_PHOTO);
+ tmpBuilder.append(VCARD_PARAM_SEPARATOR);
+ if (mIsV30) {
+ tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V30);
+ } else {
+ tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V21);
+ }
+ tmpBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameter(tmpBuilder, photoType);
+ tmpBuilder.append(VCARD_DATA_SEPARATOR);
+ tmpBuilder.append(encodedValue);
+
+ final String tmpStr = tmpBuilder.toString();
+ tmpBuilder = new StringBuilder();
+ int lineCount = 0;
+ final int length = tmpStr.length();
+ final int maxNumForFirstLine = VCardConstants.MAX_CHARACTER_NUMS_BASE64_V30
+ - VCARD_END_OF_LINE.length();
+ final int maxNumInGeneral = maxNumForFirstLine - VCARD_WS.length();
+ int maxNum = maxNumForFirstLine;
+ for (int i = 0; i < length; i++) {
+ tmpBuilder.append(tmpStr.charAt(i));
+ lineCount++;
+ if (lineCount > maxNum) {
+ tmpBuilder.append(VCARD_END_OF_LINE);
+ tmpBuilder.append(VCARD_WS);
+ maxNum = maxNumInGeneral;
+ lineCount = 0;
+ }
+ }
+ mBuilder.append(tmpBuilder.toString());
+ mBuilder.append(VCARD_END_OF_LINE);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendAndroidSpecificProperty(final String mimeType, ContentValues contentValues) {
+ List<String> rawValueList = new ArrayList<String>();
+ rawValueList.add(mimeType);
+ final List<String> columnNameList;
+ if (!sAllowedAndroidPropertySet.contains(mimeType)) {
+ return;
+ }
+
+ for (int i = 1; i <= VCardConstants.MAX_DATA_COLUMN; i++) {
+ String value = contentValues.getAsString("data" + i);
+ if (value == null) {
+ value = "";
+ }
+ rawValueList.add(value);
+ }
+
+ appendLineWithCharsetAndQPDetection(
+ VCardConstants.PROPERTY_X_ANDROID_CUSTOM, rawValueList);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(final String propertyName,
+ final String rawValue) {
+ appendLineWithCharsetAndQPDetection(propertyName, null, rawValue);
+ }
+
+ private void appendLineWithCharsetAndQPDetection(
+ final String propertyName, final List<String> rawValueList) {
+ appendLineWithCharsetAndQPDetection(propertyName, null, rawValueList);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(final String propertyName,
+ final List<String> parameterList, final String rawValue) {
+ final boolean needCharset =
+ (mUsesQuotedPrintable && !VCardUtils.containsOnlyPrintableAscii(rawValue));
+ final boolean reallyUseQuotedPrintable =
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValue);
+ appendLine(propertyName, parameterList,
+ rawValue, needCharset, reallyUseQuotedPrintable);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(final String propertyName,
+ final List<String> parameterList, final List<String> rawValueList) {
+ boolean needCharset = false;
+ boolean reallyUseQuotedPrintable = false;
+ for (String rawValue : rawValueList) {
+ if (!needCharset && mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyPrintableAscii(rawValue)) {
+ needCharset = true;
+ }
+ if (!reallyUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValue)) {
+ reallyUseQuotedPrintable = true;
+ }
+ if (needCharset && reallyUseQuotedPrintable) {
+ break;
+ }
+ }
+
+ appendLine(propertyName, parameterList, rawValueList,
+ needCharset, reallyUseQuotedPrintable);
+ }
+
+ /**
+ * Appends one line with a given property name and value.
+ */
+ public void appendLine(final String propertyName, final String rawValue) {
+ appendLine(propertyName, rawValue, false, false);
+ }
+
+ public void appendLine(final String propertyName, final List<String> rawValueList) {
+ appendLine(propertyName, rawValueList, false, false);
+ }
+
+ public void appendLine(final String propertyName,
+ final String rawValue, final boolean needCharset, boolean needQuotedPrintable) {
+ appendLine(propertyName, null, rawValue, needCharset, needQuotedPrintable);
+ }
+
+ public void appendLine(final String propertyName, final List<String> parameterList,
+ final String rawValue) {
+ appendLine(propertyName, parameterList, rawValue, false, false);
+ }
+
+ public void appendLine(final String propertyName, final List<String> parameterList,
+ final String rawValue, final boolean needCharset,
+ boolean needQuotedPrintable) {
+ mBuilder.append(propertyName);
+ if (parameterList != null && parameterList.size() > 0) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameters(parameterList);
+ }
+ if (needCharset) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+
+ final String encodedValue;
+ if (needQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ encodedValue = encodeQuotedPrintable(rawValue);
+ } else {
+ // TODO: one line may be too huge, which may be invalid in vCard spec, though
+ // several (even well-known) applications do not care this.
+ encodedValue = escapeCharacters(rawValue);
+ }
+
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedValue);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendLine(final String propertyName, final List<String> rawValueList,
+ final boolean needCharset, boolean needQuotedPrintable) {
+ appendLine(propertyName, null, rawValueList, needCharset, needQuotedPrintable);
+ }
+
+ public void appendLine(final String propertyName, final List<String> parameterList,
+ final List<String> rawValueList, final boolean needCharset,
+ final boolean needQuotedPrintable) {
+ mBuilder.append(propertyName);
+ if (parameterList != null && parameterList.size() > 0) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameters(parameterList);
+ }
+ if (needCharset) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ boolean first = true;
+ for (String rawValue : rawValueList) {
+ final String encodedValue;
+ if (needQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ encodedValue = encodeQuotedPrintable(rawValue);
+ } else {
+ // TODO: one line may be too huge, which may be invalid in vCard 3.0
+ // (which says "When generating a content line, lines longer than
+ // 75 characters SHOULD be folded"), though several
+ // (even well-known) applications do not care this.
+ encodedValue = escapeCharacters(rawValue);
+ }
+
+ if (first) {
+ first = false;
+ } else {
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ }
+ mBuilder.append(encodedValue);
+ }
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ /**
+ * VCARD_PARAM_SEPARATOR must be appended before this method being called.
+ */
+ private void appendTypeParameters(final List<String> types) {
+ // We may have to make this comma separated form like "TYPE=DOM,WORK" in the future,
+ // which would be recommended way in vcard 3.0 though not valid in vCard 2.1.
+ boolean first = true;
+ for (String type : types) {
+ if (first) {
+ first = false;
+ } else {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ }
+ appendTypeParameter(type);
+ }
+ }
+
+ /**
+ * VCARD_PARAM_SEPARATOR must be appended before this method being called.
+ */
+ private void appendTypeParameter(final String type) {
+ appendTypeParameter(mBuilder, type);
+ }
+
+ private void appendTypeParameter(final StringBuilder builder, final String type) {
+ // Refrain from using appendType() so that "TYPE=" is not be appended when the
+ // device is DoCoMo's (just for safety).
+ //
+ // Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF"
+ if ((mIsV30 || mAppendTypeParamName) && !mIsDoCoMo) {
+ builder.append(VCardConstants.PARAM_TYPE).append(VCARD_PARAM_EQUAL);
+ }
+ builder.append(type);
+ }
+
+ /**
+ * Returns true when the property line should contain charset parameter
+ * information. This method may return true even when vCard version is 3.0.
+ *
+ * Strictly, adding charset information is invalid in VCard 3.0.
+ * However we'll add the info only when charset we use is not UTF-8
+ * in vCard 3.0 format, since parser side may be able to use the charset
+ * via this field, though we may encounter another problem by adding it.
+ *
+ * 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.
+ */
+ private boolean shouldAppendCharsetParam(String...propertyValueList) {
+ if (!mShouldAppendCharsetParam) {
+ return false;
+ }
+ for (String propertyValue : propertyValueList) {
+ if (!VCardUtils.containsOnlyPrintableAscii(propertyValue)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private String encodeQuotedPrintable(String str) {
+ if (TextUtils.isEmpty(str)) {
+ return "";
+ }
+ {
+ // Replace "\n" and "\r" with "\r\n".
+ final StringBuilder tmpBuilder = new StringBuilder();
+ int length = str.length();
+ for (int i = 0; i < length; i++) {
+ char ch = str.charAt(i);
+ if (ch == '\r') {
+ if (i + 1 < length && str.charAt(i + 1) == '\n') {
+ i++;
+ }
+ tmpBuilder.append("\r\n");
+ } else if (ch == '\n') {
+ tmpBuilder.append("\r\n");
+ } else {
+ tmpBuilder.append(ch);
+ }
+ }
+ str = tmpBuilder.toString();
+ }
+
+ final StringBuilder tmpBuilder = new StringBuilder();
+ int index = 0;
+ int lineCount = 0;
+ byte[] strArray = null;
+
+ try {
+ strArray = str.getBytes(mCharsetString);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
+ + "Try default charset");
+ strArray = str.getBytes();
+ }
+ while (index < strArray.length) {
+ tmpBuilder.append(String.format("=%02X", strArray[index]));
+ index += 1;
+ lineCount += 3;
+
+ if (lineCount >= 67) {
+ // Specification requires CRLF must be inserted before the
+ // length of the line
+ // becomes more than 76.
+ // Assuming that the next character is a multi-byte character,
+ // it will become
+ // 6 bytes.
+ // 76 - 6 - 3 = 67
+ tmpBuilder.append("=\r\n");
+ lineCount = 0;
+ }
+ }
+
+ return tmpBuilder.toString();
+ }
+
+
+ /**
+ * Append '\' to the characters which should be escaped. The character set is different
+ * not only between vCard 2.1 and vCard 3.0 but also among each device.
+ *
+ * Note that Quoted-Printable string must not be input here.
+ */
+ @SuppressWarnings("fallthrough")
+ private String escapeCharacters(final String unescaped) {
+ if (TextUtils.isEmpty(unescaped)) {
+ return "";
+ }
+
+ final StringBuilder tmpBuilder = new StringBuilder();
+ final int length = unescaped.length();
+ for (int i = 0; i < length; i++) {
+ final char ch = unescaped.charAt(i);
+ switch (ch) {
+ case ';': {
+ tmpBuilder.append('\\');
+ tmpBuilder.append(';');
+ break;
+ }
+ case '\r': {
+ if (i + 1 < length) {
+ char nextChar = unescaped.charAt(i);
+ if (nextChar == '\n') {
+ break;
+ } else {
+ // fall through
+ }
+ } else {
+ // fall through
+ }
+ }
+ case '\n': {
+ // In vCard 2.1, there's no specification about this, while
+ // vCard 3.0 explicitly requires this should be encoded to "\n".
+ tmpBuilder.append("\\n");
+ break;
+ }
+ case '\\': {
+ if (mIsV30) {
+ tmpBuilder.append("\\\\");
+ break;
+ } else {
+ // fall through
+ }
+ }
+ case '<':
+ case '>': {
+ if (mIsDoCoMo) {
+ tmpBuilder.append('\\');
+ tmpBuilder.append(ch);
+ } else {
+ tmpBuilder.append(ch);
+ }
+ break;
+ }
+ case ',': {
+ if (mIsV30) {
+ tmpBuilder.append("\\,");
+ } else {
+ tmpBuilder.append(ch);
+ }
+ break;
+ }
+ default: {
+ tmpBuilder.append(ch);
+ break;
+ }
+ }
+ }
+ return tmpBuilder.toString();
+ }
+
+ @Override
+ public String toString() {
+ if (!mEndAppended) {
+ if (mIsDoCoMo) {
+ appendLine(VCardConstants.PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
+ appendLine(VCardConstants.PROPERTY_X_REDUCTION, "");
+ appendLine(VCardConstants.PROPERTY_X_NO, "");
+ appendLine(VCardConstants.PROPERTY_X_DCM_HMN_MODE, "");
+ }
+ appendLine(VCardConstants.PROPERTY_END, VCARD_DATA_VCARD);
+ mEndAppended = true;
+ }
+ return mBuilder.toString();
+ }
+}
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index d9f38bb..c2d628f 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -38,17 +38,15 @@
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
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.TextUtils;
import android.text.format.Time;
import android.util.CharsetUtils;
import android.util.Log;
-import org.apache.commons.codec.binary.Base64;
-
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -131,21 +129,6 @@
private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING";
private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
- private static final String VCARD_DATA_VCARD = "VCARD";
- private static final String VCARD_DATA_PUBLIC = "PUBLIC";
-
- private static final String VCARD_PARAM_SEPARATOR = ";";
- private static final String VCARD_END_OF_LINE = "\r\n";
- private static final String VCARD_DATA_SEPARATOR = ":";
- private static final String VCARD_ITEM_SEPARATOR = ";";
- private static final String VCARD_WS = " ";
- private static final String VCARD_PARAM_EQUAL = "=";
-
- private static final String VCARD_PARAM_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
-
- private static final String VCARD_PARAM_ENCODING_BASE64_V21 = "ENCODING=BASE64";
- private static final String VCARD_PARAM_ENCODING_BASE64_V30 = "ENCODING=b";
-
private static final String SHIFT_JIS = "SHIFT_JIS";
private static final String UTF_8 = "UTF-8";
@@ -171,19 +154,19 @@
builder.appendQueryParameter(Data.FOR_EXPORT_ONLY, "1");
sDataRequestUri = builder.build();
sImMap = new HashMap<Integer, String>();
- sImMap.put(Im.PROTOCOL_AIM, Constants.PROPERTY_X_AIM);
- sImMap.put(Im.PROTOCOL_MSN, Constants.PROPERTY_X_MSN);
- sImMap.put(Im.PROTOCOL_YAHOO, Constants.PROPERTY_X_YAHOO);
- sImMap.put(Im.PROTOCOL_ICQ, Constants.PROPERTY_X_ICQ);
- sImMap.put(Im.PROTOCOL_JABBER, Constants.PROPERTY_X_JABBER);
- sImMap.put(Im.PROTOCOL_SKYPE, Constants.PROPERTY_X_SKYPE_USERNAME);
+ sImMap.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
+ sImMap.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
+ sImMap.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
+ sImMap.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
+ sImMap.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
+ sImMap.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
// Google talk is a special case.
// TODO: incomplete. Implement properly
sPrimaryPropertyNameSet = new HashSet<String>();
- sPrimaryPropertyNameSet.add(Constants.PROPERTY_N);
- sPrimaryPropertyNameSet.add(Constants.PROPERTY_FN);
- sPrimaryPropertyNameSet.add(Constants.PROPERTY_SOUND);
+ sPrimaryPropertyNameSet.add(VCardConstants.PROPERTY_N);
+ sPrimaryPropertyNameSet.add(VCardConstants.PROPERTY_FN);
+ sPrimaryPropertyNameSet.add(VCardConstants.PROPERTY_SOUND);
}
public static interface OneEntryHandler {
@@ -294,26 +277,12 @@
private final boolean mCareHandlerErrors;
private final ContentResolver mContentResolver;
- // Convenient member variables about the restriction of the vCard format.
- // Used for not calling the same methods returning same results.
- private final boolean mIsV30;
- private final boolean mIsJapaneseMobilePhone;
- private final boolean mOnlyOneNoteFieldIsAvailable;
private final boolean mIsDoCoMo;
- private final boolean mUsesQuotedPrintable;
- private final boolean mUsesAndroidProperty;
- private final boolean mUsesDefactProperty;
- private final boolean mUsesUtf8;
private final boolean mUsesShiftJis;
- private final boolean mAppendTypeParamName;
- private final boolean mRefrainsQPToPrimaryProperties;
- private final boolean mNeedsToConvertPhoneticString;
-
private Cursor mCursor;
private int mIdColumn;
private final String mCharsetString;
- private final String mVCardCharsetParameter;
private boolean mTerminateIsCalled;
final private List<OneEntryHandler> mHandlerList;
@@ -361,18 +330,8 @@
mCareHandlerErrors = careHandlerErrors;
mContentResolver = context.getContentResolver();
- mIsV30 = VCardConfig.isV30(vcardType);
- mUsesQuotedPrintable = VCardConfig.usesQuotedPrintable(vcardType);
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
- mIsJapaneseMobilePhone = VCardConfig.needsToConvertPhoneticString(vcardType);
- mOnlyOneNoteFieldIsAvailable = VCardConfig.onlyOneNoteFieldIsAvailable(vcardType);
- mUsesAndroidProperty = VCardConfig.usesAndroidSpecificProperty(vcardType);
- mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
- mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
- mRefrainsQPToPrimaryProperties = VCardConfig.refrainsQPToPrimaryProperties(vcardType);
- mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType);
- mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType);
mHandlerList = new ArrayList<OneEntryHandler>();
if (mIsDoCoMo) {
@@ -384,10 +343,6 @@
charset = SHIFT_JIS;
}
mCharsetString = charset;
- // Do not use mCharsetString bellow since it is different from "SHIFT_JIS" but
- // may be "DOCOMO_SHIFT_JIS" or something like that (internal expression used in
- // Android, not shown to the public).
- mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
} else if (mUsesShiftJis) {
String charset;
try {
@@ -397,10 +352,8 @@
charset = SHIFT_JIS;
}
mCharsetString = charset;
- mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
} else {
mCharsetString = UTF_8;
- mVCardCharsetParameter = "CHARSET=" + UTF_8;
}
}
@@ -583,36 +536,19 @@
return "";
}
- final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
- if (mIsV30) {
- appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
- } else {
- appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
- }
-
- appendStructuredNames(builder, contentValuesListMap);
- appendNickNames(builder, contentValuesListMap);
- appendPhones(builder, contentValuesListMap);
- appendEmails(builder, contentValuesListMap);
- appendPostals(builder, contentValuesListMap);
- appendIms(builder, contentValuesListMap);
- appendWebsites(builder, contentValuesListMap);
- appendBirthday(builder, contentValuesListMap);
- appendOrganizations(builder, contentValuesListMap);
- appendPhotos(builder, contentValuesListMap);
- appendNotes(builder, contentValuesListMap);
- // TODO: GroupMembership, Relation, Event other than birthday.
-
- if (mIsDoCoMo) {
- appendVCardLine(builder, Constants.PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
- appendVCardLine(builder, Constants.PROPERTY_X_REDUCTION, "");
- appendVCardLine(builder, Constants.PROPERTY_X_NO, "");
- appendVCardLine(builder, Constants.PROPERTY_X_DCM_HMN_MODE, "");
- }
-
- appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
-
+ final VCardBuilder builder = new VCardBuilder(mVCardType);
+ builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
+ .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
+ .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
+ .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
+ .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
+ .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
+ .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
+ .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
+ .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
+ .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
+ .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
+ .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
return builder.toString();
}
@@ -625,8 +561,7 @@
try {
mCursor.close();
} catch (SQLiteException e) {
- Log.e(LOG_TAG, "SQLiteException on Cursor#close(): "
- + e.getMessage());
+ Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
}
mCursor = null;
}
@@ -662,1752 +597,27 @@
return mErrorReason;
}
- private void appendStructuredNames(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(StructuredName.CONTENT_ITEM_TYPE);
- if (contentValuesList != null && contentValuesList.size() > 0) {
- appendStructuredNamesInternal(builder, contentValuesList);
- } else if (mIsDoCoMo) {
- appendVCardLine(builder, Constants.PROPERTY_N, "");
- } else if (mIsV30) {
- // vCard 3.0 requires "N" and "FN" properties.
- appendVCardLine(builder, Constants.PROPERTY_N, "");
- appendVCardLine(builder, Constants.PROPERTY_FN, "");
- }
- }
-
- private boolean containsNonEmptyName(final ContentValues contentValues) {
- 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 phoneticFamilyName =
- contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
- final String phoneticMiddleName =
- contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
- final String phoneticGivenName =
- contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
- final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
- return !(TextUtils.isEmpty(familyName) && TextUtils.isEmpty(middleName) &&
- TextUtils.isEmpty(givenName) && TextUtils.isEmpty(prefix) &&
- TextUtils.isEmpty(suffix) && TextUtils.isEmpty(phoneticFamilyName) &&
- TextUtils.isEmpty(phoneticMiddleName) && TextUtils.isEmpty(phoneticGivenName) &&
- TextUtils.isEmpty(displayName));
- }
-
- 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;
- ContentValues subprimaryContentValues = null;
- for (ContentValues contentValues : contentValuesList) {
- if (contentValues == null){
- continue;
- }
- 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) {
- // We choose the first "primary" ContentValues
- // if "super primary" ContentValues does not exist.
- final Integer isPrimary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
- if (isPrimary != null && isPrimary > 0 &&
- containsNonEmptyName(contentValues)) {
- primaryContentValues = contentValues;
- // Do not break, since there may be ContentValues with "super primary"
- // afterword.
- } else if (subprimaryContentValues == null &&
- containsNonEmptyName(contentValues)) {
- subprimaryContentValues = contentValues;
- }
- }
- }
-
- if (primaryContentValues == null) {
- if (subprimaryContentValues != null) {
- // We choose the first ContentValues if any "primary" ContentValues does not exist.
- primaryContentValues = subprimaryContentValues;
- } else {
- 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);
-
- if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
- final boolean shouldAppendCharsetParameterToName =
- !(mIsV30 && UTF_8.equalsIgnoreCase(mCharsetString)) &&
- shouldAppendCharsetParameters(Arrays.asList(
- familyName, givenName, middleName, prefix, suffix));
- final boolean reallyUseQuotedPrintableToName =
- (!mRefrainsQPToPrimaryProperties &&
- !(VCardUtils.containsOnlyNonCrLfPrintableAscii(familyName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(givenName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(middleName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(prefix) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(suffix)));
-
- final String formattedName;
- if (!TextUtils.isEmpty(displayName)) {
- formattedName = displayName;
- } else {
- formattedName = VCardUtils.constructNameFromElements(
- VCardConfig.getNameOrderType(mVCardType),
- familyName, middleName, givenName, prefix, suffix);
- }
- final boolean shouldAppendCharsetParameterToFN =
- !(mIsV30 && UTF_8.equalsIgnoreCase(mCharsetString)) &&
- shouldAppendCharsetParameter(formattedName);
- final boolean reallyUseQuotedPrintableToFN =
- !mRefrainsQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedName);
-
- final String encodedFamily;
- final String encodedGiven;
- final String encodedMiddle;
- final String encodedPrefix;
- final String encodedSuffix;
- if (reallyUseQuotedPrintableToName) {
- encodedFamily = encodeQuotedPrintable(familyName);
- encodedGiven = encodeQuotedPrintable(givenName);
- encodedMiddle = encodeQuotedPrintable(middleName);
- encodedPrefix = encodeQuotedPrintable(prefix);
- encodedSuffix = encodeQuotedPrintable(suffix);
- } else {
- encodedFamily = escapeCharacters(familyName);
- encodedGiven = escapeCharacters(givenName);
- encodedMiddle = escapeCharacters(middleName);
- encodedPrefix = escapeCharacters(prefix);
- encodedSuffix = escapeCharacters(suffix);
- }
-
- final String encodedFormattedname =
- (reallyUseQuotedPrintableToFN ?
- encodeQuotedPrintable(formattedName) : escapeCharacters(formattedName));
-
- builder.append(Constants.PROPERTY_N);
- if (mIsDoCoMo) {
- if (shouldAppendCharsetParameterToName) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(mVCardCharsetParameter);
- }
- if (reallyUseQuotedPrintableToName) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(VCARD_PARAM_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- // DoCoMo phones require that all the elements in the "family name" field.
- builder.append(formattedName);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- } else {
- if (shouldAppendCharsetParameterToName) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(mVCardCharsetParameter);
- }
- if (reallyUseQuotedPrintableToName) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(VCARD_PARAM_ENCODING_QP);
- }
- 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_END_OF_LINE);
-
- // FN property
- builder.append(Constants.PROPERTY_FN);
- if (shouldAppendCharsetParameterToFN) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(mVCardCharsetParameter);
- }
- if (reallyUseQuotedPrintableToFN) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(VCARD_PARAM_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedFormattedname);
- builder.append(VCARD_END_OF_LINE);
- } else if (!TextUtils.isEmpty(displayName)) {
- final boolean reallyUseQuotedPrintableToDisplayName =
- (!mRefrainsQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(displayName));
- final String encodedDisplayName =
- reallyUseQuotedPrintableToDisplayName ?
- encodeQuotedPrintable(displayName) :
- escapeCharacters(displayName);
-
- builder.append(Constants.PROPERTY_N);
- if (shouldAppendCharsetParameter(displayName)) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(mVCardCharsetParameter);
- }
- if (reallyUseQuotedPrintableToDisplayName) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(VCARD_PARAM_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedDisplayName);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_END_OF_LINE);
- builder.append(Constants.PROPERTY_FN);
-
- // Note: "CHARSET" param is not allowed in vCard 3.0, but we may add it
- // when it would be useful for external importers, assuming no external
- // importer allows this vioration.
- if (shouldAppendCharsetParameter(displayName)) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(mVCardCharsetParameter);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedDisplayName);
- builder.append(VCARD_END_OF_LINE);
- } else if (mIsV30) {
- // vCard 3.0 specification requires these fields.
- appendVCardLine(builder, Constants.PROPERTY_N, "");
- appendVCardLine(builder, Constants.PROPERTY_FN, "");
- } else if (mIsDoCoMo) {
- appendVCardLine(builder, Constants.PROPERTY_N, "");
- }
-
- final String phoneticFamilyName;
- final String phoneticMiddleName;
- final String phoneticGivenName;
- {
- final String tmpPhoneticFamilyName =
- primaryContentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
- final String tmpPhoneticMiddleName =
- primaryContentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
- final String tmpPhoneticGivenName =
- primaryContentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
- if (mNeedsToConvertPhoneticString) {
- phoneticFamilyName = VCardUtils.toHalfWidthString(tmpPhoneticFamilyName);
- phoneticMiddleName = VCardUtils.toHalfWidthString(tmpPhoneticMiddleName);
- phoneticGivenName = VCardUtils.toHalfWidthString(tmpPhoneticGivenName);
- } else {
- phoneticFamilyName = tmpPhoneticFamilyName;
- phoneticMiddleName = tmpPhoneticMiddleName;
- phoneticGivenName = tmpPhoneticGivenName;
- }
- }
-
- if (!(TextUtils.isEmpty(phoneticFamilyName)
- && TextUtils.isEmpty(phoneticMiddleName)
- && TextUtils.isEmpty(phoneticGivenName))) {
- // Try to emit the field(s) related to phonetic name.
- if (mIsV30) {
- final String sortString = VCardUtils
- .constructNameFromElements(mVCardType,
- phoneticFamilyName, phoneticMiddleName, phoneticGivenName);
- builder.append(Constants.PROPERTY_SORT_STRING);
- if (shouldAppendCharsetParameter(sortString)) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(mVCardCharsetParameter);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(escapeCharacters(sortString));
- builder.append(VCARD_END_OF_LINE);
- } else if (mIsJapaneseMobilePhone) {
- // 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 when the device is Japanese one
- // since it is supported by
- // a lot of Japanese mobile phones. This is "X-" property, so
- // any parser hopefully would not get confused with this.
- //
- // Also, DoCoMo's specification requires vCard composer to use just the first
- // column.
- // i.e.
- // o SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
- // x SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
- builder.append(Constants.PROPERTY_SOUND);
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(Constants.PARAM_TYPE_X_IRMC_N);
-
- boolean reallyUseQuotedPrintable =
- (!mRefrainsQPToPrimaryProperties
- && !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticFamilyName)
- && VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticMiddleName)
- && VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticGivenName)));
-
- final String encodedPhoneticFamilyName;
- final String encodedPhoneticMiddleName;
- final String encodedPhoneticGivenName;
- if (reallyUseQuotedPrintable) {
- encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
- encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
- encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
- } else {
- encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
- encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
- encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
- }
-
- if (shouldAppendCharsetParameters(Arrays.asList(
- encodedPhoneticFamilyName, encodedPhoneticMiddleName,
- encodedPhoneticGivenName))) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(mVCardCharsetParameter);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- {
- boolean first = true;
- if (!TextUtils.isEmpty(encodedPhoneticFamilyName)) {
- builder.append(encodedPhoneticFamilyName);
- first = false;
- }
- if (!TextUtils.isEmpty(encodedPhoneticMiddleName)) {
- if (first) {
- first = false;
- } else {
- builder.append(' ');
- }
- builder.append(encodedPhoneticMiddleName);
- }
- if (!TextUtils.isEmpty(encodedPhoneticGivenName)) {
- if (!first) {
- builder.append(' ');
- }
- builder.append(encodedPhoneticGivenName);
- }
- }
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_END_OF_LINE);
- }
- } else { // If phonetic name fields are all empty
- if (mIsDoCoMo) {
- builder.append(Constants.PROPERTY_SOUND);
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(Constants.PARAM_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_END_OF_LINE);
- }
- }
-
- if (mUsesDefactProperty) {
- if (!TextUtils.isEmpty(phoneticGivenName)) {
- final boolean reallyUseQuotedPrintable =
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName));
- final String encodedPhoneticGivenName;
- if (reallyUseQuotedPrintable) {
- encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
- } else {
- encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
- }
- builder.append(Constants.PROPERTY_X_PHONETIC_FIRST_NAME);
- if (shouldAppendCharsetParameter(phoneticGivenName)) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(mVCardCharsetParameter);
- }
- if (reallyUseQuotedPrintable) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(VCARD_PARAM_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedPhoneticGivenName);
- builder.append(VCARD_END_OF_LINE);
- }
- if (!TextUtils.isEmpty(phoneticMiddleName)) {
- final boolean reallyUseQuotedPrintable =
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName));
- final String encodedPhoneticMiddleName;
- if (reallyUseQuotedPrintable) {
- encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
- } else {
- encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
- }
- builder.append(Constants.PROPERTY_X_PHONETIC_MIDDLE_NAME);
- if (shouldAppendCharsetParameter(phoneticMiddleName)) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(mVCardCharsetParameter);
- }
- if (reallyUseQuotedPrintable) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(VCARD_PARAM_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedPhoneticMiddleName);
- builder.append(VCARD_END_OF_LINE);
- }
- if (!TextUtils.isEmpty(phoneticFamilyName)) {
- final boolean reallyUseQuotedPrintable =
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName));
- final String encodedPhoneticFamilyName;
- if (reallyUseQuotedPrintable) {
- encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
- } else {
- encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
- }
- builder.append(Constants.PROPERTY_X_PHONETIC_LAST_NAME);
- if (shouldAppendCharsetParameter(phoneticFamilyName)) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(mVCardCharsetParameter);
- }
- if (reallyUseQuotedPrintable) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(VCARD_PARAM_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedPhoneticFamilyName);
- builder.append(VCARD_END_OF_LINE);
- }
- }
- }
-
- private void appendNickNames(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Nickname.CONTENT_ITEM_TYPE);
- if (contentValuesList == null) {
- return;
- }
-
- final boolean useAndroidProperty;
- if (mIsV30) {
- useAndroidProperty = false;
- } else if (mUsesAndroidProperty) {
- useAndroidProperty = true;
- } else {
- // There's no way to add this field.
- return;
- }
-
- for (ContentValues contentValues : contentValuesList) {
- final String nickname = contentValues.getAsString(Nickname.NAME);
- if (TextUtils.isEmpty(nickname)) {
- continue;
- }
- if (useAndroidProperty) {
- appendAndroidSpecificProperty(builder, Nickname.CONTENT_ITEM_TYPE,
- contentValues);
- } else {
- appendVCardLineWithCharsetAndQPDetection(builder,
- Constants.PROPERTY_NICKNAME, nickname);
- }
- }
- }
-
- private void appendPhones(final StringBuilder builder,
- 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) {
- final Integer typeAsObject = contentValues.getAsInteger(Phone.TYPE);
- final String label = contentValues.getAsString(Phone.LABEL);
- final Integer isPrimaryAsInteger = contentValues.getAsInteger(Phone.IS_PRIMARY);
- final boolean isPrimary = (isPrimaryAsInteger != null ?
- (isPrimaryAsInteger > 0) : false);
- String phoneNumber = contentValues.getAsString(Phone.NUMBER);
- if (phoneNumber != null) {
- phoneNumber = phoneNumber.trim();
- }
- if (TextUtils.isEmpty(phoneNumber)) {
- continue;
- }
- int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
- if (type == Phone.TYPE_PAGER) {
- phoneLineExists = true;
- if (!phoneSet.contains(phoneNumber)) {
- phoneSet.add(phoneNumber);
- appendVCardTelephoneLine(builder, type, label, phoneNumber, isPrimary);
- }
- } else {
- // The entry "may" have several phone numbers when the contact entry is
- // corrupted because of its original source.
- //
- // e.g. I encountered the entry like the following.
- // "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami); ..."
- // This kind of entry is not able to be inserted via Android devices, but
- // possible if the source of the data is already corrupted.
- List<String> phoneNumberList = splitIfSeveralPhoneNumbersExist(phoneNumber);
- if (phoneNumberList.isEmpty()) {
- continue;
- }
- phoneLineExists = true;
- for (String actualPhoneNumber : phoneNumberList) {
- if (!phoneSet.contains(actualPhoneNumber)) {
- final int format = VCardUtils.getPhoneNumberFormat(mVCardType);
- final String formattedPhoneNumber =
- PhoneNumberUtils.formatNumber(actualPhoneNumber, format);
- phoneSet.add(actualPhoneNumber);
- appendVCardTelephoneLine(builder, type, label,
- formattedPhoneNumber, isPrimary);
- }
- }
- }
- }
- }
-
- if (!phoneLineExists && mIsDoCoMo) {
- appendVCardTelephoneLine(builder, Phone.TYPE_HOME, "", "", false);
- }
- }
-
- private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) {
- List<String> phoneList = new ArrayList<String>();
-
- StringBuilder builder = new StringBuilder();
- final int length = phoneNumber.length();
- for (int i = 0; i < length; i++) {
- final char ch = phoneNumber.charAt(i);
- if (Character.isDigit(ch)) {
- builder.append(ch);
- } else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
- phoneList.add(builder.toString());
- builder = new StringBuilder();
- }
- }
- if (builder.length() > 0) {
- phoneList.add(builder.toString());
- }
-
- return phoneList;
- }
-
- private void appendEmails(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Email.CONTENT_ITEM_TYPE);
-
- boolean emailAddressExists = false;
- if (contentValuesList != null) {
- final Set<String> addressSet = new HashSet<String>();
- for (ContentValues contentValues : contentValuesList) {
- String emailAddress = contentValues.getAsString(Email.DATA);
- if (emailAddress != null) {
- emailAddress = emailAddress.trim();
- }
- if (TextUtils.isEmpty(emailAddress)) {
- continue;
- }
- Integer typeAsObject = contentValues.getAsInteger(Email.TYPE);
- final int type = (typeAsObject != null ?
- typeAsObject : DEFAULT_EMAIL_TYPE);
- final String label = contentValues.getAsString(Email.LABEL);
- Integer isPrimaryAsInteger = contentValues.getAsInteger(Email.IS_PRIMARY);
- final boolean isPrimary = (isPrimaryAsInteger != null ?
- (isPrimaryAsInteger > 0) : false);
- emailAddressExists = true;
- if (!addressSet.contains(emailAddress)) {
- addressSet.add(emailAddress);
- appendVCardEmailLine(builder, type, label, emailAddress, isPrimary);
- }
- }
- }
-
- if (!emailAddressExists && mIsDoCoMo) {
- appendVCardEmailLine(builder, Email.TYPE_HOME, "", "", false);
- }
- }
-
- private void appendPostals(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(StructuredPostal.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- if (mIsDoCoMo) {
- appendPostalsForDoCoMo(builder, contentValuesList);
- } else {
- appendPostalsForGeneric(builder, contentValuesList);
- }
- } else if (mIsDoCoMo) {
- builder.append(Constants.PROPERTY_ADR);
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(Constants.PARAM_TYPE_HOME);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(VCARD_END_OF_LINE);
- }
- }
-
- private static final Map<Integer, Integer> sPostalTypePriorityMap;
-
- static {
- sPostalTypePriorityMap = new HashMap<Integer, Integer>();
- sPostalTypePriorityMap.put(StructuredPostal.TYPE_HOME, 0);
- sPostalTypePriorityMap.put(StructuredPostal.TYPE_WORK, 1);
- sPostalTypePriorityMap.put(StructuredPostal.TYPE_OTHER, 2);
- sPostalTypePriorityMap.put(StructuredPostal.TYPE_CUSTOM, 3);
- }
-
- /**
- * Tries to append just one line. If there's no appropriate address
- * information, append an empty line.
- */
- private void appendPostalsForDoCoMo(final StringBuilder builder,
- final List<ContentValues> contentValuesList) {
- int currentPriority = Integer.MAX_VALUE;
- int currentType = Integer.MAX_VALUE;
- ContentValues currentContentValues = null;
- for (ContentValues contentValues : contentValuesList) {
- if (contentValues == null) {
- continue;
- }
- final Integer typeAsInteger = contentValues.getAsInteger(StructuredPostal.TYPE);
- final Integer priorityAsInteger = sPostalTypePriorityMap.get(typeAsInteger);
- final int priority =
- (priorityAsInteger != null ? priorityAsInteger : Integer.MAX_VALUE);
- if (priority < currentPriority) {
- currentPriority = priority;
- currentType = typeAsInteger;
- currentContentValues = contentValues;
- if (priority == 0) {
- break;
- }
- }
- }
-
- if (currentContentValues == null) {
- Log.w(LOG_TAG, "Should not come here. Must have at least one postal data.");
- return;
- }
-
- final String label = currentContentValues.getAsString(StructuredPostal.LABEL);
- appendVCardPostalLine(builder, currentType, label, currentContentValues, false, true);
- }
-
- private void appendPostalsForGeneric(final StringBuilder builder,
- final List<ContentValues> contentValuesList) {
- for (ContentValues contentValues : contentValuesList) {
- if (contentValues == null) {
- continue;
- }
- final Integer typeAsInteger = contentValues.getAsInteger(StructuredPostal.TYPE);
- final int type = (typeAsInteger != null ?
- typeAsInteger : DEFAULT_POSTAL_TYPE);
- final String label = contentValues.getAsString(StructuredPostal.LABEL);
- final Integer isPrimaryAsInteger =
- contentValues.getAsInteger(StructuredPostal.IS_PRIMARY);
- final boolean isPrimary = (isPrimaryAsInteger != null ?
- (isPrimaryAsInteger > 0) : false);
- appendVCardPostalLine(builder, type, label, contentValues, isPrimary, false);
- }
- }
-
- private void appendIms(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Im.CONTENT_ITEM_TYPE);
- if (contentValuesList == null) {
- return;
- }
- for (ContentValues contentValues : contentValuesList) {
- final Integer protocolAsObject = contentValues.getAsInteger(Im.PROTOCOL);
- if (protocolAsObject == null) {
- continue;
- }
- final String propertyName = VCardUtils.getPropertyNameForIm(protocolAsObject);
- if (propertyName == null) {
- continue;
- }
- String data = contentValues.getAsString(Im.DATA);
- if (data != null) {
- data = data.trim();
- }
- if (TextUtils.isEmpty(data)) {
- continue;
- }
- final String typeAsString;
- {
- final Integer typeAsInteger = contentValues.getAsInteger(Im.TYPE);
- switch (typeAsInteger != null ? typeAsInteger : Im.TYPE_OTHER) {
- case Im.TYPE_HOME: {
- typeAsString = Constants.PARAM_TYPE_HOME;
- break;
- }
- case Im.TYPE_WORK: {
- typeAsString = Constants.PARAM_TYPE_WORK;
- break;
- }
- case Im.TYPE_CUSTOM: {
- final String label = contentValues.getAsString(Im.LABEL);
- typeAsString = (label != null ? "X-" + label : null);
- break;
- }
- case Im.TYPE_OTHER: // Ignore
- default: {
- typeAsString = null;
- break;
- }
- }
- }
-
- List<String> parameterList = new ArrayList<String>();
- if (!TextUtils.isEmpty(typeAsString)) {
- parameterList.add(typeAsString);
- }
- final Integer isPrimaryAsInteger = contentValues.getAsInteger(Im.IS_PRIMARY);
- final boolean isPrimary = (isPrimaryAsInteger != null ?
- (isPrimaryAsInteger > 0) : false);
- if (isPrimary) {
- parameterList.add(Constants.PARAM_TYPE_PREF);
- }
-
- appendVCardLineWithCharsetAndQPDetection(
- builder, propertyName, parameterList, data);
- }
- }
-
- private void appendWebsites(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Website.CONTENT_ITEM_TYPE);
- if (contentValuesList == null) {
- return;
- }
- for (ContentValues contentValues : contentValuesList) {
- String website = contentValues.getAsString(Website.URL);
- if (website != null) {
- website = website.trim();
- }
-
- // Note: vCard 3.0 does not allow any parameter addition toward "URL"
- // property, while there's no document in vCard 2.1.
- //
- // TODO: Should we allow adding it when appropriate?
- // (Actually, we drop some data. Using "group.X-URL-TYPE" or something
- // may help)
- if (!TextUtils.isEmpty(website)) {
- appendVCardLine(builder, Constants.PROPERTY_URL, website);
- }
- }
- }
-
- /**
- * Theoretically, there must be only one birthday for each vCard entry.
- * Also, we are afraid of some importer's parse error during its import.
- * We emit only one birthday entry even when there are more than one.
- */
- private void appendBirthday(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList =
- contentValuesListMap.get(Event.CONTENT_ITEM_TYPE);
- if (contentValuesList == null) {
- return;
- }
- String primaryBirthday = null;
- String secondaryBirthday = null;
- for (ContentValues contentValues : contentValuesList) {
- if (contentValues == null) {
- continue;
- }
- final Integer eventType = contentValues.getAsInteger(Event.TYPE);
- if (eventType == null || !eventType.equals(Event.TYPE_BIRTHDAY)) {
- continue;
- }
- final String birthdayCandidate = contentValues.getAsString(Event.START_DATE);
- if (birthdayCandidate == null) {
- continue;
- }
- final Integer isSuperPrimaryAsInteger =
- contentValues.getAsInteger(Event.IS_SUPER_PRIMARY);
- final boolean isSuperPrimary = (isSuperPrimaryAsInteger != null ?
- (isSuperPrimaryAsInteger > 0) : false);
- if (isSuperPrimary) {
- // "super primary" birthday should the prefered one.
- primaryBirthday = birthdayCandidate;
- break;
- }
- final Integer isPrimaryAsInteger =
- contentValues.getAsInteger(Event.IS_PRIMARY);
- final boolean isPrimary = (isPrimaryAsInteger != null ?
- (isPrimaryAsInteger > 0) : false);
- if (isPrimary) {
- // We don't break here since "super primary" birthday may exist later.
- primaryBirthday = birthdayCandidate;
- } else if (secondaryBirthday == null) {
- // First entry is set to the "secondary" candidate.
- secondaryBirthday = birthdayCandidate;
- }
- }
-
- final String birthday;
- if (primaryBirthday != null) {
- birthday = primaryBirthday.trim();
- } else if (secondaryBirthday != null){
- birthday = secondaryBirthday.trim();
- } else {
- return;
- }
- appendVCardLineWithCharsetAndQPDetection(builder, Constants.PROPERTY_BDAY, birthday);
- }
-
- private void appendOrganizations(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Organization.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- for (ContentValues contentValues : contentValuesList) {
- String company = contentValues.getAsString(Organization.COMPANY);
- if (company != null) {
- company = company.trim();
- }
- String department = contentValues.getAsString(Organization.DEPARTMENT);
- if (department != null) {
- department = department.trim();
- }
- String title = contentValues.getAsString(Organization.TITLE);
- if (title != null) {
- title = title.trim();
- }
-
- StringBuilder orgBuilder = new StringBuilder();
- if (!TextUtils.isEmpty(company)) {
- orgBuilder.append(company);
- }
- if (!TextUtils.isEmpty(department)) {
- if (orgBuilder.length() > 0) {
- orgBuilder.append(';');
- }
- orgBuilder.append(department);
- }
- final String orgline = orgBuilder.toString();
- appendVCardLine(builder, Constants.PROPERTY_ORG, orgline,
- !VCardUtils.containsOnlyPrintableAscii(orgline),
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(orgline)));
-
- if (!TextUtils.isEmpty(title)) {
- appendVCardLine(builder, Constants.PROPERTY_TITLE, title,
- !VCardUtils.containsOnlyPrintableAscii(title),
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
- }
- }
- }
- }
-
- private void appendPhotos(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Photo.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- for (ContentValues contentValues : contentValuesList) {
- byte[] data = contentValues.getAsByteArray(Photo.PHOTO);
- if (data == null) {
- continue;
- }
- final String photoType;
- // Use some heuristics for guessing the format of the image.
- // TODO: there should be some general API for detecting the file format.
- if (data.length >= 3 && data[0] == 'G' && data[1] == 'I'
- && data[2] == 'F') {
- photoType = "GIF";
- } else if (data.length >= 4 && data[0] == (byte) 0x89
- && data[1] == 'P' && data[2] == 'N' && data[3] == 'G') {
- // Note: vCard 2.1 officially does not support PNG, but we may have it and
- // using X- word like "X-PNG" may not let importers know it is PNG.
- // So we use the String "PNG" as is...
- photoType = "PNG";
- } else if (data.length >= 2 && data[0] == (byte) 0xff
- && data[1] == (byte) 0xd8) {
- photoType = "JPEG";
- } else {
- Log.d(LOG_TAG, "Unknown photo type. Ignore.");
- continue;
- }
- final String photoString = new String(Base64.encodeBase64(data));
- if (!TextUtils.isEmpty(photoString)) {
- appendVCardPhotoLine(builder, photoString, photoType);
- }
- }
- }
- }
-
- private void appendNotes(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList =
- contentValuesListMap.get(Note.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- if (mOnlyOneNoteFieldIsAvailable) {
- StringBuilder noteBuilder = new StringBuilder();
- boolean first = true;
- for (ContentValues contentValues : contentValuesList) {
- String note = contentValues.getAsString(Note.NOTE);
- if (note == null) {
- note = "";
- }
- if (note.length() > 0) {
- if (first) {
- first = false;
- } else {
- noteBuilder.append('\n');
- }
- noteBuilder.append(note);
- }
- }
- 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, Constants.PROPERTY_NOTE, noteStr,
- shouldAppendCharsetInfo, reallyUseQuotedPrintable);
- } else {
- for (ContentValues contentValues : contentValuesList) {
- 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, Constants.PROPERTY_NOTE, noteStr,
- shouldAppendCharsetInfo, reallyUseQuotedPrintable);
- }
- }
- }
- }
- }
-
- private void appendAndroidSpecificProperty(final StringBuilder builder,
- final String mimeType, ContentValues contentValues) {
- List<String> rawDataList = new ArrayList<String>();
- rawDataList.add(mimeType);
- final List<String> columnNameList;
- if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
-
- } else {
- // If you add the other field, please check all the columns are able to be
- // converted to String.
- //
- // e.g. BLOB is not what we can handle here now.
- return;
- }
-
- for (int i = 1; i <= Constants.MAX_DATA_COLUMN; i++) {
- String value = contentValues.getAsString("data" + i);
- if (value == null) {
- value = "";
- }
- rawDataList.add(value);
- }
-
- appendVCardLineWithCharsetAndQPDetection(builder,
- Constants.PROPERTY_X_ANDROID_CUSTOM, rawDataList);
- }
-
- /**
- * Append '\' to the characters which should be escaped. The character set is different
- * not only between vCard 2.1 and vCard 3.0 but also among each device.
- *
- * Note that Quoted-Printable string must not be input here.
- */
- @SuppressWarnings("fallthrough")
- private String escapeCharacters(final String unescaped) {
- if (TextUtils.isEmpty(unescaped)) {
- return "";
- }
-
- final StringBuilder tmpBuilder = new StringBuilder();
- final int length = unescaped.length();
- for (int i = 0; i < length; i++) {
- final char ch = unescaped.charAt(i);
- switch (ch) {
- case ';': {
- tmpBuilder.append('\\');
- tmpBuilder.append(';');
- break;
- }
- case '\r': {
- if (i + 1 < length) {
- char nextChar = unescaped.charAt(i);
- if (nextChar == '\n') {
- break;
- } else {
- // fall through
- }
- } else {
- // fall through
- }
- }
- case '\n': {
- // In vCard 2.1, there's no specification about this, while
- // vCard 3.0 explicitly requires this should be encoded to "\n".
- tmpBuilder.append("\\n");
- break;
- }
- case '\\': {
- if (mIsV30) {
- tmpBuilder.append("\\\\");
- break;
- } else {
- // fall through
- }
- }
- case '<':
- case '>': {
- if (mIsDoCoMo) {
- tmpBuilder.append('\\');
- tmpBuilder.append(ch);
- } else {
- tmpBuilder.append(ch);
- }
- break;
- }
- case ',': {
- if (mIsV30) {
- tmpBuilder.append("\\,");
- } else {
- tmpBuilder.append(ch);
- }
- break;
- }
- default: {
- tmpBuilder.append(ch);
- break;
- }
- }
- }
- return tmpBuilder.toString();
- }
-
- private void appendVCardPhotoLine(final StringBuilder builder,
- final String encodedData, final String photoType) {
- StringBuilder tmpBuilder = new StringBuilder();
- tmpBuilder.append(Constants.PROPERTY_PHOTO);
- tmpBuilder.append(VCARD_PARAM_SEPARATOR);
- if (mIsV30) {
- tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V30);
- } else {
- tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V21);
- }
- tmpBuilder.append(VCARD_PARAM_SEPARATOR);
- appendTypeParameter(tmpBuilder, photoType);
- tmpBuilder.append(VCARD_DATA_SEPARATOR);
- tmpBuilder.append(encodedData);
-
- final String tmpStr = tmpBuilder.toString();
- tmpBuilder = new StringBuilder();
- int lineCount = 0;
- int length = tmpStr.length();
- for (int i = 0; i < length; i++) {
- tmpBuilder.append(tmpStr.charAt(i));
- lineCount++;
- if (lineCount > 72) {
- tmpBuilder.append(VCARD_END_OF_LINE);
- tmpBuilder.append(VCARD_WS);
- lineCount = 0;
- }
- }
- builder.append(tmpBuilder.toString());
- builder.append(VCARD_END_OF_LINE);
- builder.append(VCARD_END_OF_LINE);
- }
-
- private class PostalStruct {
- final boolean reallyUseQuotedPrintable;
- final boolean appendCharset;
- final String addressData;
- public PostalStruct(final boolean reallyUseQuotedPrintable,
- final boolean appendCharset, final String addressData) {
- this.reallyUseQuotedPrintable = reallyUseQuotedPrintable;
- this.appendCharset = appendCharset;
- this.addressData = addressData;
- }
- }
-
- /**
- * @return null when there's no information available to construct the data.
- */
- private PostalStruct tryConstructPostalStruct(ContentValues contentValues) {
- boolean reallyUseQuotedPrintable = false;
- boolean appendCharset = false;
-
- boolean dataArrayExists = false;
- String[] dataArray = VCardUtils.getVCardPostalElements(contentValues);
- for (String data : dataArray) {
- if (!TextUtils.isEmpty(data)) {
- dataArrayExists = true;
- if (!appendCharset && !VCardUtils.containsOnlyPrintableAscii(data)) {
- appendCharset = true;
- }
- if (mUsesQuotedPrintable && !VCardUtils.containsOnlyNonCrLfPrintableAscii(data)) {
- reallyUseQuotedPrintable = true;
- break;
- }
- }
- }
-
- if (dataArrayExists) {
- StringBuffer addressBuffer = new StringBuffer();
- boolean first = true;
- for (String data : dataArray) {
- if (first) {
- first = false;
- } else {
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- }
- if (!TextUtils.isEmpty(data)) {
- if (reallyUseQuotedPrintable) {
- addressBuffer.append(encodeQuotedPrintable(data));
- } else {
- addressBuffer.append(escapeCharacters(data));
- }
- }
- }
- return new PostalStruct(reallyUseQuotedPrintable, appendCharset,
- addressBuffer.toString());
- }
-
- String formattedAddress =
- contentValues.getAsString(StructuredPostal.FORMATTED_ADDRESS);
- if (!TextUtils.isEmpty(formattedAddress)) {
- reallyUseQuotedPrintable =
- !VCardUtils.containsOnlyPrintableAscii(formattedAddress);
- appendCharset =
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedAddress);
- if (reallyUseQuotedPrintable) {
- formattedAddress = encodeQuotedPrintable(formattedAddress);
- } else {
- formattedAddress = escapeCharacters(formattedAddress);
- }
- // We use the second value ("Extended Address").
- //
- // adr-value = 0*6(text-value ";") text-value
- // ; PO Box, Extended Address, Street, Locality, Region, Postal
- // ; Code, Country Name
- StringBuffer addressBuffer = new StringBuffer();
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(formattedAddress);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- return new PostalStruct(
- reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
- }
- return null; // There's no data available.
- }
-
- private void appendVCardPostalLine(final StringBuilder builder,
- final int type, final String label, final ContentValues contentValues,
- final boolean isPrimary, final boolean emitLineEveryTime) {
- final boolean reallyUseQuotedPrintable;
- final boolean appendCharset;
- final String addressData;
- {
- PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
- if (postalStruct == null) {
- if (emitLineEveryTime) {
- reallyUseQuotedPrintable = false;
- appendCharset = false;
- addressData = "";
- } else {
- return;
- }
- } else {
- reallyUseQuotedPrintable = postalStruct.reallyUseQuotedPrintable;
- appendCharset = postalStruct.appendCharset;
- addressData = postalStruct.addressData;
- }
- }
-
- List<String> parameterList = new ArrayList<String>();
- if (isPrimary) {
- parameterList.add(Constants.PARAM_TYPE_PREF);
- }
- switch (type) {
- case StructuredPostal.TYPE_HOME: {
- parameterList.add(Constants.PARAM_TYPE_HOME);
- break;
- }
- case StructuredPostal.TYPE_WORK: {
- parameterList.add(Constants.PARAM_TYPE_WORK);
- break;
- }
- case StructuredPostal.TYPE_CUSTOM: {
- if (!TextUtils.isEmpty(label)
- && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
- // We're not sure whether the label is valid in the spec
- // ("IANA-token" in the vCard 3.0 is unclear...)
- // Just for safety, we add "X-" at the beggining of each label.
- // Also checks the label obeys with vCard 3.0 spec.
- parameterList.add("X-" + label);
- }
- break;
- }
- case StructuredPostal.TYPE_OTHER: {
- break;
- }
- default: {
- Log.e(LOG_TAG, "Unknown StructuredPostal type: " + type);
- break;
- }
- }
-
- // Actual data construction starts from here.
-
- builder.append(Constants.PROPERTY_ADR);
-
- // Parameters
- {
- if (!parameterList.isEmpty()) {
- builder.append(VCARD_PARAM_SEPARATOR);
- appendTypeParameters(builder, parameterList);
- }
-
- if (appendCharset) {
- // 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 parameter in vCard 3.0.
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(mVCardCharsetParameter);
- }
-
- if (reallyUseQuotedPrintable) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(VCARD_PARAM_ENCODING_QP);
- }
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(addressData);
- builder.append(VCARD_END_OF_LINE);
- }
-
- private void appendVCardEmailLine(final StringBuilder builder,
- final int type, final String label,
- final String rawData, final boolean isPrimary) {
- final String typeAsString;
- switch (type) {
- case Email.TYPE_CUSTOM: {
- if (VCardUtils.isMobilePhoneLabel(label)) {
- typeAsString = Constants.PARAM_TYPE_CELL;
- } else if (!TextUtils.isEmpty(label)
- && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
- typeAsString = "X-" + label;
- } else {
- typeAsString = null;
- }
- break;
- }
- case Email.TYPE_HOME: {
- typeAsString = Constants.PARAM_TYPE_HOME;
- break;
- }
- case Email.TYPE_WORK: {
- typeAsString = Constants.PARAM_TYPE_WORK;
- break;
- }
- case Email.TYPE_OTHER: {
- typeAsString = null;
- break;
- }
- case Email.TYPE_MOBILE: {
- typeAsString = Constants.PARAM_TYPE_CELL;
- break;
- }
- default: {
- Log.e(LOG_TAG, "Unknown Email type: " + type);
- typeAsString = null;
- break;
- }
- }
-
- final List<String> parameterList = new ArrayList<String>();
- if (isPrimary) {
- parameterList.add(Constants.PARAM_TYPE_PREF);
- }
- if (!TextUtils.isEmpty(typeAsString)) {
- parameterList.add(typeAsString);
- }
-
- appendVCardLineWithCharsetAndQPDetection(builder, Constants.PROPERTY_EMAIL,
- parameterList, rawData);
- }
-
- private void appendVCardTelephoneLine(final StringBuilder builder,
- final Integer typeAsInteger, final String label,
- final String encodedData, boolean isPrimary) {
- builder.append(Constants.PROPERTY_TEL);
- builder.append(VCARD_PARAM_SEPARATOR);
-
- final int type;
- if (typeAsInteger == null) {
- type = Phone.TYPE_OTHER;
- } else {
- type = typeAsInteger;
- }
-
- ArrayList<String> parameterList = new ArrayList<String>();
- switch (type) {
- case Phone.TYPE_HOME: {
- parameterList.addAll(
- Arrays.asList(Constants.PARAM_TYPE_HOME));
- break;
- }
- case Phone.TYPE_WORK: {
- parameterList.addAll(
- Arrays.asList(Constants.PARAM_TYPE_WORK));
- break;
- }
- case Phone.TYPE_FAX_HOME: {
- parameterList.addAll(
- Arrays.asList(Constants.PARAM_TYPE_HOME, Constants.PARAM_TYPE_FAX));
- break;
- }
- case Phone.TYPE_FAX_WORK: {
- parameterList.addAll(
- Arrays.asList(Constants.PARAM_TYPE_WORK, Constants.PARAM_TYPE_FAX));
- break;
- }
- case Phone.TYPE_MOBILE: {
- parameterList.add(Constants.PARAM_TYPE_CELL);
- break;
- }
- case Phone.TYPE_PAGER: {
- if (mIsDoCoMo) {
- // Not sure about the reason, but previous implementation had
- // used "VOICE" instead of "PAGER"
- parameterList.add(Constants.PARAM_TYPE_VOICE);
- } else {
- parameterList.add(Constants.PARAM_TYPE_PAGER);
- }
- break;
- }
- case Phone.TYPE_OTHER: {
- parameterList.add(Constants.PARAM_TYPE_VOICE);
- break;
- }
- case Phone.TYPE_CAR: {
- parameterList.add(Constants.PARAM_TYPE_CAR);
- break;
- }
- case Phone.TYPE_COMPANY_MAIN: {
- // There's no relevant field in vCard (at least 2.1).
- parameterList.add(Constants.PARAM_TYPE_WORK);
- isPrimary = true;
- break;
- }
- case Phone.TYPE_ISDN: {
- parameterList.add(Constants.PARAM_TYPE_ISDN);
- break;
- }
- case Phone.TYPE_MAIN: {
- isPrimary = true;
- break;
- }
- case Phone.TYPE_OTHER_FAX: {
- parameterList.add(Constants.PARAM_TYPE_FAX);
- break;
- }
- case Phone.TYPE_TELEX: {
- parameterList.add(Constants.PARAM_TYPE_TLX);
- break;
- }
- case Phone.TYPE_WORK_MOBILE: {
- parameterList.addAll(
- Arrays.asList(Constants.PARAM_TYPE_WORK, Constants.PARAM_TYPE_CELL));
- break;
- }
- case Phone.TYPE_WORK_PAGER: {
- parameterList.add(Constants.PARAM_TYPE_WORK);
- // See above.
- if (mIsDoCoMo) {
- parameterList.add(Constants.PARAM_TYPE_VOICE);
- } else {
- parameterList.add(Constants.PARAM_TYPE_PAGER);
- }
- break;
- }
- case Phone.TYPE_MMS: {
- parameterList.add(Constants.PARAM_TYPE_MSG);
- break;
- }
- case Phone.TYPE_CUSTOM: {
- if (TextUtils.isEmpty(label)) {
- // Just ignore the custom type.
- parameterList.add(Constants.PARAM_TYPE_VOICE);
- } else if (VCardUtils.isMobilePhoneLabel(label)) {
- parameterList.add(Constants.PARAM_TYPE_CELL);
- } else {
- final String upperLabel = label.toUpperCase();
- if (VCardUtils.isValidInV21ButUnknownToContactsPhoteType(upperLabel)) {
- parameterList.add(upperLabel);
- } else if (VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
- // Note: Strictly, vCard 2.1 does not allow "X-" parameter without
- // "TYPE=" string.
- parameterList.add("X-" + label);
- }
- }
- break;
- }
- case Phone.TYPE_RADIO:
- case Phone.TYPE_TTY_TDD:
- default: {
- break;
- }
- }
-
- if (isPrimary) {
- parameterList.add(Constants.PARAM_TYPE_PREF);
- }
-
- if (parameterList.isEmpty()) {
- appendUncommonPhoneType(builder, type);
- } else {
- appendTypeParameters(builder, parameterList);
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedData);
- builder.append(VCARD_END_OF_LINE);
- }
-
- /**
- * Appends phone type string which may not be available in some devices.
- */
- private void appendUncommonPhoneType(final StringBuilder builder, final Integer type) {
- if (mIsDoCoMo) {
- // The previous implementation for DoCoMo had been conservative
- // about miscellaneous types.
- builder.append(Constants.PARAM_TYPE_VOICE);
- } else {
- String phoneType = VCardUtils.getPhoneTypeString(type);
- if (phoneType != null) {
- appendTypeParameter(builder, phoneType);
- } else {
- Log.e(LOG_TAG, "Unknown or unsupported (by vCard) Phone type: " + type);
- }
- }
- }
-
- // appendVCardLine() variants accepting one String.
-
- private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
- final String propertyName, final String rawData) {
- appendVCardLineWithCharsetAndQPDetection(builder, propertyName, null, rawData);
- }
-
- private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
- final String propertyName,
- final List<String> parameterList, final String rawData) {
- final boolean needCharset =
- (mUsesQuotedPrintable && !VCardUtils.containsOnlyPrintableAscii(rawData));
- final boolean reallyUseQuotedPrintable =
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawData);
- appendVCardLine(builder, propertyName, parameterList,
- rawData, needCharset, reallyUseQuotedPrintable);
- }
-
- private void appendVCardLine(final StringBuilder builder,
- final String propertyName, final String rawData) {
- appendVCardLine(builder, propertyName, rawData, false, false);
- }
-
- private void appendVCardLine(final StringBuilder builder,
- final String propertyName, final String rawData, final boolean needCharset,
- boolean needQuotedPrintable) {
- appendVCardLine(builder, propertyName, null, rawData, needCharset, needQuotedPrintable);
- }
-
- private void appendVCardLine(final StringBuilder builder,
- final String propertyName,
- final List<String> parameterList,
- final String rawData, final boolean needCharset,
- boolean needQuotedPrintable) {
- builder.append(propertyName);
- if (parameterList != null && parameterList.size() > 0) {
- builder.append(VCARD_PARAM_SEPARATOR);
- appendTypeParameters(builder, parameterList);
- }
- if (needCharset) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(mVCardCharsetParameter);
- }
-
- final String encodedData;
- if (needQuotedPrintable) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(VCARD_PARAM_ENCODING_QP);
- encodedData = encodeQuotedPrintable(rawData);
- } else {
- // TODO: one line may be too huge, which may be invalid in vCard spec, though
- // several (even well-known) applications do not care this.
- encodedData = escapeCharacters(rawData);
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedData);
- builder.append(VCARD_END_OF_LINE);
- }
-
- // appendVCardLine() variants accepting List<String>.
-
- private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
- final String propertyName, final List<String> rawDataList) {
- appendVCardLineWithCharsetAndQPDetection(builder, propertyName, null, rawDataList);
- }
-
- private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
- final String propertyName,
- final List<String> parameterList, final List<String> rawDataList) {
- boolean needCharset = false;
- boolean reallyUseQuotedPrintable = false;
- for (String rawData : rawDataList) {
- if (!needCharset && mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyPrintableAscii(rawData)) {
- needCharset = true;
- }
- if (!reallyUseQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawData)) {
- reallyUseQuotedPrintable = true;
- }
- if (needCharset && reallyUseQuotedPrintable) {
- break;
- }
- }
-
- appendVCardLine(builder, propertyName, parameterList,
- rawDataList, needCharset, reallyUseQuotedPrintable);
- }
-
- /*
- private void appendVCardLine(final StringBuilder builder,
- final String propertyName, final List<String> rawDataList) {
- appendVCardLine(builder, propertyName, rawDataList, false, false);
- }
-
- private void appendVCardLine(final StringBuilder builder,
- final String propertyName, final List<String> rawDataList,
- final boolean needCharset, boolean needQuotedPrintable) {
- appendVCardLine(builder, propertyName, null, rawDataList, needCharset, needQuotedPrintable);
- }*/
-
- private void appendVCardLine(final StringBuilder builder,
- final String propertyName,
- final List<String> parameterList,
- final List<String> rawDataList, final boolean needCharset,
- final boolean needQuotedPrintable) {
- builder.append(propertyName);
- if (parameterList != null && parameterList.size() > 0) {
- builder.append(VCARD_PARAM_SEPARATOR);
- appendTypeParameters(builder, parameterList);
- }
- if (needCharset) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(mVCardCharsetParameter);
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- boolean first = true;
- for (String rawData : rawDataList) {
- final String encodedData;
- if (needQuotedPrintable) {
- builder.append(VCARD_PARAM_SEPARATOR);
- builder.append(VCARD_PARAM_ENCODING_QP);
- encodedData = encodeQuotedPrintable(rawData);
- } else {
- // TODO: one line may be too huge, which may be invalid in vCard 3.0
- // (which says "When generating a content line, lines longer than
- // 75 characters SHOULD be folded"), though several
- // (even well-known) applications do not care this.
- encodedData = escapeCharacters(rawData);
- }
-
- if (first) {
- first = false;
- } else {
- builder.append(VCARD_ITEM_SEPARATOR);
- }
- builder.append(encodedData);
- }
- builder.append(VCARD_END_OF_LINE);
- }
-
- /**
- * VCARD_PARAM_SEPARATOR must be appended before this method being called.
- */
- private void appendTypeParameters(final StringBuilder builder,
- final List<String> types) {
- // We may have to make this comma separated form like "TYPE=DOM,WORK" in the future,
- // which would be recommended way in vcard 3.0 though not valid in vCard 2.1.
- boolean first = true;
- for (String type : types) {
- if (first) {
- first = false;
- } else {
- builder.append(VCARD_PARAM_SEPARATOR);
- }
- appendTypeParameter(builder, type);
- }
- }
-
- /**
- * VCARD_PARAM_SEPARATOR must be appended before this method being called.
- */
- private void appendTypeParameter(final StringBuilder builder, final String type) {
- // Refrain from using appendType() so that "TYPE=" is not be appended when the
- // device is DoCoMo's (just for safety).
- //
- // Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF"
- if ((mIsV30 || mAppendTypeParamName) && !mIsDoCoMo) {
- builder.append(Constants.PARAM_TYPE).append(VCARD_PARAM_EQUAL);
- }
- builder.append(type);
- }
-
- /**
- * Returns true when the property line should contain charset parameter
- * information. This method may return true even when vCard version is 3.0.
- *
- * Strictly, adding charset information is invalid in VCard 3.0.
- * However we'll add the info only when charset we use is not UTF-8
- * in vCard 3.0 format, since parser side may be able to use the charset
- * via this field, though we may encounter another problem by adding it.
- *
- * 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.
- */
- private boolean shouldAppendCharsetParameter(final String propertyValue) {
- return (!(mIsV30 && mUsesUtf8) && !VCardUtils.containsOnlyPrintableAscii(propertyValue));
- }
-
- private boolean shouldAppendCharsetParameters(final List<String> propertyValueList) {
- if (mIsV30 && mUsesUtf8) {
- return false;
- }
- for (String propertyValue : propertyValueList) {
- if (!VCardUtils.containsOnlyPrintableAscii(propertyValue)) {
- return true;
- }
- }
- return false;
- }
-
- private String encodeQuotedPrintable(String str) {
- if (TextUtils.isEmpty(str)) {
- return "";
- }
- {
- // Replace "\n" and "\r" with "\r\n".
- final StringBuilder tmpBuilder = new StringBuilder();
- int length = str.length();
- for (int i = 0; i < length; i++) {
- char ch = str.charAt(i);
- if (ch == '\r') {
- if (i + 1 < length && str.charAt(i + 1) == '\n') {
- i++;
- }
- tmpBuilder.append("\r\n");
- } else if (ch == '\n') {
- tmpBuilder.append("\r\n");
- } else {
- tmpBuilder.append(ch);
- }
- }
- str = tmpBuilder.toString();
- }
-
- final StringBuilder tmpBuilder = new StringBuilder();
- int index = 0;
- int lineCount = 0;
- byte[] strArray = null;
-
- try {
- strArray = str.getBytes(mCharsetString);
- } catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
- + "Try default charset");
- strArray = str.getBytes();
- }
- while (index < strArray.length) {
- tmpBuilder.append(String.format("=%02X", strArray[index]));
- index += 1;
- lineCount += 3;
-
- if (lineCount >= 67) {
- // Specification requires CRLF must be inserted before the
- // length of the line
- // becomes more than 76.
- // Assuming that the next character is a multi-byte character,
- // it will become
- // 6 bytes.
- // 76 - 6 - 3 = 67
- tmpBuilder.append("=\r\n");
- lineCount = 0;
- }
- }
-
- return tmpBuilder.toString();
- }
-
- //// The methods bellow are for call log history ////
-
/**
* This static function is to compose vCard for phone own number
*/
public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
String phoneNumber, boolean vcardVer21) {
- final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
- if (!vcardVer21) {
- appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
- } else {
- appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
- }
-
+ final int vcardType = (vcardVer21 ?
+ VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8 :
+ VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8);
+ final VCardBuilder builder = new VCardBuilder(vcardType);
boolean needCharset = false;
if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
needCharset = true;
}
- appendVCardLine(builder, Constants.PROPERTY_FN, phoneName, needCharset, false);
- appendVCardLine(builder, Constants.PROPERTY_N, phoneName, needCharset, false);
+ builder.appendLine(VCardConstants.PROPERTY_FN, phoneName, needCharset, false);
+ builder.appendLine(VCardConstants.PROPERTY_N, phoneName, needCharset, false);
if (!TextUtils.isEmpty(phoneNumber)) {
String label = Integer.toString(phonetype);
- appendVCardTelephoneLine(builder, phonetype, label, phoneNumber, false);
+ builder.appendTelLine(phonetype, label, phoneNumber, false);
}
- appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
-
return builder.toString();
}
@@ -2426,7 +636,7 @@
* Try to append the property line for a call history time stamp field if possible.
* Do nothing if the call log type gotton from the database is invalid.
*/
- private void tryAppendCallHistoryTimeStampField(final StringBuilder builder) {
+ private void tryAppendCallHistoryTimeStampField(final VCardBuilder builder) {
// Extension for call history as defined in
// in the Specification for Ic Mobile Communcation - ver 1.1,
// Oct 2000. This is used to send the details of the call
@@ -2457,39 +667,28 @@
}
final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
- builder.append(VCARD_PROPERTY_X_TIMESTAMP);
- builder.append(VCARD_PARAM_SEPARATOR);
- appendTypeParameter(builder, callLogTypeStr);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(toRfc2455Format(dateAsLong));
- builder.append(VCARD_END_OF_LINE);
+ builder.appendLine(VCARD_PROPERTY_X_TIMESTAMP,
+ Arrays.asList(callLogTypeStr), toRfc2455Format(dateAsLong));
}
private String createOneCallLogEntryInternal() {
- final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
- if (mIsV30) {
- appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
- } else {
- appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
- }
+ final VCardBuilder builder = new VCardBuilder(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
if (TextUtils.isEmpty(name)) {
name = mCursor.getString(NUMBER_COLUMN_INDEX);
}
final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
- appendVCardLine(builder, Constants.PROPERTY_FN, name, needCharset, false);
- appendVCardLine(builder, Constants.PROPERTY_N, name, needCharset, false);
+ builder.appendLine(VCardConstants.PROPERTY_FN, name, needCharset, false);
+ builder.appendLine(VCardConstants.PROPERTY_N, name, needCharset, false);
- String number = mCursor.getString(NUMBER_COLUMN_INDEX);
- int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
+ final String number = mCursor.getString(NUMBER_COLUMN_INDEX);
+ final int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
if (TextUtils.isEmpty(label)) {
label = Integer.toString(type);
}
- appendVCardTelephoneLine(builder, type, label, number, false);
+ builder.appendTelLine(type, label, number, false);
tryAppendCallHistoryTimeStampField(builder);
- appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
return builder.toString();
}
}
diff --git a/core/java/android/pim/vcard/Constants.java b/core/java/android/pim/vcard/VCardConstants.java
similarity index 99%
rename from core/java/android/pim/vcard/Constants.java
rename to core/java/android/pim/vcard/VCardConstants.java
index 9e4b13a..8c07126 100644
--- a/core/java/android/pim/vcard/Constants.java
+++ b/core/java/android/pim/vcard/VCardConstants.java
@@ -18,8 +18,7 @@
/**
* Constants used in both exporter and importer code.
*/
-/* package */ class Constants {
-
+public class VCardConstants {
public static final String VERSION_V21 = "2.1";
public static final String VERSION_V30 = "3.0";
@@ -148,6 +147,6 @@
/* package */ static final int MAX_CHARACTER_NUMS_QP = 76;
static final int MAX_CHARACTER_NUMS_BASE64_V30 = 75;
- private Constants() {
+ private VCardConstants() {
}
}
\ No newline at end of file
diff --git a/core/java/android/pim/vcard/VCardEntry.java b/core/java/android/pim/vcard/VCardEntry.java
index c1b4c03..a6e432d 100644
--- a/core/java/android/pim/vcard/VCardEntry.java
+++ b/core/java/android/pim/vcard/VCardEntry.java
@@ -65,14 +65,14 @@
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);
- sImMap.put(Constants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO);
- sImMap.put(Constants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ);
- sImMap.put(Constants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER);
- sImMap.put(Constants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE);
- sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK);
- sImMap.put(Constants.ImportOnly.PROPERTY_X_GOOGLE_TALK_WITH_SPACE,
+ sImMap.put(VCardConstants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
+ sImMap.put(VCardConstants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
+ sImMap.put(VCardConstants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO);
+ sImMap.put(VCardConstants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ);
+ sImMap.put(VCardConstants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER);
+ sImMap.put(VCardConstants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE);
+ sImMap.put(VCardConstants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK);
+ sImMap.put(VCardConstants.ImportOnly.PROPERTY_X_GOOGLE_TALK_WITH_SPACE,
Im.PROTOCOL_GOOGLE_TALK);
}
@@ -749,24 +749,25 @@
}
final String propValue = listToString(propValueList).trim();
- if (propName.equals(Constants.PROPERTY_VERSION)) {
+ if (propName.equals(VCardConstants.PROPERTY_VERSION)) {
// vCard version. Ignore this.
- } else if (propName.equals(Constants.PROPERTY_FN)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_FN)) {
mFullName = propValue;
- } else if (propName.equals(Constants.PROPERTY_NAME) && mFullName == null) {
+ } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFullName == null) {
// Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not
// actually exist in the real vCard data, does not exist.
mFullName = propValue;
- } else if (propName.equals(Constants.PROPERTY_N)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_N)) {
handleNProperty(propValueList);
- } else if (propName.equals(Constants.PROPERTY_SORT_STRING)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_SORT_STRING)) {
mPhoneticFullName = propValue;
- } else if (propName.equals(Constants.PROPERTY_NICKNAME) ||
- propName.equals(Constants.ImportOnly.PROPERTY_X_NICKNAME)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_NICKNAME) ||
+ propName.equals(VCardConstants.ImportOnly.PROPERTY_X_NICKNAME)) {
addNickName(propValue);
- } else if (propName.equals(Constants.PROPERTY_SOUND)) {
- Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
- if (typeCollection != null && typeCollection.contains(Constants.PARAM_TYPE_X_IRMC_N)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_SOUND)) {
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+ if (typeCollection != null
+ && typeCollection.contains(VCardConstants.PARAM_TYPE_X_IRMC_N)) {
// 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.
@@ -778,7 +779,7 @@
} else {
// Ignore this field since Android cannot understand what it is.
}
- } else if (propName.equals(Constants.PROPERTY_ADR)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_ADR)) {
boolean valuesAreAllEmpty = true;
for (String value : propValueList) {
if (value.length() > 0) {
@@ -793,25 +794,25 @@
int type = -1;
String label = "";
boolean isPrimary = false;
- Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
- } else if (typeString.equals(Constants.PARAM_TYPE_HOME)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_HOME)) {
type = StructuredPostal.TYPE_HOME;
label = "";
- } else if (typeString.equals(Constants.PARAM_TYPE_WORK) ||
- typeString.equalsIgnoreCase(Constants.PARAM_EXTRA_TYPE_COMPANY)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_WORK) ||
+ typeString.equalsIgnoreCase(VCardConstants.PARAM_EXTRA_TYPE_COMPANY)) {
// "COMPANY" seems emitted by Windows Mobile, which is not
// specifically supported by vCard 2.1. We assume this is same
// as "WORK".
type = StructuredPostal.TYPE_WORK;
label = "";
- } else if (typeString.equals(Constants.PARAM_ADR_TYPE_PARCEL) ||
- typeString.equals(Constants.PARAM_ADR_TYPE_DOM) ||
- typeString.equals(Constants.PARAM_ADR_TYPE_INTL)) {
+ } else if (typeString.equals(VCardConstants.PARAM_ADR_TYPE_PARCEL) ||
+ typeString.equals(VCardConstants.PARAM_ADR_TYPE_DOM) ||
+ typeString.equals(VCardConstants.PARAM_ADR_TYPE_INTL)) {
// We do not have any appropriate way to store this information.
} else {
if (typeString.startsWith("X-") && type < 0) {
@@ -830,21 +831,21 @@
}
addPostal(type, propValueList, label, isPrimary);
- } else if (propName.equals(Constants.PROPERTY_EMAIL)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_EMAIL)) {
int type = -1;
String label = null;
boolean isPrimary = false;
- Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
- } else if (typeString.equals(Constants.PARAM_TYPE_HOME)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_HOME)) {
type = Email.TYPE_HOME;
- } else if (typeString.equals(Constants.PARAM_TYPE_WORK)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_WORK)) {
type = Email.TYPE_WORK;
- } else if (typeString.equals(Constants.PARAM_TYPE_CELL)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_CELL)) {
type = Email.TYPE_MOBILE;
} else {
if (typeString.startsWith("X-") && type < 0) {
@@ -862,26 +863,26 @@
type = Email.TYPE_OTHER;
}
addEmail(type, propValue, label, isPrimary);
- } else if (propName.equals(Constants.PROPERTY_ORG)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_ORG)) {
// vCard specification does not specify other types.
final int type = Organization.TYPE_WORK;
boolean isPrimary = false;
- Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
- if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
}
}
}
handleOrgValue(type, propValueList, isPrimary);
- } else if (propName.equals(Constants.PROPERTY_TITLE)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_TITLE)) {
handleTitleValue(propValue);
- } else if (propName.equals(Constants.PROPERTY_ROLE)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_ROLE)) {
// This conflicts with TITLE. Ignore for now...
// handleTitleValue(propValue);
- } else if (propName.equals(Constants.PROPERTY_PHOTO) ||
- propName.equals(Constants.PROPERTY_LOGO)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_PHOTO) ||
+ propName.equals(VCardConstants.PROPERTY_LOGO)) {
Collection<String> paramMapValue = paramMap.get("VALUE");
if (paramMapValue != null && paramMapValue.contains("URL")) {
// Currently we do not have appropriate example for testing this case.
@@ -891,7 +892,7 @@
boolean isPrimary = false;
if (typeCollection != null) {
for (String typeValue : typeCollection) {
- if (Constants.PARAM_TYPE_PREF.equals(typeValue)) {
+ if (VCardConstants.PARAM_TYPE_PREF.equals(typeValue)) {
isPrimary = true;
} else if (formatName == null){
formatName = typeValue;
@@ -900,8 +901,8 @@
}
addPhotoBytes(formatName, propBytes, isPrimary);
}
- } else if (propName.equals(Constants.PROPERTY_TEL)) {
- final Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
+ } else if (propName.equals(VCardConstants.PROPERTY_TEL)) {
+ final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
final Object typeObject =
VCardUtils.getPhoneTypeFromStrings(typeCollection, propValue);
final int type;
@@ -915,18 +916,18 @@
}
final boolean isPrimary;
- if (typeCollection != null && typeCollection.contains(Constants.PARAM_TYPE_PREF)) {
+ if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
} else {
isPrimary = false;
}
addPhone(type, propValue, label, isPrimary);
- } else if (propName.equals(Constants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
// The phone number available via Skype.
- Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
final int type = Phone.TYPE_OTHER;
final boolean isPrimary;
- if (typeCollection != null && typeCollection.contains(Constants.PARAM_TYPE_PREF)) {
+ if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
} else {
isPrimary = false;
@@ -936,15 +937,15 @@
final int protocol = sImMap.get(propName);
boolean isPrimary = false;
int type = -1;
- final Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
+ final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
- if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
} else if (type < 0) {
- if (typeString.equalsIgnoreCase(Constants.PARAM_TYPE_HOME)) {
+ if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_HOME)) {
type = Im.TYPE_HOME;
- } else if (typeString.equalsIgnoreCase(Constants.PARAM_TYPE_WORK)) {
+ } else if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_WORK)) {
type = Im.TYPE_WORK;
}
}
@@ -954,22 +955,22 @@
type = Phone.TYPE_HOME;
}
addIm(protocol, null, type, propValue, isPrimary);
- } else if (propName.equals(Constants.PROPERTY_NOTE)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_NOTE)) {
addNote(propValue);
- } else if (propName.equals(Constants.PROPERTY_URL)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_URL)) {
if (mWebsiteList == null) {
mWebsiteList = new ArrayList<String>(1);
}
mWebsiteList.add(propValue);
- } else if (propName.equals(Constants.PROPERTY_BDAY)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_BDAY)) {
mBirthday = propValue;
- } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
mPhoneticGivenName = propValue;
- } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
mPhoneticMiddleName = propValue;
- } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_LAST_NAME)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME)) {
mPhoneticFamilyName = propValue;
- } else if (propName.equals(Constants.PROPERTY_X_ANDROID_CUSTOM)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_X_ANDROID_CUSTOM)) {
final List<String> customPropertyList =
VCardUtils.constructListFromValue(propValue,
VCardConfig.isV30(mVCardType));
@@ -1247,10 +1248,10 @@
int size = customPropertyList.size();
if (size < 2 || TextUtils.isEmpty(customPropertyList.get(0))) {
continue;
- } else if (size > Constants.MAX_DATA_COLUMN + 1) {
- size = Constants.MAX_DATA_COLUMN + 1;
+ } else if (size > VCardConstants.MAX_DATA_COLUMN + 1) {
+ size = VCardConstants.MAX_DATA_COLUMN + 1;
customPropertyList =
- customPropertyList.subList(0, Constants.MAX_DATA_COLUMN + 2);
+ customPropertyList.subList(0, VCardConstants.MAX_DATA_COLUMN + 2);
}
int i = 0;
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index 3bbf698..0dea972 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -178,7 +178,7 @@
}
protected String getVersionString() {
- return Constants.VERSION_V21;
+ return VCardConstants.VERSION_V21;
}
/**
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 3dd467c..7314b87 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -84,7 +84,7 @@
@Override
protected String getVersionString() {
- return Constants.VERSION_V30;
+ return VCardConstants.VERSION_V30;
}
@Override
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index 12a8e42..45c0172 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -50,41 +50,44 @@
sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>();
sKnownPhoneTypeMap_StoI = new HashMap<String, Integer>();
- sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, Constants.PARAM_TYPE_CAR);
- sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_CAR, Phone.TYPE_CAR);
- sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, Constants.PARAM_TYPE_PAGER);
- sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_PAGER, Phone.TYPE_PAGER);
- sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, Constants.PARAM_TYPE_ISDN);
- sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_ISDN, Phone.TYPE_ISDN);
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, VCardConstants.PARAM_TYPE_CAR);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CAR, Phone.TYPE_CAR);
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, VCardConstants.PARAM_TYPE_PAGER);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_PAGER, Phone.TYPE_PAGER);
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, VCardConstants.PARAM_TYPE_ISDN);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_ISDN, Phone.TYPE_ISDN);
- sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_HOME, Phone.TYPE_HOME);
- sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_WORK, Phone.TYPE_WORK);
- sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_CELL, Phone.TYPE_MOBILE);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_HOME, Phone.TYPE_HOME);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_WORK, Phone.TYPE_WORK);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CELL, Phone.TYPE_MOBILE);
- sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
- sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_CALLBACK, Phone.TYPE_CALLBACK);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_CALLBACK,
+ Phone.TYPE_CALLBACK);
sKnownPhoneTypeMap_StoI.put(
- Constants.PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
- sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
- sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_TTY_TDD, Phone.TYPE_TTY_TDD);
- sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_ASSISTANT,
+ VCardConstants.PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_TTY_TDD,
+ Phone.TYPE_TTY_TDD);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_ASSISTANT,
Phone.TYPE_ASSISTANT);
sPhoneTypesUnknownToContactsSet = new HashSet<String>();
- sPhoneTypesUnknownToContactsSet.add(Constants.PARAM_TYPE_MODEM);
- sPhoneTypesUnknownToContactsSet.add(Constants.PARAM_TYPE_BBS);
- sPhoneTypesUnknownToContactsSet.add(Constants.PARAM_TYPE_VIDEO);
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MODEM);
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_BBS);
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_VIDEO);
sKnownImPropNameMap_ItoS = new HashMap<Integer, String>();
- sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, Constants.PROPERTY_X_AIM);
- sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, Constants.PROPERTY_X_MSN);
- sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, Constants.PROPERTY_X_YAHOO);
- sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, Constants.PROPERTY_X_SKYPE_USERNAME);
- sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK, Constants.PROPERTY_X_GOOGLE_TALK);
- sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, Constants.PROPERTY_X_ICQ);
- sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, Constants.PROPERTY_X_JABBER);
- sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, Constants.PROPERTY_X_QQ);
- sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, Constants.PROPERTY_X_NETMEETING);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK,
+ VCardConstants.PROPERTY_X_GOOGLE_TALK);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, VCardConstants.PROPERTY_X_QQ);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, VCardConstants.PROPERTY_X_NETMEETING);
// \u643A\u5E2F\u96FB\u8A71 = Full-width Hiragana "Keitai-Denwa" (mobile phone)
// \u643A\u5E2F = Full-width Hiragana "Keitai" (mobile phone)
@@ -119,9 +122,9 @@
continue;
}
typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
hasPref = true;
- } else if (typeString.equals(Constants.PARAM_TYPE_FAX)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_FAX)) {
isFax = true;
} else {
if (typeString.startsWith("X-") && type < 0) {
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java
index c1727de..dcdc167 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java
@@ -26,6 +26,7 @@
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
@@ -52,12 +53,13 @@
VCardVerifier verifier = new VCardVerifier(resolver, V21);
verifier.addPropertyNodesVerifierElem()
.addNodeWithoutOrder("FN", "Roid Ando")
- .addNodeWithoutOrder("N", "Ando;Roid;;;", Arrays.asList("Ando", "Roid", "", "", ""));
+ .addNodeWithoutOrder("N", "Ando;Roid;;;",
+ Arrays.asList("Ando", "Roid", "", "", ""));
verifier.verify();
}
- private void testStructuredNameBasic(int version) {
- final boolean isV30 = VCardConfig.isV30(version);
+ private void testStructuredNameBasic(int vcardType) {
+ final boolean isV30 = VCardConfig.isV30(vcardType);
ExportTestResolver resolver = new ExportTestResolver();
resolver.buildContactEntry().buildData(StructuredName.CONTENT_ITEM_TYPE)
@@ -70,7 +72,7 @@
.put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle");
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
PropertyNodesVerifierElem elem = verifier.addPropertyNodesVerifierElem()
.addNodeWithOrder("N",
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
@@ -107,8 +109,8 @@
* which presume that there's only one property toward each of "N", "FN", etc.
* Note that more than one "N", "FN", etc. properties are acceptable in vCard spec.
*/
- private void testStructuredNameUsePrimaryCommon(int version) {
- final boolean isV30 = (version == V30);
+ private void testStructuredNameUsePrimaryCommon(int vcardType) {
+ final boolean isV30 = (vcardType == V30);
ExportTestResolver resolver = new ExportTestResolver();
ContactEntry entry = resolver.buildContactEntry();
@@ -146,7 +148,7 @@
.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2")
.put(StructuredName.IS_PRIMARY, 1);
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
PropertyNodesVerifierElem elem = verifier.addPropertyNodesVerifierElem()
.addNodeWithOrder("N",
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
@@ -181,8 +183,8 @@
* Tests that only "super primary" StructuredName is emitted.
* See also the comment in {@link #testStructuredNameUsePrimaryCommon(int)}.
*/
- private void testStructuredNameUseSuperPrimaryCommon(int version) {
- final boolean isV30 = (version == V30);
+ private void testStructuredNameUseSuperPrimaryCommon(int vcardType) {
+ final boolean isV30 = (vcardType == V30);
ExportTestResolver resolver = new ExportTestResolver();
ContactEntry entry = resolver.buildContactEntry();
@@ -231,7 +233,7 @@
.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle3")
.put(StructuredName.IS_PRIMARY, 1);
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
PropertyNodesVerifierElem elem = verifier.addPropertyNodesVerifierElem()
.addNodeWithOrder("N",
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
@@ -274,14 +276,14 @@
verifier.verify();
}
- private void testPhoneBasicCommon(int version) {
+ private void testPhoneBasicCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
resolver.buildContactEntry().buildData(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "1")
.put(Phone.TYPE, Phone.TYPE_HOME);
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElemWithEmptyName()
.addNodeWithoutOrder("TEL", "1", new TypeSet("HOME"));
@@ -299,7 +301,7 @@
/**
* Tests that vCard composer emits corresponding type param which we expect.
*/
- private void testPhoneVariousTypeSupport(int version) {
+ private void testPhoneVariousTypeSupport(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
ContactEntry entry = resolver.buildContactEntry();
@@ -352,7 +354,7 @@
.put(Phone.NUMBER, "160")
.put(Phone.TYPE, Phone.TYPE_MMS);
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElemWithEmptyName()
.addNodeWithoutOrder("TEL", "10", new TypeSet("HOME"))
.addNodeWithoutOrder("TEL", "20", new TypeSet("WORK"))
@@ -384,7 +386,7 @@
/**
* Tests that "PREF"s are emitted appropriately.
*/
- private void testPhonePrefHandlingCommon(int version) {
+ private void testPhonePrefHandlingCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
ContactEntry entry = resolver.buildContactEntry();
entry.buildData(Phone.CONTENT_ITEM_TYPE)
@@ -402,7 +404,7 @@
.put(Phone.NUMBER, "4")
.put(Phone.TYPE, Phone.TYPE_FAX_WORK);
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElemWithEmptyName()
.addNodeWithoutOrder("TEL", "4", new TypeSet("WORK", "FAX"))
.addNodeWithoutOrder("TEL", "3", new TypeSet("HOME", "FAX", "PREF"))
@@ -475,12 +477,12 @@
testMiscPhoneTypeHandling(V30);
}
- private void testEmailBasicCommon(int version) {
+ private void testEmailBasicCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
resolver.buildContactEntry().buildData(Email.CONTENT_ITEM_TYPE)
.put(Email.DATA, "sample@example.com");
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElemWithEmptyName()
.addNodeWithoutOrder("EMAIL", "sample@example.com");
@@ -496,7 +498,7 @@
testEmailBasicCommon(V30);
}
- private void testEmailVariousTypeSupportCommon(int version) {
+ private void testEmailVariousTypeSupportCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
ContactEntry entry = resolver.buildContactEntry();
@@ -513,7 +515,7 @@
.put(Email.DATA, "type_other@example.com")
.put(Email.TYPE, Email.TYPE_OTHER);
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElemWithEmptyName()
.addNodeWithoutOrder("EMAIL", "type_home@example.com", new TypeSet("HOME"))
@@ -532,7 +534,7 @@
testEmailVariousTypeSupportCommon(V30);
}
- private void testEmailPrefHandlingCommon(int version) {
+ private void testEmailPrefHandlingCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
ContactEntry entry = resolver.buildContactEntry();
@@ -544,7 +546,7 @@
.put(Email.DATA, "type_notype@example.com")
.put(Email.IS_PRIMARY, 1);
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElemWithEmptyName()
.addNodeWithoutOrder("EMAIL", "type_notype@example.com", new TypeSet("PREF"))
@@ -561,7 +563,7 @@
testEmailPrefHandlingCommon(V30);
}
- private void testPostalOnlyWithStructuredDataCommon(int version) {
+ private void testPostalOnlyWithStructuredDataCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
// adr-value = 0*6(text-value ";") text-value
@@ -575,7 +577,7 @@
.put(StructuredPostal.REGION, "Region")
.put(StructuredPostal.POSTCODE, "100")
.put(StructuredPostal.COUNTRY, "Country");
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElemWithEmptyName()
.addNodeWithoutOrder("ADR", "Pobox;Neighborhood;Street;City;Region;100;Country",
Arrays.asList("Pobox", "Neighborhood", "Street", "City",
@@ -592,14 +594,14 @@
testPostalOnlyWithStructuredDataCommon(V30);
}
- private void testPostalOnlyWithFormattedAddressCommon(int version) {
+ private void testPostalOnlyWithFormattedAddressCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
resolver.buildContactEntry().buildData(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.FORMATTED_ADDRESS,
"Formatted address CA 123-334 United Statue");
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElemWithEmptyName()
.addNodeWithOrder("ADR", ";Formatted address CA 123-334 United Statue;;;;;",
Arrays.asList("", "Formatted address CA 123-334 United Statue",
@@ -620,7 +622,7 @@
* Tests that the vCard composer honors formatted data when it is available
* even when it is partial.
*/
- private void testPostalWithBothStructuredAndFormattedCommon(int version) {
+ private void testPostalWithBothStructuredAndFormattedCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
resolver.buildContactEntry().buildData(StructuredPostal.CONTENT_ITEM_TYPE)
@@ -629,7 +631,7 @@
.put(StructuredPostal.FORMATTED_ADDRESS,
"Formatted address CA 123-334 United Statue"); // Should be ignored
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElemWithEmptyName()
.addNodeWithoutOrder("ADR", "Pobox;;;;;;Country",
Arrays.asList("Pobox", "", "", "", "", "", "Country"),
@@ -646,7 +648,7 @@
testPostalWithBothStructuredAndFormattedCommon(V30);
}
- private void testOrganizationCommon(int version) {
+ private void testOrganizationCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
ContactEntry entry = resolver.buildContactEntry();
entry.buildData(Organization.CONTENT_ITEM_TYPE)
@@ -668,7 +670,7 @@
.putNull(Organization.DEPARTMENT)
.put(Organization.TITLE, "TitleXYZYX");
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
// Currently we do not use group but depend on the order.
verifier.addPropertyNodesVerifierElemWithEmptyName()
@@ -690,7 +692,7 @@
testOrganizationCommon(V30);
}
- private void testImVariousTypeSupportCommon(int version) {
+ private void testImVariousTypeSupportCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
ContactEntry entry = resolver.buildContactEntry();
@@ -732,7 +734,7 @@
// No determined way to express unknown type...
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElemWithEmptyName()
.addNodeWithoutOrder("X-JABBER", "jabber")
.addNodeWithoutOrder("X-ICQ", "icq")
@@ -755,7 +757,7 @@
testImVariousTypeSupportCommon(V30);
}
- private void testImPrefHandlingCommon(int version) {
+ private void testImPrefHandlingCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
ContactEntry entry = resolver.buildContactEntry();
@@ -769,7 +771,7 @@
.put(Im.TYPE, Im.TYPE_HOME)
.put(Im.IS_PRIMARY, 1);
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElemWithEmptyName()
.addNodeWithoutOrder("X-AIM", "aim1")
.addNodeWithoutOrder("X-AIM", "aim2", new TypeSet("HOME", "PREF"));
@@ -785,7 +787,7 @@
testImPrefHandlingCommon(V30);
}
- private void testWebsiteCommon(int version) {
+ private void testWebsiteCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
ContactEntry entry = resolver.buildContactEntry();
@@ -798,7 +800,7 @@
.put(Website.TYPE, Website.TYPE_FTP);
// We drop TYPE information since vCard (especially 3.0) does not allow us to emit it.
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElemWithEmptyName()
.addNodeWithoutOrder("URL", "ftp://ftp.example.android.com/index.html")
.addNodeWithoutOrder("URL", "http://website.example.android.com/index.html");
@@ -813,7 +815,19 @@
testWebsiteCommon(V30);
}
- private void testEventCommon(int version) {
+ private String getAndroidPropValue(final String mimeType, String value,
+ Integer type) {
+ return getAndroidPropValue(mimeType, value, type, null);
+ }
+
+ private String getAndroidPropValue(final String mimeType, String value,
+ Integer type, String label) {
+ return (mimeType + ";" + value + ";"
+ + (type != null ? type : "") + ";"
+ + (label != null ? label : "") + ";;;;;;;;;;;;");
+ }
+
+ private void testEventCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
ContactEntry entry = resolver.buildContactEntry();
@@ -832,12 +846,22 @@
.put(Event.START_DATE, "When the Tower of Hanoi with 64 rings is completed.");
entry.buildData(Event.CONTENT_ITEM_TYPE)
.put(Event.TYPE, Event.TYPE_BIRTHDAY)
- .put(Event.START_DATE, "2009-05-19");
+ .put(Event.START_DATE, "2009-05-19"); // Should be ignored.
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElemWithEmptyName()
- .addNodeWithoutOrder("BDAY", "2008-10-22");
-
+ .addNodeWithoutOrder("BDAY", "2008-10-22")
+ .addNodeWithoutOrder("X-ANDROID-CUSTOM",
+ getAndroidPropValue(
+ Event.CONTENT_ITEM_TYPE, "1982-06-16", Event.TYPE_ANNIVERSARY))
+ .addNodeWithoutOrder("X-ANDROID-CUSTOM",
+ getAndroidPropValue(
+ Event.CONTENT_ITEM_TYPE, "2018-03-12", Event.TYPE_OTHER))
+ .addNodeWithoutOrder("X-ANDROID-CUSTOM",
+ getAndroidPropValue(
+ Event.CONTENT_ITEM_TYPE,
+ "When the Tower of Hanoi with 64 rings is completed.",
+ Event.TYPE_CUSTOM, "The last day"));
verifier.verify();
}
@@ -849,7 +873,7 @@
testEventCommon(V30);
}
- private void testNoteCommon(int version) {
+ private void testNoteCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
ContactEntry entry = resolver.buildContactEntry();
@@ -859,7 +883,7 @@
.put(Note.NOTE, "note2")
.put(Note.IS_PRIMARY, 1); // Just ignored.
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElemWithEmptyName()
.addNodeWithOrder("NOTE", "note1")
.addNodeWithOrder("NOTE", "note2");
@@ -875,8 +899,8 @@
testNoteCommon(V30);
}
- private void testPhotoCommon(int version) {
- final boolean isV30 = version == V30;
+ private void testPhotoCommon(int vcardType) {
+ final boolean isV30 = vcardType == V30;
ExportTestResolver resolver = new ExportTestResolver();
ContactEntry entry = resolver.buildContactEntry();
entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
@@ -886,7 +910,7 @@
ContentValues contentValuesForPhoto = new ContentValues();
contentValuesForPhoto.put("ENCODING", (isV30 ? "b" : "BASE64"));
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElem()
.addNodeWithoutOrder("FN", "PhotoTest")
.addNodeWithoutOrder("N", "PhotoTest;;;;",
@@ -905,8 +929,31 @@
testPhotoCommon(V30);
}
+ private void testRelationCommon(int vcardType) {
+ ExportTestResolver resolver = new ExportTestResolver();
+ ContactEntry entry = resolver.buildContactEntry();
+ entry.buildData(Relation.CONTENT_ITEM_TYPE)
+ .put(Relation.TYPE, Relation.TYPE_MOTHER)
+ .put(Relation.NAME, "Ms. Mother");
+
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
+ ImportVerifierElem elem = verifier.addImportVerifier();
+ elem.addExpected(Relation.CONTENT_ITEM_TYPE)
+ .put(Relation.TYPE, Relation.TYPE_MOTHER)
+ .put(Relation.NAME, "Ms. Mother");
+ verifier.verify();
+ }
+
+ public void testRelationV21() {
+ testRelationCommon(V21);
+ }
+
+ public void testRelationV30() {
+ testRelationCommon(V30);
+ }
+
public void testV30HandleEscape() {
- final int version = V30;
+ final int vcardType = V30;
ExportTestResolver resolver = new ExportTestResolver();
resolver.buildContactEntry().buildData(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\\")
@@ -914,7 +961,7 @@
.put(StructuredName.MIDDLE_NAME, ",")
.put(StructuredName.PREFIX, "\n")
.put(StructuredName.DISPLAY_NAME, "[<{Unescaped:Asciis}>]");
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
// Verifies the vCard String correctly escapes each character which must be escaped.
verifier.addLineVerifier()
.addExpected("N:\\\\;\\;;\\,;\\n;")
@@ -960,7 +1007,7 @@
verifier.verify();
}
- private void testPickUpNonEmptyContentValuesCommon(int version) {
+ private void testPickUpNonEmptyContentValuesCommon(int vcardType) {
ExportTestResolver resolver = new ExportTestResolver();
ContactEntry entry = resolver.buildContactEntry();
entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
@@ -975,7 +1022,7 @@
.put(StructuredName.FAMILY_NAME, "family3");
entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "family4");
- VCardVerifier verifier = new VCardVerifier(resolver, version);
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
verifier.addPropertyNodesVerifierElem()
.addNodeWithoutOrder("N", Arrays.asList("family2", "", "", "", ""))
.addNodeWithoutOrder("FN", "family2");