From a750fdd765ec253ffa8bf3d4848d5c3a35e1979b Mon Sep 17 00:00:00 2001 From: Daisuke Miyakawa Date: Fri, 20 Nov 2009 16:09:34 +0900 Subject: [PATCH] Fix a problem in which Android custom fields are not emitted correctly in non-Ascii languages. Internal issue number: 2195990 --- core/java/android/pim/vcard/VCardBuilder.java | 79 ++++++++++++------- .../android/pim/vcard/VCardParser_V21.java | 3 - core/java/android/pim/vcard/VCardUtils.java | 75 +++++++++++------- .../vcard/VCardJapanizationTests.java | 13 +++ .../unit_tests/vcard/VCardUtilsTests.java | 5 ++ 5 files changed, 116 insertions(+), 59 deletions(-) diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java index 3980940b87ed7..09ac1fd6262f5 100644 --- a/core/java/android/pim/vcard/VCardBuilder.java +++ b/core/java/android/pim/vcard/VCardBuilder.java @@ -1536,13 +1536,10 @@ public class VCardBuilder { } public void appendAndroidSpecificProperty(final String mimeType, ContentValues contentValues) { - List rawValueList = new ArrayList(); - rawValueList.add(mimeType); - final List columnNameList; if (!sAllowedAndroidPropertySet.contains(mimeType)) { return; } - + final List rawValueList = new ArrayList(); for (int i = 1; i <= VCardConstants.MAX_DATA_COLUMN; i++) { String value = contentValues.getAsString("data" + i); if (value == null) { @@ -1551,8 +1548,38 @@ public class VCardBuilder { rawValueList.add(value); } - appendLineWithCharsetAndQPDetection( - VCardConstants.PROPERTY_X_ANDROID_CUSTOM, rawValueList); + boolean needCharset = + (mShouldAppendCharsetParam && + !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList)); + boolean reallyUseQuotedPrintable = + (mShouldUseQuotedPrintable && + !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList)); + mBuilder.append(VCardConstants.PROPERTY_X_ANDROID_CUSTOM); + if (needCharset) { + 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(mimeType); // Should not be encoded. + for (String rawValue : rawValueList) { + final String encodedValue; + if (reallyUseQuotedPrintable) { + 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); + } + mBuilder.append(VCARD_ITEM_SEPARATOR); + mBuilder.append(encodedValue); + } + mBuilder.append(VCARD_END_OF_LINE); } public void appendLineWithCharsetAndQPDetection(final String propertyName, @@ -1560,7 +1587,7 @@ public class VCardBuilder { appendLineWithCharsetAndQPDetection(propertyName, null, rawValue); } - private void appendLineWithCharsetAndQPDetection( + public void appendLineWithCharsetAndQPDetection( final String propertyName, final List rawValueList) { appendLineWithCharsetAndQPDetection(propertyName, null, rawValueList); } @@ -1578,22 +1605,12 @@ public class VCardBuilder { public void appendLineWithCharsetAndQPDetection(final String propertyName, final List parameterList, final List rawValueList) { - boolean needCharset = false; - boolean reallyUseQuotedPrintable = false; - for (String rawValue : rawValueList) { - if (!needCharset && mShouldUseQuotedPrintable && - !VCardUtils.containsOnlyPrintableAscii(rawValue)) { - needCharset = true; - } - if (!reallyUseQuotedPrintable && - !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValue)) { - reallyUseQuotedPrintable = true; - } - if (needCharset && reallyUseQuotedPrintable) { - break; - } - } - + boolean needCharset = + (mShouldAppendCharsetParam && + !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList)); + boolean reallyUseQuotedPrintable = + (mShouldUseQuotedPrintable && + !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList)); appendLine(propertyName, parameterList, rawValueList, needCharset, reallyUseQuotedPrintable); } @@ -1610,8 +1627,9 @@ public class VCardBuilder { } public void appendLine(final String propertyName, - final String rawValue, final boolean needCharset, boolean needQuotedPrintable) { - appendLine(propertyName, null, rawValue, needCharset, needQuotedPrintable); + final String rawValue, final boolean needCharset, + boolean reallyUseQuotedPrintable) { + appendLine(propertyName, null, rawValue, needCharset, reallyUseQuotedPrintable); } public void appendLine(final String propertyName, final List parameterList, @@ -1620,7 +1638,8 @@ public class VCardBuilder { } public void appendLine(final String propertyName, final List parameterList, - final String rawValue, final boolean needCharset, boolean needQuotedPrintable) { + final String rawValue, final boolean needCharset, + boolean reallyUseQuotedPrintable) { mBuilder.append(propertyName); if (parameterList != null && parameterList.size() > 0) { mBuilder.append(VCARD_PARAM_SEPARATOR); @@ -1632,7 +1651,7 @@ public class VCardBuilder { } final String encodedValue; - if (needQuotedPrintable) { + if (reallyUseQuotedPrintable) { mBuilder.append(VCARD_PARAM_SEPARATOR); mBuilder.append(VCARD_PARAM_ENCODING_QP); encodedValue = encodeQuotedPrintable(rawValue); @@ -1664,14 +1683,16 @@ public class VCardBuilder { mBuilder.append(VCARD_PARAM_SEPARATOR); mBuilder.append(mVCardCharsetParameter); } + if (needQuotedPrintable) { + mBuilder.append(VCARD_PARAM_SEPARATOR); + mBuilder.append(VCARD_PARAM_ENCODING_QP); + } 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 diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java index ec569d4f6089b..e7c19cf451bac 100644 --- a/core/java/android/pim/vcard/VCardParser_V21.java +++ b/core/java/android/pim/vcard/VCardParser_V21.java @@ -117,9 +117,6 @@ public class VCardParser_V21 extends VCardParser { this(detector.getEstimatedType()); } - /** - * TODO: Merge detector and parser mode. - */ public VCardParser_V21(int parseType) { super(parseType); if (parseType == VCardConfig.PARSE_TYPE_FOMA) { diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java index 80457bbc95a1c..ec75a9882032b 100644 --- a/core/java/android/pim/vcard/VCardUtils.java +++ b/core/java/android/pim/vcard/VCardUtils.java @@ -349,6 +349,13 @@ public class VCardUtils { } public static boolean containsOnlyPrintableAscii(final String...values) { + if (values == null) { + return true; + } + return containsOnlyPrintableAscii(Arrays.asList(values)); + } + + public static boolean containsOnlyPrintableAscii(final Collection values) { if (values == null) { return true; } @@ -375,6 +382,13 @@ public class VCardUtils { * See the definition of "7bit" in vCard 2.1 spec for more information. */ public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) { + if (values == null) { + return true; + } + return containsOnlyNonCrLfPrintableAscii(Arrays.asList(values)); + } + + public static boolean containsOnlyNonCrLfPrintableAscii(final Collection values) { if (values == null) { return true; } @@ -398,32 +412,6 @@ public class VCardUtils { private static final Set sUnAcceptableAsciiInV21WordSet = new HashSet(Arrays.asList('[', ']', '=', ':', '.', ',', ' ')); - /** - *

- * Returns true when the given String is categorized as "word" specified in vCard spec 2.1. - *

- *

- * vCard 2.1 specifies:
- * word = <any printable 7bit us-ascii except []=:., > - *

- */ - public static boolean isV21Word(final String value) { - if (TextUtils.isEmpty(value)) { - return true; - } - final int asciiFirst = 0x20; - final int asciiLast = 0x7E; // included - final int length = value.length(); - for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) { - final int c = value.codePointAt(i); - if (!(asciiFirst <= c && c <= asciiLast) || - sUnAcceptableAsciiInV21WordSet.contains((char)c)) { - return false; - } - } - return true; - } - /** * This is useful since vCard 3.0 often requires the ("X-") properties and groups * should contain only alphabets, digits, and hyphen. @@ -434,6 +422,13 @@ public class VCardUtils { * to the device which is able to parse the malformed input. */ public static boolean containsOnlyAlphaDigitHyphen(final String...values) { + if (values == null) { + return true; + } + return containsOnlyAlphaDigitHyphen(Arrays.asList(values)); + } + + public static boolean containsOnlyAlphaDigitHyphen(final Collection values) { if (values == null) { return true; } @@ -461,7 +456,33 @@ public class VCardUtils { } return true; } - + + /** + *

+ * Returns true when the given String is categorized as "word" specified in vCard spec 2.1. + *

+ *

+ * vCard 2.1 specifies:
+ * word = <any printable 7bit us-ascii except []=:., > + *

+ */ + public static boolean isV21Word(final String value) { + if (TextUtils.isEmpty(value)) { + return true; + } + final int asciiFirst = 0x20; + final int asciiLast = 0x7E; // included + final int length = value.length(); + for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) { + final int c = value.codePointAt(i); + if (!(asciiFirst <= c && c <= asciiLast) || + sUnAcceptableAsciiInV21WordSet.contains((char)c)) { + return false; + } + } + return true; + } + public static String toHalfWidthString(final String orgString) { if (TextUtils.isEmpty(orgString)) { return null; diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardJapanizationTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardJapanizationTests.java index 967c22dc186e0..eea98c601ee69 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardJapanizationTests.java +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardJapanizationTests.java @@ -18,6 +18,7 @@ package com.android.unit_tests.vcard; import android.content.ContentValues; import android.pim.vcard.VCardConfig; +import android.provider.ContactsContract.CommonDataKinds.Nickname; import android.provider.ContactsContract.CommonDataKinds.Note; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.StructuredName; @@ -418,4 +419,16 @@ public class VCardJapanizationTests extends VCardTestsBase { .addExpectedNode("ADR", "", new TypeSet("HOME")) .addExpectedNode("NOTE", "note1\nnote2\nnote3", mContentValuesForQP); } + + public void testAndroidCustomV21() { + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE) + .put(Nickname.NAME, "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC"); + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("X-ANDROID-CUSTOM", + Arrays.asList(Nickname.CONTENT_ITEM_TYPE, + "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC", + "", "", "", "", "", "", "", "", "", "", "", "", "", ""), + mContentValuesForQPAndUtf8); + } } diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardUtilsTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardUtilsTests.java index 592c28596b55c..9f173af716e3a 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardUtilsTests.java +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardUtilsTests.java @@ -20,10 +20,13 @@ import android.pim.vcard.VCardUtils; import junit.framework.TestCase; +import java.util.List; + public class VCardUtilsTests extends TestCase { public void testContainsOnlyPrintableAscii() { assertTrue(VCardUtils.containsOnlyPrintableAscii((String)null)); assertTrue(VCardUtils.containsOnlyPrintableAscii((String[])null)); + assertTrue(VCardUtils.containsOnlyPrintableAscii((List)null)); assertTrue(VCardUtils.containsOnlyPrintableAscii("")); assertTrue(VCardUtils.containsOnlyPrintableAscii("abcdefghijklmnopqrstuvwxyz")); assertTrue(VCardUtils.containsOnlyPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ")); @@ -40,6 +43,7 @@ public class VCardUtilsTests extends TestCase { public void testContainsOnlyNonCrLfPrintableAscii() { assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((String)null)); assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((String[])null)); + assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((List)null)); assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("")); assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("abcdefghijklmnopqrstuvwxyz")); assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ")); @@ -57,6 +61,7 @@ public class VCardUtilsTests extends TestCase { public void testContainsOnlyAlphaDigitHyphen() { assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((String)null)); assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((String[])null)); + assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((List)null)); assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen("")); assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("abcdefghijklmnopqrstuvwxyz")); assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));