am ac3e2159: Merge change Icaf78440 into eclair-mr2

Merge commit 'ac3e2159b41b825874cf574cf59225d4fbddfbcb' into eclair-mr2-plus-aosp

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