1f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa/*
2f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa * Copyright (C) 2009 The Android Open Source Project
3f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa *
4f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa * Licensed under the Apache License, Version 2.0 (the "License");
5f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa * you may not use this file except in compliance with the License.
6f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa * You may obtain a copy of the License at
7f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa *
8f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa *      http://www.apache.org/licenses/LICENSE-2.0
9f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa *
10f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa * Unless required by applicable law or agreed to in writing, software
11f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa * distributed under the License is distributed on an "AS IS" BASIS,
12f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa * See the License for the specific language governing permissions and
14f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa * limitations under the License.
15f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa */
16f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawapackage android.pim.vcard;
17f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa
18f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawaimport android.content.ContentProviderOperation;
1969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawaimport android.pim.vcard.exception.VCardException;
2099a0a2cd73503513891565a4aaf99e209bd262d2Daisuke Miyakawaimport android.provider.ContactsContract.CommonDataKinds.Im;
21f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawaimport android.provider.ContactsContract.CommonDataKinds.Phone;
22f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawaimport android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
23b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawaimport android.provider.ContactsContract.Data;
24f2ad61c83da509270b8c5e9583b503ced928315dDaisuke Miyakawaimport android.telephony.PhoneNumberUtils;
25f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawaimport android.text.TextUtils;
2669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawaimport android.util.Log;
27f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa
2869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawaimport org.apache.commons.codec.DecoderException;
2969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawaimport org.apache.commons.codec.net.QuotedPrintableCodec;
3069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
3169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawaimport java.io.UnsupportedEncodingException;
3269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawaimport java.nio.ByteBuffer;
3369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawaimport java.nio.charset.Charset;
345c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawaimport java.util.ArrayList;
355a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawaimport java.util.Arrays;
36f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawaimport java.util.Collection;
37f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawaimport java.util.HashMap;
38f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawaimport java.util.HashSet;
395c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawaimport java.util.List;
40f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawaimport java.util.Map;
41f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawaimport java.util.Set;
42f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa
43f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa/**
44f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa * Utilities for VCard handling codes.
45f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa */
46f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawapublic class VCardUtils {
4769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    private static final String LOG_TAG = "VCardUtils";
4869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
49f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    // Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
506cb76ac37f1b4a36d81db7d7e8652d82b6f1b8b5Daisuke Miyakawa    // converted to two parameter Strings. These only contain some minor fields valid in both
51f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    // vCard and current (as of 2009-08-07) Contacts structure.
52f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    private static final Map<Integer, String> sKnownPhoneTypesMap_ItoS;
535a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa    private static final Set<String> sPhoneTypesUnknownToContactsSet;
5499a0a2cd73503513891565a4aaf99e209bd262d2Daisuke Miyakawa    private static final Map<String, Integer> sKnownPhoneTypeMap_StoI;
5599a0a2cd73503513891565a4aaf99e209bd262d2Daisuke Miyakawa    private static final Map<Integer, String> sKnownImPropNameMap_ItoS;
565a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa    private static final Set<String> sMobilePhoneLabelSet;
57021de736237f6d408d61c1c4f884f98125cc081aDaisuke Miyakawa
58f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    static {
59f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>();
6099a0a2cd73503513891565a4aaf99e209bd262d2Daisuke Miyakawa        sKnownPhoneTypeMap_StoI = new HashMap<String, Integer>();
61f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa
621b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, VCardConstants.PARAM_TYPE_CAR);
631b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CAR, Phone.TYPE_CAR);
641b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, VCardConstants.PARAM_TYPE_PAGER);
651b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_PAGER, Phone.TYPE_PAGER);
661b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, VCardConstants.PARAM_TYPE_ISDN);
671b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_ISDN, Phone.TYPE_ISDN);
68f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa
691b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_HOME, Phone.TYPE_HOME);
701b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_WORK, Phone.TYPE_WORK);
711b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CELL, Phone.TYPE_MOBILE);
72f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa
731b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
741b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_CALLBACK,
751b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa                Phone.TYPE_CALLBACK);
7699a0a2cd73503513891565a4aaf99e209bd262d2Daisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(
771b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa                VCardConstants.PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
781b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
791b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_TTY_TDD,
801b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa                Phone.TYPE_TTY_TDD);
811b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_ASSISTANT,
82021de736237f6d408d61c1c4f884f98125cc081aDaisuke Miyakawa                Phone.TYPE_ASSISTANT);
83f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa
845a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa        sPhoneTypesUnknownToContactsSet = new HashSet<String>();
851b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MODEM);
86839c036444c8a5335cc557e174acc7ee28baafc4Daisuke Miyakawa        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MSG);
871b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_BBS);
881b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_VIDEO);
8999a0a2cd73503513891565a4aaf99e209bd262d2Daisuke Miyakawa
9099a0a2cd73503513891565a4aaf99e209bd262d2Daisuke Miyakawa        sKnownImPropNameMap_ItoS = new HashMap<Integer, String>();
911b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
921b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
931b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
941b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
951b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK,
961b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa                VCardConstants.PROPERTY_X_GOOGLE_TALK);
971b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
981b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
991b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, VCardConstants.PROPERTY_X_QQ);
1001b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, VCardConstants.PROPERTY_X_NETMEETING);
1015a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa
1025a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa        // \u643A\u5E2F\u96FB\u8A71 = Full-width Hiragana "Keitai-Denwa" (mobile phone)
1035a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa        // \u643A\u5E2F = Full-width Hiragana "Keitai" (mobile phone)
1045a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa        // \u30B1\u30A4\u30BF\u30A4 = Full-width Katakana "Keitai" (mobile phone)
1055a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa        // \uFF79\uFF72\uFF80\uFF72 = Half-width Katakana "Keitai" (mobile phone)
1065a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa        sMobilePhoneLabelSet = new HashSet<String>(Arrays.asList(
1075a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa                "MOBILE", "\u643A\u5E2F\u96FB\u8A71", "\u643A\u5E2F", "\u30B1\u30A4\u30BF\u30A4",
1085a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa                "\uFF79\uFF72\uFF80\uFF72"));
109f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    }
110021de736237f6d408d61c1c4f884f98125cc081aDaisuke Miyakawa
1116cb76ac37f1b4a36d81db7d7e8652d82b6f1b8b5Daisuke Miyakawa    public static String getPhoneTypeString(Integer type) {
112f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        return sKnownPhoneTypesMap_ItoS.get(type);
113f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    }
114021de736237f6d408d61c1c4f884f98125cc081aDaisuke Miyakawa
115f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    /**
116f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa     * Returns Interger when the given types can be parsed as known type. Returns String object
117f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa     * when not, which should be set to label.
118f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa     */
11926ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa    public static Object getPhoneTypeFromStrings(Collection<String> types,
12026ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa            String number) {
12126ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa        if (number == null) {
12226ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa            number = "";
12326ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa        }
124f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        int type = -1;
125f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        String label = null;
126f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        boolean isFax = false;
127f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        boolean hasPref = false;
128f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa
129f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        if (types != null) {
130f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            for (String typeString : types) {
1315a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa                if (typeString == null) {
1325a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa                    continue;
1335a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa                }
1345a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa                typeString = typeString.toUpperCase();
1351b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa                if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
136f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                    hasPref = true;
1371b9e2bec6381ac7e7d0cfe0db549c5396d2ba7ceDaisuke Miyakawa                } else if (typeString.equals(VCardConstants.PARAM_TYPE_FAX)) {
138f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                    isFax = true;
139f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                } else {
140f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                    if (typeString.startsWith("X-") && type < 0) {
141f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                        typeString = typeString.substring(2);
142f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                    }
1435a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa                    if (typeString.length() == 0) {
1445a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa                        continue;
1455a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa                    }
1465a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa                    final Integer tmp = sKnownPhoneTypeMap_StoI.get(typeString);
147f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                    if (tmp != null) {
14826ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa                        final int typeCandidate = tmp;
14926ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa                        // TYPE_PAGER is prefered when the number contains @ surronded by
15026ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa                        // a pager number and a domain name.
15126ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa                        // e.g.
15226ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa                        // o 1111@domain.com
15326ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa                        // x @domain.com
15426ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa                        // x 1111@
15526ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa                        final int indexOfAt = number.indexOf("@");
15626ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa                        if ((typeCandidate == Phone.TYPE_PAGER
15726ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa                                && 0 < indexOfAt && indexOfAt < number.length() - 1)
15826ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa                                || type < 0
15926ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa                                || type == Phone.TYPE_CUSTOM) {
16026ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa                            type = tmp;
16126ee0ae9de37e50b790a0a7b7bddb1d27d5b1214Daisuke Miyakawa                        }
162f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                    } else if (type < 0) {
163f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                        type = Phone.TYPE_CUSTOM;
164f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                        label = typeString;
165f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                    }
166f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                }
167f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            }
168f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
169f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        if (type < 0) {
170f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            if (hasPref) {
171f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                type = Phone.TYPE_MAIN;
172f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            } else {
173f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                // default to TYPE_HOME
174f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                type = Phone.TYPE_HOME;
175f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            }
176f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
177f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        if (isFax) {
178f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            if (type == Phone.TYPE_HOME) {
179f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                type = Phone.TYPE_FAX_HOME;
180f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            } else if (type == Phone.TYPE_WORK) {
181f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                type = Phone.TYPE_FAX_WORK;
182f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            } else if (type == Phone.TYPE_OTHER) {
183f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                type = Phone.TYPE_OTHER_FAX;
184f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            }
185f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
186f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        if (type == Phone.TYPE_CUSTOM) {
187f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            return label;
188f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        } else {
189f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            return type;
190f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
191f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    }
19299a0a2cd73503513891565a4aaf99e209bd262d2Daisuke Miyakawa
1935a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa    @SuppressWarnings("deprecation")
1945a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa    public static boolean isMobilePhoneLabel(final String label) {
1955a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa        // For backward compatibility.
1965a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa        // Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now.
1975a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa        //         To support mobile type at that time, this custom label had been used.
19869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        return ("_AUTO_CELL".equals(label) || sMobilePhoneLabelSet.contains(label));
19999a0a2cd73503513891565a4aaf99e209bd262d2Daisuke Miyakawa    }
20099a0a2cd73503513891565a4aaf99e209bd262d2Daisuke Miyakawa
2015a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa    public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) {
2025a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa        return sPhoneTypesUnknownToContactsSet.contains(label);
203f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    }
2045a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa
2055a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa    public static String getPropertyNameForIm(final int protocol) {
2065a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa        return sKnownImPropNameMap_ItoS.get(protocol);
2075a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa    }
2085a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa
209592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa    public static String[] sortNameElements(final int vcardType,
210592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa            final String familyName, final String middleName, final String givenName) {
211592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa        final String[] list = new String[3];
212592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa        final int nameOrderType = VCardConfig.getNameOrderType(vcardType);
213592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa        switch (nameOrderType) {
214592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa            case VCardConfig.NAME_ORDER_JAPANESE: {
215592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                if (containsOnlyPrintableAscii(familyName) &&
216592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                        containsOnlyPrintableAscii(givenName)) {
217592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                    list[0] = givenName;
218592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                    list[1] = middleName;
219592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                    list[2] = familyName;
220592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                } else {
221592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                    list[0] = familyName;
222592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                    list[1] = middleName;
223592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                    list[2] = givenName;
224592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                }
225592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                break;
226592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa            }
227592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa            case VCardConfig.NAME_ORDER_EUROPE: {
228592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                list[0] = middleName;
229592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                list[1] = givenName;
230592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                list[2] = familyName;
231592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                break;
232592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa            }
233592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa            default: {
234592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                list[0] = givenName;
235592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                list[1] = middleName;
236592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                list[2] = familyName;
237592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa                break;
238592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa            }
239f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
240f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        return list;
241f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    }
242f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa
243f2ad61c83da509270b8c5e9583b503ced928315dDaisuke Miyakawa    public static int getPhoneNumberFormat(final int vcardType) {
244f2ad61c83da509270b8c5e9583b503ced928315dDaisuke Miyakawa        if (VCardConfig.isJapaneseDevice(vcardType)) {
245f2ad61c83da509270b8c5e9583b503ced928315dDaisuke Miyakawa            return PhoneNumberUtils.FORMAT_JAPAN;
246f2ad61c83da509270b8c5e9583b503ced928315dDaisuke Miyakawa        } else {
247f2ad61c83da509270b8c5e9583b503ced928315dDaisuke Miyakawa            return PhoneNumberUtils.FORMAT_NANP;
248f2ad61c83da509270b8c5e9583b503ced928315dDaisuke Miyakawa        }
249f2ad61c83da509270b8c5e9583b503ced928315dDaisuke Miyakawa    }
250f2ad61c83da509270b8c5e9583b503ced928315dDaisuke Miyakawa
251f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    /**
25269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * <p>
253f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa     * Inserts postal data into the builder object.
25469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * </p>
25569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * <p>
256f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa     * Note that the data structure of ContactsContract is different from that defined in vCard.
257c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa     * So some conversion may be performed in this method.
25869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * </p>
259f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa     */
260f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
261f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            final ContentProviderOperation.Builder builder,
2625a1f2d2de026b582fbe8b1a46a83ad33760a2c48Daisuke Miyakawa            final VCardEntry.PostalData postalData) {
263f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        builder.withValueBackReference(StructuredPostal.RAW_CONTACT_ID, 0);
264f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        builder.withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
265f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa
266f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        builder.withValue(StructuredPostal.TYPE, postalData.type);
267f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        if (postalData.type == StructuredPostal.TYPE_CUSTOM) {
268f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            builder.withValue(StructuredPostal.LABEL, postalData.label);
269f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
270f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa
271a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa        final String streetString;
272a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa        if (TextUtils.isEmpty(postalData.street)) {
273a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa            if (TextUtils.isEmpty(postalData.extendedAddress)) {
274a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa                streetString = null;
275a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa            } else {
276a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa                streetString = postalData.extendedAddress;
277a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa            }
278a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa        } else {
279a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa            if (TextUtils.isEmpty(postalData.extendedAddress)) {
280a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa                streetString = postalData.street;
281a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa            } else {
282a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa                streetString = postalData.street + " " + postalData.extendedAddress;
283a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa            }
284a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa        }
285f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        builder.withValue(StructuredPostal.POBOX, postalData.pobox);
286a6d81e3ee4cf12ade1ef6701b5f1fcdd390fc98aDaisuke Miyakawa        builder.withValue(StructuredPostal.STREET, streetString);
287f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        builder.withValue(StructuredPostal.CITY, postalData.localty);
288f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        builder.withValue(StructuredPostal.REGION, postalData.region);
289f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        builder.withValue(StructuredPostal.POSTCODE, postalData.postalCode);
290f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        builder.withValue(StructuredPostal.COUNTRY, postalData.country);
291f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa
292f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        builder.withValue(StructuredPostal.FORMATTED_ADDRESS,
293f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                postalData.getFormattedAddress(vcardType));
294f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        if (postalData.isPrimary) {
295f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            builder.withValue(Data.IS_PRIMARY, 1);
296f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
297f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    }
29899a0a2cd73503513891565a4aaf99e209bd262d2Daisuke Miyakawa
299592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa    public static String constructNameFromElements(final int vcardType,
300592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa            final String familyName, final String middleName, final String givenName) {
301592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa        return constructNameFromElements(vcardType, familyName, middleName, givenName,
302f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                null, null);
303f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    }
304f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa
305592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa    public static String constructNameFromElements(final int vcardType,
306592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa            final String familyName, final String middleName, final String givenName,
307592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa            final String prefix, final String suffix) {
308592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa        final StringBuilder builder = new StringBuilder();
309592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa        final String[] nameList = sortNameElements(vcardType, familyName, middleName, givenName);
310f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        boolean first = true;
311f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        if (!TextUtils.isEmpty(prefix)) {
312f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            first = false;
313f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            builder.append(prefix);
314f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
315592988d307e8d305ca163c4e58da0fb350054194Daisuke Miyakawa        for (final String namePart : nameList) {
316f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            if (!TextUtils.isEmpty(namePart)) {
317f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                if (first) {
318f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                    first = false;
319f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                } else {
320f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                    builder.append(' ');
321f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                }
322f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                builder.append(namePart);
323f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            }
324f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
325f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        if (!TextUtils.isEmpty(suffix)) {
326f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            if (!first) {
327f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                builder.append(' ');
328f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            }
329f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            builder.append(suffix);
330f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
331f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        return builder.toString();
332f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    }
3335c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa
33469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    /**
33569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * Splits the given value into pieces using the delimiter ';' inside it.
33669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     *
33769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * Escaped characters in those values are automatically unescaped into original form.
33869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     */
3395c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa    public static List<String> constructListFromValue(final String value,
34069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            final int vcardType) {
3415c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa        final List<String> list = new ArrayList<String>();
3425c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa        StringBuilder builder = new StringBuilder();
34369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        final int length = value.length();
3445c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa        for (int i = 0; i < length; i++) {
3455c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa            char ch = value.charAt(i);
3465c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa            if (ch == '\\' && i < length - 1) {
3475c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa                char nextCh = value.charAt(i + 1);
34869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                final String unescapedString;
34969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                if (VCardConfig.isVersion40(vcardType)) {
35069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    unescapedString = VCardParserImpl_V40.unescapeCharacter(nextCh);
35169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                } else if (VCardConfig.isVersion30(vcardType)) {
35269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    unescapedString = VCardParserImpl_V30.unescapeCharacter(nextCh);
35369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                } else {
35469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    if (!VCardConfig.isVersion21(vcardType)) {
35569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                        // Unknown vCard type
35669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                        Log.w(LOG_TAG, "Unknown vCard type");
35769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    }
35869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    unescapedString = VCardParserImpl_V21.unescapeCharacter(nextCh);
35969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                }
36069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
3615c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa                if (unescapedString != null) {
3625c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa                    builder.append(unescapedString);
3635c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa                    i++;
3645c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa                } else {
3655c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa                    builder.append(ch);
3665c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa                }
3675c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa            } else if (ch == ';') {
3685c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa                list.add(builder.toString());
3695c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa                builder = new StringBuilder();
3705c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa            } else {
3715c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa                builder.append(ch);
3725c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa            }
3735c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa        }
3745c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa        list.add(builder.toString());
3755c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa        return list;
3765c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa    }
3775c3e687965a49e4e54196a049337544a6eed61d9Daisuke Miyakawa
378c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa    public static boolean containsOnlyPrintableAscii(final String...values) {
379c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        if (values == null) {
380f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            return true;
381f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
382a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        return containsOnlyPrintableAscii(Arrays.asList(values));
383a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa    }
384a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa
385a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa    public static boolean containsOnlyPrintableAscii(final Collection<String> values) {
386a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        if (values == null) {
387a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa            return true;
388a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        }
389c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        for (final String value : values) {
390c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            if (TextUtils.isEmpty(value)) {
391c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa                continue;
392c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            }
393973afa96bf184be6b8b5a25963568650e70750f7Daisuke Miyakawa            if (!TextUtils.isPrintableAsciiOnly(value)) {
394973afa96bf184be6b8b5a25963568650e70750f7Daisuke Miyakawa                return false;
395f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            }
396f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
397f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        return true;
398f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    }
399a2ba9a199962eb1b14ac3e0aa0fbc4c557298c96Daisuke Miyakawa
400a2ba9a199962eb1b14ac3e0aa0fbc4c557298c96Daisuke Miyakawa    /**
40169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * <p>
402a2ba9a199962eb1b14ac3e0aa0fbc4c557298c96Daisuke Miyakawa     * This is useful when checking the string should be encoded into quoted-printable
403a2ba9a199962eb1b14ac3e0aa0fbc4c557298c96Daisuke Miyakawa     * or not, which is required by vCard 2.1.
40469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * </p>
40569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * <p>
406a2ba9a199962eb1b14ac3e0aa0fbc4c557298c96Daisuke Miyakawa     * See the definition of "7bit" in vCard 2.1 spec for more information.
40769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * </p>
408a2ba9a199962eb1b14ac3e0aa0fbc4c557298c96Daisuke Miyakawa     */
409c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa    public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
410c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        if (values == null) {
411a2ba9a199962eb1b14ac3e0aa0fbc4c557298c96Daisuke Miyakawa            return true;
412a2ba9a199962eb1b14ac3e0aa0fbc4c557298c96Daisuke Miyakawa        }
413a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        return containsOnlyNonCrLfPrintableAscii(Arrays.asList(values));
414a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa    }
415a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa
416a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa    public static boolean containsOnlyNonCrLfPrintableAscii(final Collection<String> values) {
417a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        if (values == null) {
418a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa            return true;
419a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        }
420c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        final int asciiFirst = 0x20;
421c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        final int asciiLast = 0x7E;  // included
422c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        for (final String value : values) {
423c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            if (TextUtils.isEmpty(value)) {
424c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa                continue;
425c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            }
426c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            final int length = value.length();
427c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
428c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa                final int c = value.codePointAt(i);
429c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa                if (!(asciiFirst <= c && c <= asciiLast)) {
430c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa                    return false;
431c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa                }
432c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            }
433c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        }
434c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        return true;
435c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa    }
436c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa
437c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa    private static final Set<Character> sUnAcceptableAsciiInV21WordSet =
438c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));
439a2ba9a199962eb1b14ac3e0aa0fbc4c557298c96Daisuke Miyakawa
440c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa    /**
44169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * <p>
442f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa     * This is useful since vCard 3.0 often requires the ("X-") properties and groups
443f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa     * should contain only alphabets, digits, and hyphen.
44469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * </p>
44569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * <p>
446f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa     * Note: It is already known some devices (wrongly) outputs properties with characters
447a2ba9a199962eb1b14ac3e0aa0fbc4c557298c96Daisuke Miyakawa     *       which should not be in the field. One example is "X-GOOGLE TALK". We accept
448f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa     *       such kind of input but must never output it unless the target is very specific
44969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     *       to the device which is able to parse the malformed input.
45069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * </p>
451f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa     */
452c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa    public static boolean containsOnlyAlphaDigitHyphen(final String...values) {
453c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        if (values == null) {
454f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            return true;
455f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
456a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        return containsOnlyAlphaDigitHyphen(Arrays.asList(values));
457a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa    }
458a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa
459a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa    public static boolean containsOnlyAlphaDigitHyphen(final Collection<String> values) {
460a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        if (values == null) {
461a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa            return true;
462a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        }
463839c036444c8a5335cc557e174acc7ee28baafc4Daisuke Miyakawa        final int upperAlphabetFirst = 0x41;  // A
464839c036444c8a5335cc557e174acc7ee28baafc4Daisuke Miyakawa        final int upperAlphabetAfterLast = 0x5b;  // [
465839c036444c8a5335cc557e174acc7ee28baafc4Daisuke Miyakawa        final int lowerAlphabetFirst = 0x61;  // a
466839c036444c8a5335cc557e174acc7ee28baafc4Daisuke Miyakawa        final int lowerAlphabetAfterLast = 0x7b;  // {
467839c036444c8a5335cc557e174acc7ee28baafc4Daisuke Miyakawa        final int digitFirst = 0x30;  // 0
468839c036444c8a5335cc557e174acc7ee28baafc4Daisuke Miyakawa        final int digitAfterLast = 0x3A;  // :
469f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        final int hyphen = '-';
470c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        for (final String str : values) {
471c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            if (TextUtils.isEmpty(str)) {
472c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa                continue;
473c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            }
474c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            final int length = str.length();
475c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
476c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa                int codepoint = str.codePointAt(i);
477c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa                if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetAfterLast) ||
478839c036444c8a5335cc557e174acc7ee28baafc4Daisuke Miyakawa                    (upperAlphabetFirst <= codepoint && codepoint < upperAlphabetAfterLast) ||
479839c036444c8a5335cc557e174acc7ee28baafc4Daisuke Miyakawa                    (digitFirst <= codepoint && codepoint < digitAfterLast) ||
480f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                    (codepoint == hyphen))) {
481c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa                    return false;
482c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa                }
483f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            }
484f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
485f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        return true;
486f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    }
487a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa
48869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    public static boolean containsOnlyWhiteSpaces(final String...values) {
48969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        if (values == null) {
49069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            return true;
49169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        }
49269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        return containsOnlyWhiteSpaces(Arrays.asList(values));
49369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    }
49469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
49569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    public static boolean containsOnlyWhiteSpaces(final Collection<String> values) {
49669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        if (values == null) {
49769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            return true;
49869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        }
49969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        for (final String str : values) {
50069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            if (TextUtils.isEmpty(str)) {
50169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                continue;
50269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            }
50369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            final int length = str.length();
50469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
50569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                if (!Character.isWhitespace(str.codePointAt(i))) {
50669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    return false;
50769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                }
50869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            }
50969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        }
51069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        return true;
51169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    }
51269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
513a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa    /**
51469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * <p>
515a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa     * Returns true when the given String is categorized as "word" specified in vCard spec 2.1.
51669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * </p>
51769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * <p>
51869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * vCard 2.1 specifies:<br />
519a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa     * word = &lt;any printable 7bit us-ascii except []=:., &gt;
52069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * </p>
521a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa     */
522a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa    public static boolean isV21Word(final String value) {
523a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        if (TextUtils.isEmpty(value)) {
524a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa            return true;
525a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        }
526a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        final int asciiFirst = 0x20;
527a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        final int asciiLast = 0x7E;  // included
528a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        final int length = value.length();
529a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
530a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa            final int c = value.codePointAt(i);
531a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa            if (!(asciiFirst <= c && c <= asciiLast) ||
532a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa                    sUnAcceptableAsciiInV21WordSet.contains((char)c)) {
533a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa                return false;
534a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa            }
535a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        }
536a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa        return true;
537a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa    }
538a750fdd765ec253ffa8bf3d4848d5c3a35e1979bDaisuke Miyakawa
53969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    private static final int[] sEscapeIndicatorsV30 = new int[]{
54069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        ':', ';', ',', ' '
54169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    };
54269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
54369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    private static final int[] sEscapeIndicatorsV40 = new int[]{
54469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        ';', ':'
54569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    };
54669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
547b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa    /**
548b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa     * <P>
549b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa     * Returns String available as parameter value in vCard 3.0.
550b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa     * </P>
551b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa     * <P>
552b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa     * RFC 2426 requires vCard composer to quote parameter values when it contains
553b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa     * semi-colon, for example (See RFC 2426 for more information).
554b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa     * This method checks whether the given String can be used without quotes.
555b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa     * </P>
556b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa     * <P>
55769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * Note: We remove DQUOTE inside the given value silently for now.
558b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa     * </P>
559b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa     */
56069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    public static String toStringAsV30ParamValue(String value) {
56169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        return toStringAsParamValue(value, sEscapeIndicatorsV30);
56269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    }
56369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
56469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    public static String toStringAsV40ParamValue(String value) {
56569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        return toStringAsParamValue(value, sEscapeIndicatorsV40);
56669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    }
56769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
56869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    private static String toStringAsParamValue(String value, final int[] escapeIndicators) {
569b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa        if (TextUtils.isEmpty(value)) {
570b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa            value = "";
571b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa        }
572b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa        final int asciiFirst = 0x20;
573b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa        final int asciiLast = 0x7E;  // included
574b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa        final StringBuilder builder = new StringBuilder();
575b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa        final int length = value.length();
576b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa        boolean needQuote = false;
577b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa        for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
578b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa            final int codePoint = value.codePointAt(i);
579b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa            if (codePoint < asciiFirst || codePoint == '"') {
580b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa                // CTL characters and DQUOTE are never accepted. Remove them.
581b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa                continue;
582b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa            }
583b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa            builder.appendCodePoint(codePoint);
58469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            for (int indicator : escapeIndicators) {
58569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                if (codePoint == indicator) {
58669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    needQuote = true;
58769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    break;
58869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                }
589b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa            }
590b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa        }
59169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
592b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa        final String result = builder.toString();
59369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        return ((result.isEmpty() || VCardUtils.containsOnlyWhiteSpaces(result))
59469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                ? ""
59569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                : (needQuote ? ('"' + result + '"')
59669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                : result));
597b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa    }
598b7688558cc30bceac8377640db68126e53dd545eDaisuke Miyakawa
599c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa    public static String toHalfWidthString(final String orgString) {
600f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        if (TextUtils.isEmpty(orgString)) {
601f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            return null;
602f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
6033d745c2b925ccc7c6591dbb3dfd21649782af991Daisuke Miyakawa        final StringBuilder builder = new StringBuilder();
6043d745c2b925ccc7c6591dbb3dfd21649782af991Daisuke Miyakawa        final int length = orgString.length();
605c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        for (int i = 0; i < length; i = orgString.offsetByCodePoints(i, 1)) {
606f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            // All Japanese character is able to be expressed by char.
607f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            // Do not need to use String#codepPointAt().
6083d745c2b925ccc7c6591dbb3dfd21649782af991Daisuke Miyakawa            final char ch = orgString.charAt(i);
609c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            final String halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
610f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            if (halfWidthText != null) {
611f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                builder.append(halfWidthText);
612f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            } else {
613f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa                builder.append(ch);
614f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa            }
615f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        }
616f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa        return builder.toString();
617f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    }
61849c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa
61949c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa    /**
62049c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa     * Guesses the format of input image. Currently just the first few bytes are used.
62149c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa     * The type "GIF", "PNG", or "JPEG" is returned when possible. Returns null when
62249c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa     * the guess failed.
62349c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa     * @param input Image as byte array.
62449c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa     * @return The image type or null when the type cannot be determined.
62549c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa     */
62649c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa    public static String guessImageType(final byte[] input) {
627c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        if (input == null) {
628c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            return null;
629c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        }
63049c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa        if (input.length >= 3 && input[0] == 'G' && input[1] == 'I' && input[2] == 'F') {
63149c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa            return "GIF";
63249c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa        } else if (input.length >= 4 && input[0] == (byte) 0x89
63349c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa                && input[1] == 'P' && input[2] == 'N' && input[3] == 'G') {
63449c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa            // Note: vCard 2.1 officially does not support PNG, but we may have it and
63549c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa            //       using X- word like "X-PNG" may not let importers know it is PNG.
63649c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa            //       So we use the String "PNG" as is...
63749c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa            return "PNG";
63849c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa        } else if (input.length >= 2 && input[0] == (byte) 0xff
63949c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa                && input[1] == (byte) 0xd8) {
64049c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa            return "JPEG";
64149c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa        } else {
64249c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa            return null;
64349c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa        }
64449c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa    }
64549c0decf46d4f7082a17e595fba2c501a8369452Daisuke Miyakawa
646c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa    /**
647c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa     * @return True when all the given values are null or empty Strings.
648c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa     */
649c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa    public static boolean areAllEmpty(final String...values) {
650c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        if (values == null) {
651c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            return true;
652c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        }
653c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa
654c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        for (final String value : values) {
655c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            if (!TextUtils.isEmpty(value)) {
656c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa                return false;
657c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa            }
658c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        }
659c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa        return true;
660c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa    }
661c4b51712d89e9fb742b598911e611c80ad51de82Daisuke Miyakawa
66269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    //// The methods bellow may be used by unit test.
66369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
66469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    /**
66569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     * Unquotes given Quoted-Printable value. value must not be null.
66669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa     */
66769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    public static String parseQuotedPrintable(
66869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            final String value, boolean strictLineBreaking,
66969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            String sourceCharset, String targetCharset) {
67069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        // "= " -> " ", "=\t" -> "\t".
67169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        // Previous code had done this replacement. Keep on the safe side.
67269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        final String quotedPrintable;
67369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        {
67469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            final StringBuilder builder = new StringBuilder();
67569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            final int length = value.length();
67669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            for (int i = 0; i < length; i++) {
67769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                char ch = value.charAt(i);
67869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                if (ch == '=' && i < length - 1) {
67969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    char nextCh = value.charAt(i + 1);
68069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    if (nextCh == ' ' || nextCh == '\t') {
68169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                        builder.append(nextCh);
68269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                        i++;
68369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                        continue;
68469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    }
68569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                }
68669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                builder.append(ch);
68769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            }
68869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            quotedPrintable = builder.toString();
68969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        }
69069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
69169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        String[] lines;
69269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        if (strictLineBreaking) {
69369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            lines = quotedPrintable.split("\r\n");
69469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        } else {
69569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            StringBuilder builder = new StringBuilder();
69669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            final int length = quotedPrintable.length();
69769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            ArrayList<String> list = new ArrayList<String>();
69869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            for (int i = 0; i < length; i++) {
69969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                char ch = quotedPrintable.charAt(i);
70069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                if (ch == '\n') {
70169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    list.add(builder.toString());
70269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    builder = new StringBuilder();
70369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                } else if (ch == '\r') {
70469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    list.add(builder.toString());
70569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    builder = new StringBuilder();
70669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    if (i < length - 1) {
70769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                        char nextCh = quotedPrintable.charAt(i + 1);
70869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                        if (nextCh == '\n') {
70969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                            i++;
71069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                        }
71169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    }
71269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                } else {
71369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                    builder.append(ch);
71469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                }
71569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            }
71669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            final String lastLine = builder.toString();
71769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            if (lastLine.length() > 0) {
71869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                list.add(lastLine);
71969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            }
72069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            lines = list.toArray(new String[0]);
72169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        }
72269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
72369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        final StringBuilder builder = new StringBuilder();
72469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        for (String line : lines) {
72569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            if (line.endsWith("=")) {
72669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa                line = line.substring(0, line.length() - 1);
72769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            }
72869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            builder.append(line);
72969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        }
73069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
73169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        final String rawString = builder.toString();
73269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        if (TextUtils.isEmpty(rawString)) {
73369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            Log.w(LOG_TAG, "Given raw string is empty.");
73469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        }
73569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
73669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        byte[] rawBytes = null;
73769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        try {
73869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            rawBytes = rawString.getBytes(sourceCharset);
73969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        } catch (UnsupportedEncodingException e) {
74069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            Log.w(LOG_TAG, "Failed to decode: " + sourceCharset);
74169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            rawBytes = rawString.getBytes();
74269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        }
74369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
74469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        byte[] decodedBytes = null;
74569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        try {
74669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            decodedBytes = QuotedPrintableCodec.decodeQuotedPrintable(rawBytes);
74769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        } catch (DecoderException e) {
74869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            Log.e(LOG_TAG, "DecoderException is thrown.");
74969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            decodedBytes = rawBytes;
75069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        }
75169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
75269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        try {
75369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            return new String(decodedBytes, targetCharset);
75469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        } catch (UnsupportedEncodingException e) {
75569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
75669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            return new String(decodedBytes);
75769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        }
75869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    }
75969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
76069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    public static final VCardParser getAppropriateParser(int vcardType)
76169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            throws VCardException {
76269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        if (VCardConfig.isVersion21(vcardType)) {
76369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            return new VCardParser_V21();
76469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        } else if (VCardConfig.isVersion30(vcardType)) {
76569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            return new VCardParser_V30();
76669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        } else if (VCardConfig.isVersion40(vcardType)) {
76769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            return new VCardParser_V40();
76869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        } else {
76969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            throw new VCardException("Version is not specified");
77069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        }
77169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    }
77269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
77369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    public static final String convertStringCharset(
77469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            String originalString, String sourceCharset, String targetCharset) {
77569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        if (sourceCharset.equalsIgnoreCase(targetCharset)) {
77669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            return originalString;
77769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        }
77869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        final Charset charset = Charset.forName(sourceCharset);
77969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        final ByteBuffer byteBuffer = charset.encode(originalString);
78069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        // byteBuffer.array() "may" return byte array which is larger than
78169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        // byteBuffer.remaining(). Here, we keep on the safe side.
78269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        final byte[] bytes = new byte[byteBuffer.remaining()];
78369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        byteBuffer.get(bytes);
78469831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        try {
78569831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            return new String(bytes, targetCharset);
78669831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        } catch (UnsupportedEncodingException e) {
78769831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
78869831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa            return null;
78969831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa        }
79069831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    }
79169831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
79269831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa    // TODO: utilities for vCard 4.0: datetime, timestamp, integer, float, and boolean
79369831d9dc16c1d36739328910e5d7c0fb7d327feDaisuke Miyakawa
794f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    private VCardUtils() {
795f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa    }
796f4ddea769098e24a7316b9ee895d323005433c2cDaisuke Miyakawa}
797