Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ddd017f8 authored by Android (Google) Code Review's avatar Android (Google) Code Review
Browse files

Merge change I8daabf26 into eclair-mr2

* changes:
  Modify vCard exporter code so that it does not emit non-Ascii type.
parents bdcd30ac c4b51712
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -370,7 +370,7 @@ import java.util.Map;
     * @param ch input character
     * @return CharSequence object if the mapping for ch exists. Return null otherwise.
     */
    public static CharSequence tryGetHalfWidthText(char ch) {
    public static String tryGetHalfWidthText(char ch) {
        if (sHalfWidthMap.containsKey(ch)) {
            return sHalfWidthMap.get(ch);
        } else {
+91 −59
Original line number Diff line number Diff line
@@ -835,66 +835,90 @@ public class VCardBuilder {
     * @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 (mShouldUseQuotedPrintable &&
                        !VCardUtils.containsOnlyNonCrLfPrintableAscii(data)) {
                    reallyUseQuotedPrintable = true;
                    break;
                }
            }
        }

        if (dataArrayExists) {
            StringBuffer addressBuffer = new StringBuffer();
            boolean first = true;
            for (String data : dataArray) {
                if (first) {
                    first = false;
        // adr-value    = 0*6(text-value ";") text-value
        //              ; PO Box, Extended Address, Street, Locality, Region, Postal
        //              ; Code, Country Name
        final String rawPoBox = contentValues.getAsString(StructuredPostal.POBOX);
        final String rawExtendedAddress = contentValues.getAsString(StructuredPostal.NEIGHBORHOOD);
        final String rawStreet = contentValues.getAsString(StructuredPostal.STREET);
        final String rawLocality = contentValues.getAsString(StructuredPostal.CITY);
        final String rawRegion = contentValues.getAsString(StructuredPostal.REGION);
        final String rawPostalCode = contentValues.getAsString(StructuredPostal.POSTCODE);
        final String rawCountry = contentValues.getAsString(StructuredPostal.COUNTRY);
        final String[] rawAddressArray = new String[]{
                rawPoBox, rawExtendedAddress, rawStreet, rawLocality,
                rawRegion, rawPostalCode, rawCountry};
        if (!VCardUtils.areAllEmpty(rawAddressArray)) {
            final boolean reallyUseQuotedPrintable =
                (mShouldUseQuotedPrintable &&
                        !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawAddressArray));
            final boolean appendCharset =
                !VCardUtils.containsOnlyPrintableAscii(rawAddressArray);
            final String encodedPoBox;
            final String encodedExtendedAddress;
            final String encodedStreet;
            final String encodedLocality;
            final String encodedRegion;
            final String encodedPostalCode;
            final String encodedCountry;
            if (reallyUseQuotedPrintable) {
                encodedPoBox = encodeQuotedPrintable(rawPoBox);
                encodedExtendedAddress = encodeQuotedPrintable(rawExtendedAddress);
                encodedStreet = encodeQuotedPrintable(rawStreet);
                encodedLocality = encodeQuotedPrintable(rawLocality);
                encodedRegion = encodeQuotedPrintable(rawRegion);
                encodedPostalCode = encodeQuotedPrintable(rawPostalCode);
                encodedCountry = encodeQuotedPrintable(rawCountry);
            } else {
                encodedPoBox = escapeCharacters(rawPoBox);
                encodedExtendedAddress = escapeCharacters(rawExtendedAddress);
                encodedStreet = escapeCharacters(rawStreet);
                encodedLocality = escapeCharacters(rawLocality);
                encodedRegion = escapeCharacters(rawRegion);
                encodedPostalCode = escapeCharacters(rawPostalCode);
                encodedCountry = escapeCharacters(rawCountry);
            }
            final StringBuffer addressBuffer = new StringBuffer();
            addressBuffer.append(encodedPoBox);
            addressBuffer.append(VCARD_ITEM_SEPARATOR);
            addressBuffer.append(encodedExtendedAddress);
            addressBuffer.append(VCARD_ITEM_SEPARATOR);
            addressBuffer.append(encodedStreet);
            addressBuffer.append(VCARD_ITEM_SEPARATOR);
            addressBuffer.append(encodedLocality);
            addressBuffer.append(VCARD_ITEM_SEPARATOR);
            addressBuffer.append(encodedRegion);
            addressBuffer.append(VCARD_ITEM_SEPARATOR);
            addressBuffer.append(encodedPostalCode);
            addressBuffer.append(VCARD_ITEM_SEPARATOR);
            addressBuffer.append(encodedCountry);
            return new PostalStruct(
                    reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
        } else {  // VCardUtils.areAllEmpty(rawAddressArray) == true
            // Try to use FORMATTED_ADDRESS instead.
            final String rawFormattedAddress =
                contentValues.getAsString(StructuredPostal.FORMATTED_ADDRESS);
            if (TextUtils.isEmpty(rawFormattedAddress)) {
                return null;
            }
                if (!TextUtils.isEmpty(data)) {
            final boolean reallyUseQuotedPrintable =
                (mShouldUseQuotedPrintable &&
                        !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawFormattedAddress));
            final boolean appendCharset =
                !VCardUtils.containsOnlyPrintableAscii(rawFormattedAddress);
            final String encodedFormattedAddress;
            if (reallyUseQuotedPrintable) {
                        addressBuffer.append(encodeQuotedPrintable(data));
                encodedFormattedAddress = encodeQuotedPrintable(rawFormattedAddress);
            } else {
                        addressBuffer.append(escapeCharacters(data));
                    }
                }
            }
            return new PostalStruct(reallyUseQuotedPrintable, appendCharset,
                    addressBuffer.toString());
                encodedFormattedAddress = escapeCharacters(rawFormattedAddress);
            }

        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();
            // We use the second value ("Extended Address") just because Japanese mobile phones
            // do so. If the other importer expects the value be in the other field, some flag may
            // be needed.
            final StringBuffer addressBuffer = new StringBuffer();
            addressBuffer.append(VCARD_ITEM_SEPARATOR);
            addressBuffer.append(formattedAddress);
            addressBuffer.append(encodedFormattedAddress);
            addressBuffer.append(VCARD_ITEM_SEPARATOR);
            addressBuffer.append(VCARD_ITEM_SEPARATOR);
            addressBuffer.append(VCARD_ITEM_SEPARATOR);
@@ -903,7 +927,6 @@ public class VCardBuilder {
            return new PostalStruct(
                    reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
        }
        return null;  // There's no data available.
    }

    public VCardBuilder appendIms(final List<ContentValues> contentValuesList) {
@@ -1653,13 +1676,22 @@ public class VCardBuilder {
        // 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) {
        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);
            }
            appendTypeParameter(type);
            appendTypeParameter(typeValue);
        }
    }

+86 −85
Original line number Diff line number Diff line
@@ -16,14 +16,12 @@
package android.pim.vcard;

import android.content.ContentProviderOperation;
import android.content.ContentValues;
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.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;

import java.util.ArrayList;
import java.util.Arrays;
@@ -245,8 +243,7 @@ public class VCardUtils {
     * Inserts postal data into the builder object.
     * 
     * Note that the data structure of ContactsContract is different from that defined in vCard.
     * So some conversion may be performed in this method. See also
     * {{@link #getVCardPostalElements(ContentValues)}
     * So some conversion may be performed in this method.
     */
    public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
            final ContentProviderOperation.Builder builder,
@@ -260,9 +257,6 @@ public class VCardUtils {
        }

        builder.withValue(StructuredPostal.POBOX, postalData.pobox);
        // TODO: Japanese phone seems to use this field for expressing all the address including
        // region, city, etc. Not sure we're ok to store them into NEIGHBORHOOD, while it would be
        // better than dropping them all.
        builder.withValue(StructuredPostal.NEIGHBORHOOD, postalData.extendedAddress);
        builder.withValue(StructuredPostal.STREET, postalData.street);
        builder.withValue(StructuredPostal.CITY, postalData.localty);
@@ -277,59 +271,6 @@ public class VCardUtils {
        }
    }

    /**
     * Returns String[] containing address information based on vCard spec
     * (PO Box, Extended Address, Street, Locality, Region, Postal Code, Country Name).
     * All String objects are non-null ("" is used when the relevant data is empty).
     *
     * Note that the data structure of ContactsContract is different from that defined in vCard.
     * So some conversion may be performed in this method. See also
     * {{@link #insertStructuredPostalDataUsingContactsStruct(int,
     * android.content.ContentProviderOperation.Builder,
     * android.pim.vcard.VCardEntry.PostalData)}
     */
    public static String[] getVCardPostalElements(ContentValues contentValues) {
        // adr-value    = 0*6(text-value ";") text-value
        //              ; PO Box, Extended Address, Street, Locality, Region, Postal
        //              ; Code, Country Name
        String[] dataArray = new String[7];
        dataArray[0] = contentValues.getAsString(StructuredPostal.POBOX);
        if (dataArray[0] == null) {
            dataArray[0] = "";
        }
        // We keep all the data in StructuredPostal, presuming NEIGHBORHOOD is
        // similar to "Extended Address".
        dataArray[1] = contentValues.getAsString(StructuredPostal.NEIGHBORHOOD);
        if (dataArray[1] == null) {
            dataArray[1] = "";
        }
        dataArray[2] = contentValues.getAsString(StructuredPostal.STREET);
        if (dataArray[2] == null) {
            dataArray[2] = "";
        }
        // Assume that localty == city
        dataArray[3] = contentValues.getAsString(StructuredPostal.CITY);
        if (dataArray[3] == null) {
            dataArray[3] = "";
        }
        String region = contentValues.getAsString(StructuredPostal.REGION);
        if (!TextUtils.isEmpty(region)) {
            dataArray[4] = region;
        } else {
            dataArray[4] = "";
        }
        dataArray[5] = contentValues.getAsString(StructuredPostal.POSTCODE);
        if (dataArray[5] == null) {
            dataArray[5] = "";
        }
        dataArray[6] = contentValues.getAsString(StructuredPostal.COUNTRY);
        if (dataArray[6] == null) {
            dataArray[6] = "";
        }

        return dataArray;
    }
    
    public static String constructNameFromElements(final int vcardType,
            final String familyName, final String middleName, final String givenName) {
        return constructNameFromElements(vcardType, familyName, middleName, givenName,
@@ -394,20 +335,24 @@ public class VCardUtils {
        return list;
    }

    public static boolean containsOnlyPrintableAscii(String str) {
        if (TextUtils.isEmpty(str)) {
    public static boolean containsOnlyPrintableAscii(final String...values) {
        if (values == null) {
            return true;
        }

        final int length = str.length();
        final int asciiFirst = 0x20;
        final int asciiLast = 0x7E;  // included
        for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
            final int c = str.codePointAt(i);
        for (final String value : values) {
            if (TextUtils.isEmpty(value)) {
                continue;
            }
            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) || c == '\r' || c == '\n')) {
                    return false;
                }
            }
        }
        return true;
    }

@@ -416,20 +361,53 @@ public class VCardUtils {
     * or not, which is required by vCard 2.1.
     * See the definition of "7bit" in vCard 2.1 spec for more information.
     */
    public static boolean containsOnlyNonCrLfPrintableAscii(String str) {
        if (TextUtils.isEmpty(str)) {
    public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
        if (values == null) {
            return true;
        }

        final int length = str.length();
        final int asciiFirst = 0x20;
        final int asciiLast = 0x7E;  // included
        for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
            final int c = str.codePointAt(i);
        for (final String value : values) {
            if (TextUtils.isEmpty(value)) {
                continue;
            }
            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)) {
                    return false;
                }
            }
        }
        return true;
    }

    private static final Set<Character> sUnAcceptableAsciiInV21WordSet =
        new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));

    /**
     * <P>
     * Returns true when the given String is categorized as "word" specified in vCard spec 2.1.
     * </P>
     * <P>
     * vCard 2.1 specifies:<BR />
     * word = &lt;any printable 7bit us-ascii except []=:., &gt;
     * </P>
     */
    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;
    }

@@ -442,11 +420,10 @@ public class VCardUtils {
     *       such kind of input but must never output it unless the target is very specific
     *       to the device which is able to parse the malformed input. 
     */
    public static boolean containsOnlyAlphaDigitHyphen(String str) {
        if (TextUtils.isEmpty(str)) {
    public static boolean containsOnlyAlphaDigitHyphen(final String...values) {
        if (values == null) {
            return true;
        }

        final int upperAlphabetFirst = 0x41;  // A
        final int upperAlphabetAfterLast = 0x5b;  // [
        final int lowerAlphabetFirst = 0x61;  // a
@@ -454,6 +431,10 @@ public class VCardUtils {
        final int digitFirst = 0x30;  // 0
        final int digitAfterLast = 0x3A;  // :
        final int hyphen = '-';
        for (final String str : values) {
            if (TextUtils.isEmpty(str)) {
                continue;
            }
            final int length = str.length();
            for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
                int codepoint = str.codePointAt(i);
@@ -464,20 +445,21 @@ public class VCardUtils {
                    return false;
                }
            }
        }
        return true;
    }
    
    public static String toHalfWidthString(String orgString) {
    public static String toHalfWidthString(final String orgString) {
        if (TextUtils.isEmpty(orgString)) {
            return null;
        }
        final StringBuilder builder = new StringBuilder();
        final int length = orgString.length();
        for (int i = 0; i < length; i++) {
        for (int i = 0; i < length; i = orgString.offsetByCodePoints(i, 1)) {
            // All Japanese character is able to be expressed by char.
            // Do not need to use String#codepPointAt().
            final char ch = orgString.charAt(i);
            CharSequence halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
            final String halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
            if (halfWidthText != null) {
                builder.append(halfWidthText);
            } else {
@@ -495,6 +477,9 @@ public class VCardUtils {
     * @return The image type or null when the type cannot be determined.
     */
    public static String guessImageType(final byte[] input) {
        if (input == null) {
            return null;
        }
        if (input.length >= 3 && input[0] == 'G' && input[1] == 'I' && input[2] == 'F') {
            return "GIF";
        } else if (input.length >= 4 && input[0] == (byte) 0x89
@@ -511,6 +496,22 @@ public class VCardUtils {
        }
    }

    /**
     * @return True when all the given values are null or empty Strings.
     */
    public static boolean areAllEmpty(final String...values) {
        if (values == null) {
            return true;
        }

        for (final String value : values) {
            if (!TextUtils.isEmpty(value)) {
                return false;
            }
        }
        return true;
    }

    private VCardUtils() {
    }
}
+22 −5
Original line number Diff line number Diff line
@@ -139,6 +139,12 @@ class PropertyNodesVerifierElem {
        return addNodeWithOrder(propName, propValue, null, null, contentValues, null, null);
    }

    public PropertyNodesVerifierElem addNodeWithOrder(String propName,
            List<String> propValueList, ContentValues contentValues) {
        return addNodeWithOrder(propName, null, propValueList,
                null, contentValues, null, null);
    }

    public PropertyNodesVerifierElem addNodeWithOrder(String propName, String propValue,
            List<String> propValueList) {
        return addNodeWithOrder(propName, propValue, propValueList, null, null, null, null);
@@ -157,8 +163,7 @@ class PropertyNodesVerifierElem {

    public PropertyNodesVerifierElem addNodeWithOrder(String propName,
            List<String> propValueList, TypeSet paramMap_TYPE) {
        final String propValue = concatinateListWithSemiColon(propValueList);
        return addNodeWithOrder(propName, propValue, propValueList, null, null,
        return addNodeWithOrder(propName, null, propValueList, null, null,
                paramMap_TYPE, null);
    }

@@ -177,6 +182,9 @@ class PropertyNodesVerifierElem {
    public PropertyNodesVerifierElem addNodeWithOrder(String propName, String propValue,
            List<String> propValueList, byte[] propValue_bytes,
            ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
        if (propValue == null && propValueList != null) {
            propValue = concatinateListWithSemiColon(propValueList);
        }
        PropertyNode propertyNode = new PropertyNode(propName,
                propValue, propValueList, propValue_bytes,
                paramMap, paramMap_TYPE, propGroupSet);
@@ -200,14 +208,20 @@ class PropertyNodesVerifierElem {
        return addNodeWithoutOrder(propName, propValue, null, null, contentValues, null, null);
    }

    public PropertyNodesVerifierElem addNodeWithoutOrder(String propName,
            List<String> propValueList, ContentValues contentValues) {
        return addNodeWithoutOrder(propName, null,
                propValueList, null, contentValues, null, null);
    }

    public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, String propValue,
            List<String> propValueList) {
        return addNodeWithoutOrder(propName, propValue, propValueList, null, null, null, null);
    }

    public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, List<String> propValueList) {
        final String propValue = concatinateListWithSemiColon(propValueList);
        return addNodeWithoutOrder(propName, propValue, propValueList,
    public PropertyNodesVerifierElem addNodeWithoutOrder(String propName,
            List<String> propValueList) {
        return addNodeWithoutOrder(propName, null, propValueList,
                null, null, null, null);
    }

@@ -238,6 +252,9 @@ class PropertyNodesVerifierElem {
    public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, String propValue,
            List<String> propValueList, byte[] propValue_bytes,
            ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
        if (propValue == null && propValueList != null) {
            propValue = concatinateListWithSemiColon(propValueList);
        }
        mUnorderedNodeList.add(new PropertyNode(propName, propValue,
                propValueList, propValue_bytes, paramMap, paramMap_TYPE, propGroupSet));
        return this;
+14 −12

File changed.

Preview size limit exceeded, changes collapsed.

Loading