From 1b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ce Mon Sep 17 00:00:00 2001 From: Daisuke Miyakawa Date: Tue, 17 Nov 2009 11:40:45 +0900 Subject: [PATCH] Split vCard composer into two parts: VCardComposer and VCardBuilder. As for VCardBuilder, there was a class with the same name, but this implementation is fundamentally different. This time, VCardBuilder is like StringBuilder. It enables developers to create their own vCard by themselves. Make Constants public and rename it to VCardConstants. Internal issue number: 2242528, 2195990 --- core/java/android/pim/vcard/VCardBuilder.java | 1874 ++++++++++++++++ .../java/android/pim/vcard/VCardComposer.java | 1881 +---------------- .../{Constants.java => VCardConstants.java} | 5 +- core/java/android/pim/vcard/VCardEntry.java | 123 +- .../android/pim/vcard/VCardParser_V21.java | 2 +- .../android/pim/vcard/VCardParser_V30.java | 2 +- core/java/android/pim/vcard/VCardUtils.java | 61 +- .../unit_tests/vcard/VCardExporterTests.java | 147 +- 8 files changed, 2109 insertions(+), 1986 deletions(-) create mode 100644 core/java/android/pim/vcard/VCardBuilder.java rename core/java/android/pim/vcard/{Constants.java => VCardConstants.java} (99%) diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java new file mode 100644 index 0000000000000..eb40a3f48e7fb --- /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 sAllowedAndroidPropertySet = + Collections.unmodifiableSet(new HashSet(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 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 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 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 contentValuesList) { + boolean phoneLineExists = false; + if (contentValuesList != null) { + Set phoneSet = new HashSet(); + 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 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 splitIfSeveralPhoneNumbersExist(final String phoneNumber) { + List phoneList = new ArrayList(); + + 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 contentValuesList) { + boolean emailAddressExists = false; + if (contentValuesList != null) { + final Set addressSet = new HashSet(); + 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 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 sPostalTypePriorityMap; + + static { + sPostalTypePriorityMap = new HashMap(); + 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 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 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 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 parameterList = new ArrayList(); + 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 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 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 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 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 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 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 parameterList = new ArrayList(); + 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 parameterList = new ArrayList(); + 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 parameterList = new ArrayList(); + 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 rawValueList = new ArrayList(); + rawValueList.add(mimeType); + final List 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 rawValueList) { + appendLineWithCharsetAndQPDetection(propertyName, null, rawValueList); + } + + public void appendLineWithCharsetAndQPDetection(final String propertyName, + final List 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 parameterList, final List 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 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 parameterList, + final String rawValue) { + appendLine(propertyName, parameterList, rawValue, false, false); + } + + public void appendLine(final String propertyName, final List 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 rawValueList, + final boolean needCharset, boolean needQuotedPrintable) { + appendLine(propertyName, null, rawValueList, needCharset, needQuotedPrintable); + } + + public void appendLine(final String propertyName, final List parameterList, + final List 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 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 d9f38bbf04c62..c2d628feb6114 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.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.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 @@ public class VCardComposer { 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 @@ public class VCardComposer { builder.appendQueryParameter(Data.FOR_EXPORT_ONLY, "1"); sDataRequestUri = builder.build(); sImMap = new HashMap(); - 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(); - 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 @@ public class VCardComposer { 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 mHandlerList; @@ -361,18 +330,8 @@ public class VCardComposer { 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(); if (mIsDoCoMo) { @@ -384,10 +343,6 @@ public class VCardComposer { 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 @@ public class VCardComposer { charset = SHIFT_JIS; } mCharsetString = charset; - mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS; } else { mCharsetString = UTF_8; - mVCardCharsetParameter = "CHARSET=" + UTF_8; } } @@ -583,36 +536,19 @@ public class VCardComposer { 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 @@ public class VCardComposer { 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 @@ public class VCardComposer { return mErrorReason; } - private void appendStructuredNames(final StringBuilder builder, - final Map> contentValuesListMap) { - final List 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 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> contentValuesListMap) { - final List 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> contentValuesListMap) { - final List contentValuesList = contentValuesListMap - .get(Phone.CONTENT_ITEM_TYPE); - boolean phoneLineExists = false; - if (contentValuesList != null) { - Set phoneSet = new HashSet(); - 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 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 splitIfSeveralPhoneNumbersExist(final String phoneNumber) { - List phoneList = new ArrayList(); - - 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> contentValuesListMap) { - final List contentValuesList = contentValuesListMap - .get(Email.CONTENT_ITEM_TYPE); - - boolean emailAddressExists = false; - if (contentValuesList != null) { - final Set addressSet = new HashSet(); - 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> contentValuesListMap) { - final List 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 sPostalTypePriorityMap; - - static { - sPostalTypePriorityMap = new HashMap(); - 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 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 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> contentValuesListMap) { - final List 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 parameterList = new ArrayList(); - 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> contentValuesListMap) { - final List 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> contentValuesListMap) { - final List 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> contentValuesListMap) { - final List 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> contentValuesListMap) { - final List 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> contentValuesListMap) { - final List 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 rawDataList = new ArrayList(); - rawDataList.add(mimeType); - final List 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 parameterList = new ArrayList(); - 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 parameterList = new ArrayList(); - 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 parameterList = new ArrayList(); - 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 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 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. - - private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder, - final String propertyName, final List rawDataList) { - appendVCardLineWithCharsetAndQPDetection(builder, propertyName, null, rawDataList); - } - - private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder, - final String propertyName, - final List parameterList, final List 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 rawDataList) { - appendVCardLine(builder, propertyName, rawDataList, false, false); - } - - private void appendVCardLine(final StringBuilder builder, - final String propertyName, final List rawDataList, - final boolean needCharset, boolean needQuotedPrintable) { - appendVCardLine(builder, propertyName, null, rawDataList, needCharset, needQuotedPrintable); - }*/ - - private void appendVCardLine(final StringBuilder builder, - final String propertyName, - final List parameterList, - final List 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 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 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 @@ public class VCardComposer { * 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 @@ public class VCardComposer { } 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 9e4b13a848567..8c07126d56bd2 100644 --- a/core/java/android/pim/vcard/Constants.java +++ b/core/java/android/pim/vcard/VCardConstants.java @@ -18,8 +18,7 @@ package android.pim.vcard; /** * 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 android.pim.vcard; /* 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 c1b4c03da6860..a6e432d734693 100644 --- a/core/java/android/pim/vcard/VCardEntry.java +++ b/core/java/android/pim/vcard/VCardEntry.java @@ -65,14 +65,14 @@ public class VCardEntry { private static final Map sImMap = new HashMap(); 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 @@ public class VCardEntry { } 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 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 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 @@ public class VCardEntry { } 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 @@ public class VCardEntry { int type = -1; String label = ""; boolean isPrimary = false; - Collection typeCollection = paramMap.get(Constants.PARAM_TYPE); + Collection 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 @@ public class VCardEntry { } 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 typeCollection = paramMap.get(Constants.PARAM_TYPE); + Collection 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 @@ public class VCardEntry { 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 typeCollection = paramMap.get(Constants.PARAM_TYPE); + Collection 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 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 @@ public class VCardEntry { 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 @@ public class VCardEntry { } addPhotoBytes(formatName, propBytes, isPrimary); } - } else if (propName.equals(Constants.PROPERTY_TEL)) { - final Collection typeCollection = paramMap.get(Constants.PARAM_TYPE); + } else if (propName.equals(VCardConstants.PROPERTY_TEL)) { + final Collection typeCollection = paramMap.get(VCardConstants.PARAM_TYPE); final Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection, propValue); final int type; @@ -915,18 +916,18 @@ public class VCardEntry { } 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 typeCollection = paramMap.get(Constants.PARAM_TYPE); + Collection 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 @@ public class VCardEntry { final int protocol = sImMap.get(propName); boolean isPrimary = false; int type = -1; - final Collection typeCollection = paramMap.get(Constants.PARAM_TYPE); + final Collection 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 @@ public class VCardEntry { 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(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 customPropertyList = VCardUtils.constructListFromValue(propValue, VCardConfig.isV30(mVCardType)); @@ -1247,10 +1248,10 @@ public class VCardEntry { 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 3bbf698f26359..0dea972db0490 100644 --- a/core/java/android/pim/vcard/VCardParser_V21.java +++ b/core/java/android/pim/vcard/VCardParser_V21.java @@ -178,7 +178,7 @@ public class VCardParser_V21 extends VCardParser { } 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 3dd467caee54d..7314b87d8f857 100644 --- a/core/java/android/pim/vcard/VCardParser_V30.java +++ b/core/java/android/pim/vcard/VCardParser_V30.java @@ -84,7 +84,7 @@ public class VCardParser_V30 extends VCardParser_V21 { @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 12a8e424abbe1..45c0172288f04 100644 --- a/core/java/android/pim/vcard/VCardUtils.java +++ b/core/java/android/pim/vcard/VCardUtils.java @@ -50,41 +50,44 @@ public class VCardUtils { sKnownPhoneTypesMap_ItoS = new HashMap(); sKnownPhoneTypeMap_StoI = new HashMap(); - 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(); - 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(); - 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 @@ public class VCardUtils { 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 c1727ded7c8e1..dcdc167dc9dcd 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.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; @@ -52,12 +53,13 @@ public class VCardExporterTests extends VCardTestsBase { 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 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { * 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 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { * 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 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { 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 @@ public class VCardExporterTests extends VCardTestsBase { /** * 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 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { /** * 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 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { 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 @@ public class VCardExporterTests extends VCardTestsBase { testEmailBasicCommon(V30); } - private void testEmailVariousTypeSupportCommon(int version) { + private void testEmailVariousTypeSupportCommon(int vcardType) { ExportTestResolver resolver = new ExportTestResolver(); ContactEntry entry = resolver.buildContactEntry(); @@ -513,7 +515,7 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { testEmailVariousTypeSupportCommon(V30); } - private void testEmailPrefHandlingCommon(int version) { + private void testEmailPrefHandlingCommon(int vcardType) { ExportTestResolver resolver = new ExportTestResolver(); ContactEntry entry = resolver.buildContactEntry(); @@ -544,7 +546,7 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { 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 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { 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 @@ public class VCardExporterTests extends VCardTestsBase { * 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 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { 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 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { testOrganizationCommon(V30); } - private void testImVariousTypeSupportCommon(int version) { + private void testImVariousTypeSupportCommon(int vcardType) { ExportTestResolver resolver = new ExportTestResolver(); ContactEntry entry = resolver.buildContactEntry(); @@ -732,7 +734,7 @@ public class VCardExporterTests extends VCardTestsBase { // 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 @@ public class VCardExporterTests extends VCardTestsBase { testImVariousTypeSupportCommon(V30); } - private void testImPrefHandlingCommon(int version) { + private void testImPrefHandlingCommon(int vcardType) { ExportTestResolver resolver = new ExportTestResolver(); ContactEntry entry = resolver.buildContactEntry(); @@ -769,7 +771,7 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { testImPrefHandlingCommon(V30); } - private void testWebsiteCommon(int version) { + private void testWebsiteCommon(int vcardType) { ExportTestResolver resolver = new ExportTestResolver(); ContactEntry entry = resolver.buildContactEntry(); @@ -798,7 +800,7 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { 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 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { testEventCommon(V30); } - private void testNoteCommon(int version) { + private void testNoteCommon(int vcardType) { ExportTestResolver resolver = new ExportTestResolver(); ContactEntry entry = resolver.buildContactEntry(); @@ -859,7 +883,7 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { 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 @@ public class VCardExporterTests extends VCardTestsBase { 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 @@ public class VCardExporterTests extends VCardTestsBase { 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 @@ public class VCardExporterTests extends VCardTestsBase { .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 @@ public class VCardExporterTests extends VCardTestsBase { 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 @@ public class VCardExporterTests extends VCardTestsBase { .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");