Loading core/java/android/pim/vcard/VCardBuilder.java +26 −14 Original line number Diff line number Diff line Loading @@ -1463,6 +1463,9 @@ public class VCardBuilder { parameterList.add(VCardConstants.PARAM_TYPE_VOICE); } else if (VCardUtils.isMobilePhoneLabel(label)) { parameterList.add(VCardConstants.PARAM_TYPE_CELL); } else if (mIsV30) { // This label is appropriately encoded in appendTypeParameters. parameterList.add(label); } else { final String upperLabel = label.toUpperCase(); if (VCardUtils.isValidInV21ButUnknownToContactsPhoteType(upperLabel)) { Loading Loading @@ -1741,12 +1744,20 @@ public class VCardBuilder { // which would be recommended way in vcard 3.0 though not valid in vCard 2.1. boolean first = true; for (final String typeValue : types) { if (VCardConfig.isV30(mVCardType)) { // Note: vCard 3.0 specifies the different type of acceptable type Strings, but // we don't emit that kind of vCard 3.0 specific type since there should be // high probabilyty in which external importers cannot understand them. // // e.g. TYPE="\u578B\u306B\u3087" (vCard 3.0 allows non-Ascii characters if they // are quoted.) if (first) { first = false; } else { mBuilder.append(VCARD_PARAM_SEPARATOR); } appendTypeParameter(VCardUtils.toStringAvailableAsV30ParameValue(typeValue)); } else { // vCard 2.1 if (!VCardUtils.isV21Word(typeValue)) { continue; } Loading @@ -1758,6 +1769,7 @@ public class VCardBuilder { appendTypeParameter(typeValue); } } } /** * VCARD_PARAM_SEPARATOR must be appended before this method being called. Loading core/java/android/pim/vcard/VCardEntryConstructor.java +4 −0 Original line number Diff line number Diff line Loading @@ -157,11 +157,15 @@ public class VCardEntryConstructor implements VCardInterpreter { mParamType = type; } @Override public void propertyParamValue(String value) { if (mParamType == null) { // From vCard 2.1 specification. vCard 3.0 formally does not allow this case. mParamType = "TYPE"; } if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) { value = encodeString(value, mCharsetForDecodedBytes); } mCurrentProperty.addParameter(mParamType, value); mParamType = null; } Loading core/java/android/pim/vcard/VCardUtils.java +38 −1 Original line number Diff line number Diff line Loading @@ -16,10 +16,10 @@ package android.pim.vcard; import android.content.ContentProviderOperation; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.CommonDataKinds.Im; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.Data; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; Loading Loading @@ -477,6 +477,43 @@ public class VCardUtils { return true; } /** * <P> * Returns String available as parameter value in vCard 3.0. * </P> * <P> * RFC 2426 requires vCard composer to quote parameter values when it contains * semi-colon, for example (See RFC 2426 for more information). * This method checks whether the given String can be used without quotes. * </P> * <P> * Note: We remove DQUOTE silently for now. * </P> */ public static String toStringAvailableAsV30ParameValue(String value) { if (TextUtils.isEmpty(value)) { value = ""; } final int asciiFirst = 0x20; final int asciiLast = 0x7E; // included final StringBuilder builder = new StringBuilder(); final int length = value.length(); boolean needQuote = false; for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) { final int codePoint = value.codePointAt(i); if (codePoint < asciiFirst || codePoint == '"') { // CTL characters and DQUOTE are never accepted. Remove them. continue; } builder.appendCodePoint(codePoint); if (codePoint == ':' || codePoint == ',' || codePoint == ' ') { needQuote = true; } } final String result = builder.toString(); return ((needQuote || result.isEmpty()) ? ('"' + result + '"') : result); } public static String toHalfWidthString(final String orgString) { if (TextUtils.isEmpty(orgString)) { return null; Loading core/tests/coretests/res/raw/v30_multibyte_param.vcf 0 → 100644 +5 −0 Original line number Diff line number Diff line BEGIN:VCARD VERSION:3.0 N:F;G;M;; TEL;TYPE="费":1 END:VCARD core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java +22 −0 Original line number Diff line number Diff line Loading @@ -1008,4 +1008,26 @@ public class VCardImporterTests extends VCardTestsBase { .put(Phone.TYPE, Phone.TYPE_PAGER) .put(Phone.NUMBER, "6101231234@pagersample.com"); } public void testMultiBytePropV30_Parse() { mVerifier.initForImportTest(V30, R.raw.v30_multibyte_param); mVerifier.addPropertyNodesVerifierElem() .addExpectedNodeWithOrder("VERSION", "3.0") .addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", "")) .addExpectedNodeWithOrder("TEL", "1", new TypeSet("\u8D39")); } public void testMultiBytePropV30() { mVerifier.initForImportTest(V30, R.raw.v30_multibyte_param); final ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.FAMILY_NAME, "F") .put(StructuredName.MIDDLE_NAME, "M") .put(StructuredName.GIVEN_NAME, "G") .put(StructuredName.DISPLAY_NAME, "G M F"); elem.addExpected(Phone.CONTENT_ITEM_TYPE) .put(Phone.TYPE, Phone.TYPE_CUSTOM) .put(Phone.LABEL, "\u8D39") .put(Phone.NUMBER, "1"); } } Loading
core/java/android/pim/vcard/VCardBuilder.java +26 −14 Original line number Diff line number Diff line Loading @@ -1463,6 +1463,9 @@ public class VCardBuilder { parameterList.add(VCardConstants.PARAM_TYPE_VOICE); } else if (VCardUtils.isMobilePhoneLabel(label)) { parameterList.add(VCardConstants.PARAM_TYPE_CELL); } else if (mIsV30) { // This label is appropriately encoded in appendTypeParameters. parameterList.add(label); } else { final String upperLabel = label.toUpperCase(); if (VCardUtils.isValidInV21ButUnknownToContactsPhoteType(upperLabel)) { Loading Loading @@ -1741,12 +1744,20 @@ public class VCardBuilder { // which would be recommended way in vcard 3.0 though not valid in vCard 2.1. boolean first = true; for (final String typeValue : types) { if (VCardConfig.isV30(mVCardType)) { // Note: vCard 3.0 specifies the different type of acceptable type Strings, but // we don't emit that kind of vCard 3.0 specific type since there should be // high probabilyty in which external importers cannot understand them. // // e.g. TYPE="\u578B\u306B\u3087" (vCard 3.0 allows non-Ascii characters if they // are quoted.) if (first) { first = false; } else { mBuilder.append(VCARD_PARAM_SEPARATOR); } appendTypeParameter(VCardUtils.toStringAvailableAsV30ParameValue(typeValue)); } else { // vCard 2.1 if (!VCardUtils.isV21Word(typeValue)) { continue; } Loading @@ -1758,6 +1769,7 @@ public class VCardBuilder { appendTypeParameter(typeValue); } } } /** * VCARD_PARAM_SEPARATOR must be appended before this method being called. Loading
core/java/android/pim/vcard/VCardEntryConstructor.java +4 −0 Original line number Diff line number Diff line Loading @@ -157,11 +157,15 @@ public class VCardEntryConstructor implements VCardInterpreter { mParamType = type; } @Override public void propertyParamValue(String value) { if (mParamType == null) { // From vCard 2.1 specification. vCard 3.0 formally does not allow this case. mParamType = "TYPE"; } if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) { value = encodeString(value, mCharsetForDecodedBytes); } mCurrentProperty.addParameter(mParamType, value); mParamType = null; } Loading
core/java/android/pim/vcard/VCardUtils.java +38 −1 Original line number Diff line number Diff line Loading @@ -16,10 +16,10 @@ package android.pim.vcard; import android.content.ContentProviderOperation; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.CommonDataKinds.Im; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.Data; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; Loading Loading @@ -477,6 +477,43 @@ public class VCardUtils { return true; } /** * <P> * Returns String available as parameter value in vCard 3.0. * </P> * <P> * RFC 2426 requires vCard composer to quote parameter values when it contains * semi-colon, for example (See RFC 2426 for more information). * This method checks whether the given String can be used without quotes. * </P> * <P> * Note: We remove DQUOTE silently for now. * </P> */ public static String toStringAvailableAsV30ParameValue(String value) { if (TextUtils.isEmpty(value)) { value = ""; } final int asciiFirst = 0x20; final int asciiLast = 0x7E; // included final StringBuilder builder = new StringBuilder(); final int length = value.length(); boolean needQuote = false; for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) { final int codePoint = value.codePointAt(i); if (codePoint < asciiFirst || codePoint == '"') { // CTL characters and DQUOTE are never accepted. Remove them. continue; } builder.appendCodePoint(codePoint); if (codePoint == ':' || codePoint == ',' || codePoint == ' ') { needQuote = true; } } final String result = builder.toString(); return ((needQuote || result.isEmpty()) ? ('"' + result + '"') : result); } public static String toHalfWidthString(final String orgString) { if (TextUtils.isEmpty(orgString)) { return null; Loading
core/tests/coretests/res/raw/v30_multibyte_param.vcf 0 → 100644 +5 −0 Original line number Diff line number Diff line BEGIN:VCARD VERSION:3.0 N:F;G;M;; TEL;TYPE="费":1 END:VCARD
core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java +22 −0 Original line number Diff line number Diff line Loading @@ -1008,4 +1008,26 @@ public class VCardImporterTests extends VCardTestsBase { .put(Phone.TYPE, Phone.TYPE_PAGER) .put(Phone.NUMBER, "6101231234@pagersample.com"); } public void testMultiBytePropV30_Parse() { mVerifier.initForImportTest(V30, R.raw.v30_multibyte_param); mVerifier.addPropertyNodesVerifierElem() .addExpectedNodeWithOrder("VERSION", "3.0") .addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", "")) .addExpectedNodeWithOrder("TEL", "1", new TypeSet("\u8D39")); } public void testMultiBytePropV30() { mVerifier.initForImportTest(V30, R.raw.v30_multibyte_param); final ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.FAMILY_NAME, "F") .put(StructuredName.MIDDLE_NAME, "M") .put(StructuredName.GIVEN_NAME, "G") .put(StructuredName.DISPLAY_NAME, "G M F"); elem.addExpected(Phone.CONTENT_ITEM_TYPE) .put(Phone.TYPE, Phone.TYPE_CUSTOM) .put(Phone.LABEL, "\u8D39") .put(Phone.NUMBER, "1"); } }