diff options
6 files changed, 125 insertions, 15 deletions
| diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java index 1da6d7a252fd..d634672b74ce 100644 --- a/core/java/android/pim/vcard/VCardBuilder.java +++ b/core/java/android/pim/vcard/VCardBuilder.java @@ -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)) { @@ -1741,21 +1744,30 @@ 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) { -            // 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 (!VCardUtils.isV21Word(typeValue)) { -                continue; -            } -            if (first) { -                first = false; -            } else { -                mBuilder.append(VCARD_PARAM_SEPARATOR); +            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; +                } +                if (first) { +                    first = false; +                } else { +                    mBuilder.append(VCARD_PARAM_SEPARATOR); +                } +                appendTypeParameter(typeValue);              } -            appendTypeParameter(typeValue);          }      } diff --git a/core/java/android/pim/vcard/VCardEntryConstructor.java b/core/java/android/pim/vcard/VCardEntryConstructor.java index 290ca2bfec14..ae4ec29ead27 100644 --- a/core/java/android/pim/vcard/VCardEntryConstructor.java +++ b/core/java/android/pim/vcard/VCardEntryConstructor.java @@ -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;      } diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java index 11b112b76dbb..f9727992a022 100644 --- a/core/java/android/pim/vcard/VCardUtils.java +++ b/core/java/android/pim/vcard/VCardUtils.java @@ -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; @@ -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; diff --git a/core/tests/coretests/res/raw/v30_multibyte_param.vcf b/core/tests/coretests/res/raw/v30_multibyte_param.vcf new file mode 100644 index 000000000000..cd200e563d11 --- /dev/null +++ b/core/tests/coretests/res/raw/v30_multibyte_param.vcf @@ -0,0 +1,5 @@ +BEGIN:VCARD
 +VERSION:3.0
 +N:F;G;M;;
 +TEL;TYPE="่ดน":1
 +END:VCARD
 diff --git a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java index 21f22540c697..e0e1f879bb2f 100644 --- a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java +++ b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java @@ -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"); +    }  } diff --git a/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java b/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java index 59299f9dff35..e805bee8cb82 100644 --- a/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java +++ b/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java @@ -82,4 +82,34 @@ public class VCardUtilsTests extends TestCase {              assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));          }      } + +    public void testToStringAvailableAsV30ParamValue() { +        // Smoke tests. +        assertEquals("HOME", VCardUtils.toStringAvailableAsV30ParameValue("HOME")); +        assertEquals("TEL", VCardUtils.toStringAvailableAsV30ParameValue("TEL")); +        assertEquals("PAGER", VCardUtils.toStringAvailableAsV30ParameValue("PAGER")); + +        assertEquals("\"\"", VCardUtils.toStringAvailableAsV30ParameValue("")); + +        // non-Ascii must be allowed +        assertEquals("\u4E8B\u52D9\u6240", +                VCardUtils.toStringAvailableAsV30ParameValue("\u4E8B\u52D9\u6240")); +        // Reported as bug report. +        assertEquals("\u8D39", VCardUtils.toStringAvailableAsV30ParameValue("\u8D39")); +        assertEquals("\"comma,separated\"", +                VCardUtils.toStringAvailableAsV30ParameValue("comma,separated")); +        assertEquals("\"colon:aware\"", +                VCardUtils.toStringAvailableAsV30ParameValue("colon:aware")); +        // CTL characters. +        assertEquals("CTLExample", +                VCardUtils.toStringAvailableAsV30ParameValue("CTL\u0001Example")); +        // DQUOTE must be removed. +        assertEquals("quoted", +                VCardUtils.toStringAvailableAsV30ParameValue("\"quoted\"")); +        // DQUOTE must be removed basically, but we should detect a space, which +        // require us to use DQUOTE again. +        // Right-side has one more illegal dquote to test quote-handle code thoroughly. +        assertEquals("\"Already quoted\"", +                VCardUtils.toStringAvailableAsV30ParameValue("\"Already quoted\"\"")); +    }  } |