14199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa/*
24199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * Copyright (C) 2009 The Android Open Source Project
34199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa *
44199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * Licensed under the Apache License, Version 2.0 (the "License");
54199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * you may not use this file except in compliance with the License.
64199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * You may obtain a copy of the License at
74199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa *
84199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa *      http://www.apache.org/licenses/LICENSE-2.0
94199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa *
104199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * Unless required by applicable law or agreed to in writing, software
114199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * distributed under the License is distributed on an "AS IS" BASIS,
124199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * See the License for the specific language governing permissions and
144199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * limitations under the License.
154199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa */
164199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawapackage com.android.vcard;
174199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport android.provider.ContactsContract.CommonDataKinds.Im;
194199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport android.provider.ContactsContract.CommonDataKinds.Phone;
204199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport android.telephony.PhoneNumberUtils;
219919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawaimport android.text.SpannableStringBuilder;
224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport android.text.TextUtils;
234199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport android.util.Log;
244199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
25d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Chengimport com.android.vcard.exception.VCardException;
26d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng
272c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawaimport java.io.ByteArrayOutputStream;
284199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.io.UnsupportedEncodingException;
2958ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawaimport java.nio.ByteBuffer;
3058ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawaimport java.nio.charset.Charset;
314199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.util.ArrayList;
324199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.util.Arrays;
334199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.util.Collection;
344199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.util.HashMap;
354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.util.HashSet;
364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.util.List;
374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.util.Map;
384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.util.Set;
394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa/**
414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * Utilities for VCard handling codes.
424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa */
434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawapublic class VCardUtils {
4402117b3d19787ff65486b9f9db8abd338ae4c9f9Daisuke Miyakawa    private static final String LOG_TAG = VCardConstants.LOG_TAG;
454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
462c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa    /**
472c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa     * See org.apache.commons.codec.DecoderException
482c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa     */
492c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa    private static class DecoderException extends Exception {
502c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa        public DecoderException(String pMessage) {
512c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa            super(pMessage);
522c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa        }
532c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa    }
542c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa
552c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa    /**
562c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa     * See org.apache.commons.codec.net.QuotedPrintableCodec
572c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa     */
582c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa    private static class QuotedPrintableCodecPort {
592c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa        private static byte ESCAPE_CHAR = '=';
602c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa        public static final byte[] decodeQuotedPrintable(byte[] bytes)
612c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                throws DecoderException {
622c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa            if (bytes == null) {
632c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                return null;
642c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa            }
652c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
662c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa            for (int i = 0; i < bytes.length; i++) {
672c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                int b = bytes[i];
682c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                if (b == ESCAPE_CHAR) {
692c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                    try {
702c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                        int u = Character.digit((char) bytes[++i], 16);
712c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                        int l = Character.digit((char) bytes[++i], 16);
722c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                        if (u == -1 || l == -1) {
732c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                            throw new DecoderException("Invalid quoted-printable encoding");
742c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                        }
752c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                        buffer.write((char) ((u << 4) + l));
762c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                    } catch (ArrayIndexOutOfBoundsException e) {
772c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                        throw new DecoderException("Invalid quoted-printable encoding");
782c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                    }
792c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                } else {
802c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                    buffer.write(b);
812c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa                }
822c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa            }
832c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa            return buffer.toByteArray();
842c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa        }
852c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa    }
862c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa
879919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa    /**
889919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa     * Ported methods which are hidden in {@link PhoneNumberUtils}.
899919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa     */
909919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa    public static class PhoneNumberUtilsPort {
919919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa        public static String formatNumber(String source, int defaultFormattingType) {
929919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa            final SpannableStringBuilder text = new SpannableStringBuilder(source);
939919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa            PhoneNumberUtils.formatNumber(text, defaultFormattingType);
949919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa            return text.toString();
959919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa        }
969919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa    }
979919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa
989919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa    /**
999919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa     * Ported methods which are hidden in {@link TextUtils}.
1009919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa     */
1019919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa    public static class TextUtilsPort {
1029919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa        public static boolean isPrintableAscii(final char c) {
1039919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa            final int asciiFirst = 0x20;
1049919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa            final int asciiLast = 0x7E;  // included
1059919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa            return (asciiFirst <= c && c <= asciiLast) || c == '\r' || c == '\n';
1069919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa        }
1079919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa
1089919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa        public static boolean isPrintableAsciiOnly(final CharSequence str) {
1099919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa            final int len = str.length();
1109919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa            for (int i = 0; i < len; i++) {
1119919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa                if (!isPrintableAscii(str.charAt(i))) {
1129919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa                    return false;
1139919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa                }
1149919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa            }
1159919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa            return true;
1169919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa        }
1179919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa    }
1189919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa
1194199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    // Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
1204199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    // converted to two parameter Strings. These only contain some minor fields valid in both
12102117b3d19787ff65486b9f9db8abd338ae4c9f9Daisuke Miyakawa    // vCard and current (as of 2009-08-07) Contacts structure.
1224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private static final Map<Integer, String> sKnownPhoneTypesMap_ItoS;
1234199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private static final Set<String> sPhoneTypesUnknownToContactsSet;
1244199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private static final Map<String, Integer> sKnownPhoneTypeMap_StoI;
1254199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private static final Map<Integer, String> sKnownImPropNameMap_ItoS;
1264199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private static final Set<String> sMobilePhoneLabelSet;
1274199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1284199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    static {
1294199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>();
1304199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypeMap_StoI = new HashMap<String, Integer>();
1314199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1324199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, VCardConstants.PARAM_TYPE_CAR);
1334199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CAR, Phone.TYPE_CAR);
1344199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, VCardConstants.PARAM_TYPE_PAGER);
1354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_PAGER, Phone.TYPE_PAGER);
1364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, VCardConstants.PARAM_TYPE_ISDN);
1374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_ISDN, Phone.TYPE_ISDN);
13802117b3d19787ff65486b9f9db8abd338ae4c9f9Daisuke Miyakawa
1394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_HOME, Phone.TYPE_HOME);
1404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_WORK, Phone.TYPE_WORK);
1414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CELL, Phone.TYPE_MOBILE);
14202117b3d19787ff65486b9f9db8abd338ae4c9f9Daisuke Miyakawa
1434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
1444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_CALLBACK,
1454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                Phone.TYPE_CALLBACK);
1464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(
1474199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                VCardConstants.PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
1484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
1494199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_TTY_TDD,
1504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                Phone.TYPE_TTY_TDD);
1514199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_ASSISTANT,
1524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                Phone.TYPE_ASSISTANT);
153449d710428682b3f44ba20ce290564cd9352ca0aDaisuke Miyakawa        // OTHER (default in Android) should correspond to VOICE (default in vCard).
154449d710428682b3f44ba20ce290564cd9352ca0aDaisuke Miyakawa        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_VOICE, Phone.TYPE_OTHER);
1554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sPhoneTypesUnknownToContactsSet = new HashSet<String>();
1574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MODEM);
1584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MSG);
1594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_BBS);
1604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_VIDEO);
1614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownImPropNameMap_ItoS = new HashMap<Integer, String>();
1634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
1644199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
1654199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
1664199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
1674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK,
1684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                VCardConstants.PROPERTY_X_GOOGLE_TALK);
1694199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
1704199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
1714199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, VCardConstants.PROPERTY_X_QQ);
1724199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, VCardConstants.PROPERTY_X_NETMEETING);
1734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1744199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // \u643A\u5E2F\u96FB\u8A71 = Full-width Hiragana "Keitai-Denwa" (mobile phone)
1754199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // \u643A\u5E2F = Full-width Hiragana "Keitai" (mobile phone)
1764199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // \u30B1\u30A4\u30BF\u30A4 = Full-width Katakana "Keitai" (mobile phone)
1774199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // \uFF79\uFF72\uFF80\uFF72 = Half-width Katakana "Keitai" (mobile phone)
1784199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sMobilePhoneLabelSet = new HashSet<String>(Arrays.asList(
1794199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                "MOBILE", "\u643A\u5E2F\u96FB\u8A71", "\u643A\u5E2F", "\u30B1\u30A4\u30BF\u30A4",
1804199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                "\uFF79\uFF72\uFF80\uFF72"));
1814199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
1824199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1834199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static String getPhoneTypeString(Integer type) {
1844199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return sKnownPhoneTypesMap_ItoS.get(type);
1854199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
1864199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1874199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
1884199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * Returns Interger when the given types can be parsed as known type. Returns String object
18902117b3d19787ff65486b9f9db8abd338ae4c9f9Daisuke Miyakawa     * when not, which should be set to label.
1904199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
1914199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static Object getPhoneTypeFromStrings(Collection<String> types,
1924199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            String number) {
1934199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (number == null) {
1944199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            number = "";
1954199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
1964199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        int type = -1;
1974199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        String label = null;
1984199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        boolean isFax = false;
1994199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        boolean hasPref = false;
20002117b3d19787ff65486b9f9db8abd338ae4c9f9Daisuke Miyakawa
2014199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (types != null) {
20200b4b98ea94df7fa3f88ee9a623d60db0d4fc451Daisuke Miyakawa            for (final String typeStringOrg : types) {
20300b4b98ea94df7fa3f88ee9a623d60db0d4fc451Daisuke Miyakawa                if (typeStringOrg == null) {
2044199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    continue;
2054199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
20600b4b98ea94df7fa3f88ee9a623d60db0d4fc451Daisuke Miyakawa                final String typeStringUpperCase = typeStringOrg.toUpperCase();
20700b4b98ea94df7fa3f88ee9a623d60db0d4fc451Daisuke Miyakawa                if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_PREF)) {
2084199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    hasPref = true;
20900b4b98ea94df7fa3f88ee9a623d60db0d4fc451Daisuke Miyakawa                } else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_FAX)) {
2104199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    isFax = true;
2114199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                } else {
21200b4b98ea94df7fa3f88ee9a623d60db0d4fc451Daisuke Miyakawa                    final String labelCandidate;
21300b4b98ea94df7fa3f88ee9a623d60db0d4fc451Daisuke Miyakawa                    if (typeStringUpperCase.startsWith("X-") && type < 0) {
21400b4b98ea94df7fa3f88ee9a623d60db0d4fc451Daisuke Miyakawa                        labelCandidate = typeStringOrg.substring(2);
21500b4b98ea94df7fa3f88ee9a623d60db0d4fc451Daisuke Miyakawa                    } else {
21600b4b98ea94df7fa3f88ee9a623d60db0d4fc451Daisuke Miyakawa                        labelCandidate = typeStringOrg;
2174199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    }
21800b4b98ea94df7fa3f88ee9a623d60db0d4fc451Daisuke Miyakawa                    if (labelCandidate.length() == 0) {
2194199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        continue;
2204199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    }
22100b4b98ea94df7fa3f88ee9a623d60db0d4fc451Daisuke Miyakawa                    // e.g. "home" -> TYPE_HOME
22200b4b98ea94df7fa3f88ee9a623d60db0d4fc451Daisuke Miyakawa                    final Integer tmp = sKnownPhoneTypeMap_StoI.get(labelCandidate.toUpperCase());
2234199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    if (tmp != null) {
2244199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        final int typeCandidate = tmp;
225449d710428682b3f44ba20ce290564cd9352ca0aDaisuke Miyakawa                        // 1. If a type isn't specified yet, we'll choose the new type candidate.
226449d710428682b3f44ba20ce290564cd9352ca0aDaisuke Miyakawa                        // 2. If the current type is default one (OTHER) or custom one, we'll
227449d710428682b3f44ba20ce290564cd9352ca0aDaisuke Miyakawa                        // prefer more specific types specified in the vCard. Note that OTHER and
228449d710428682b3f44ba20ce290564cd9352ca0aDaisuke Miyakawa                        // the other different types may appear simultaneously here, since vCard
229449d710428682b3f44ba20ce290564cd9352ca0aDaisuke Miyakawa                        // allow to have VOICE and HOME/WORK in one line.
230449d710428682b3f44ba20ce290564cd9352ca0aDaisuke Miyakawa                        // e.g. "TEL;WORK;VOICE:1" -> WORK + OTHER -> Type should be WORK
231449d710428682b3f44ba20ce290564cd9352ca0aDaisuke Miyakawa                        // 3. TYPE_PAGER is prefered when the number contains @ surronded by
2324199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        // a pager number and a domain name.
2334199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        // e.g.
2344199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        // o 1111@domain.com
2354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        // x @domain.com
2364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        // x 1111@
2374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        final int indexOfAt = number.indexOf("@");
2384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        if ((typeCandidate == Phone.TYPE_PAGER
2394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                                && 0 < indexOfAt && indexOfAt < number.length() - 1)
2404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                                || type < 0
241449d710428682b3f44ba20ce290564cd9352ca0aDaisuke Miyakawa                                || type == Phone.TYPE_CUSTOM
242449d710428682b3f44ba20ce290564cd9352ca0aDaisuke Miyakawa                                || type == Phone.TYPE_OTHER) {
2434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                            type = tmp;
2444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        }
2454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    } else if (type < 0) {
2464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        type = Phone.TYPE_CUSTOM;
24700b4b98ea94df7fa3f88ee9a623d60db0d4fc451Daisuke Miyakawa                        label = labelCandidate;
2484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    }
2494199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
2504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
2514199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
2524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (type < 0) {
2534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (hasPref) {
2544199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                type = Phone.TYPE_MAIN;
2554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else {
2564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                // default to TYPE_HOME
2574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                type = Phone.TYPE_HOME;
2584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
2594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
2604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (isFax) {
2614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (type == Phone.TYPE_HOME) {
2624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                type = Phone.TYPE_FAX_HOME;
2634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else if (type == Phone.TYPE_WORK) {
2644199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                type = Phone.TYPE_FAX_WORK;
2654199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else if (type == Phone.TYPE_OTHER) {
2664199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                type = Phone.TYPE_OTHER_FAX;
2674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
2684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
2694199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (type == Phone.TYPE_CUSTOM) {
2704199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return label;
2714199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } else {
2724199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return type;
2734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
2744199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
2754199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2764199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    @SuppressWarnings("deprecation")
2774199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static boolean isMobilePhoneLabel(final String label) {
2784199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // For backward compatibility.
2794199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now.
2804199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        //         To support mobile type at that time, this custom label had been used.
2814199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return ("_AUTO_CELL".equals(label) || sMobilePhoneLabelSet.contains(label));
2824199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
2834199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2844199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) {
2854199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return sPhoneTypesUnknownToContactsSet.contains(label);
2864199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
2874199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2884199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static String getPropertyNameForIm(final int protocol) {
2894199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return sKnownImPropNameMap_ItoS.get(protocol);
2904199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
2914199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2921de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    public static String[] sortNameElements(final int nameOrder,
2934199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final String familyName, final String middleName, final String givenName) {
2944199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final String[] list = new String[3];
2951de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        final int nameOrderType = VCardConfig.getNameOrderType(nameOrder);
2964199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        switch (nameOrderType) {
2974199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            case VCardConfig.NAME_ORDER_JAPANESE: {
2984199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                if (containsOnlyPrintableAscii(familyName) &&
2994199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        containsOnlyPrintableAscii(givenName)) {
3004199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    list[0] = givenName;
3014199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    list[1] = middleName;
3024199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    list[2] = familyName;
3034199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                } else {
3044199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    list[0] = familyName;
3054199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    list[1] = middleName;
3064199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    list[2] = givenName;
3074199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
3084199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                break;
3094199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
3104199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            case VCardConfig.NAME_ORDER_EUROPE: {
3114199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                list[0] = middleName;
3124199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                list[1] = givenName;
3134199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                list[2] = familyName;
3144199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                break;
3154199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
3164199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            default: {
3174199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                list[0] = givenName;
3184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                list[1] = middleName;
3194199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                list[2] = familyName;
3204199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                break;
3214199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
3224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
3234199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return list;
3244199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
3254199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
3264199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static int getPhoneNumberFormat(final int vcardType) {
3274199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (VCardConfig.isJapaneseDevice(vcardType)) {
3284199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return PhoneNumberUtils.FORMAT_JAPAN;
3294199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } else {
3304199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return PhoneNumberUtils.FORMAT_NANP;
3314199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
3324199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
3334199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
3341de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    public static String constructNameFromElements(final int nameOrder,
3354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final String familyName, final String middleName, final String givenName) {
3361de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        return constructNameFromElements(nameOrder, familyName, middleName, givenName,
3374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                null, null);
3384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
3394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
3401de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    public static String constructNameFromElements(final int nameOrder,
3414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final String familyName, final String middleName, final String givenName,
3424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final String prefix, final String suffix) {
3434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final StringBuilder builder = new StringBuilder();
3441de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        final String[] nameList = sortNameElements(nameOrder, familyName, middleName, givenName);
3454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        boolean first = true;
3464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (!TextUtils.isEmpty(prefix)) {
3474199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            first = false;
3484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            builder.append(prefix);
3494199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
3504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        for (final String namePart : nameList) {
3514199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (!TextUtils.isEmpty(namePart)) {
3524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                if (first) {
3534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    first = false;
3544199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                } else {
3554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    builder.append(' ');
3564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
3574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                builder.append(namePart);
3584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
3594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
3604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (!TextUtils.isEmpty(suffix)) {
3614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (!first) {
3624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                builder.append(' ');
3634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
3644199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            builder.append(suffix);
3654199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
3664199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return builder.toString();
3674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
3684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
3694560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa    /**
3704560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa     * Splits the given value into pieces using the delimiter ';' inside it.
3714560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa     *
3724560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa     * Escaped characters in those values are automatically unescaped into original form.
3734560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa     */
3744199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static List<String> constructListFromValue(final String value,
3754560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa            final int vcardType) {
3764199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final List<String> list = new ArrayList<String>();
3774199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        StringBuilder builder = new StringBuilder();
3784560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa        final int length = value.length();
3794199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        for (int i = 0; i < length; i++) {
3804199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            char ch = value.charAt(i);
3814199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (ch == '\\' && i < length - 1) {
3824199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                char nextCh = value.charAt(i + 1);
3834560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa                final String unescapedString;
3844560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa                if (VCardConfig.isVersion40(vcardType)) {
3854560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa                    unescapedString = VCardParserImpl_V40.unescapeCharacter(nextCh);
3864560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa                } else if (VCardConfig.isVersion30(vcardType)) {
3874560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa                    unescapedString = VCardParserImpl_V30.unescapeCharacter(nextCh);
3884560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa                } else {
3894560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa                    if (!VCardConfig.isVersion21(vcardType)) {
3904560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa                        // Unknown vCard type
3914560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa                        Log.w(LOG_TAG, "Unknown vCard type");
3924560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa                    }
3934560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa                    unescapedString = VCardParserImpl_V21.unescapeCharacter(nextCh);
3944560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa                }
3954560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa
3964199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                if (unescapedString != null) {
3974199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    builder.append(unescapedString);
3984199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    i++;
3994199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                } else {
4004199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    builder.append(ch);
4014199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
4024199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else if (ch == ';') {
4034199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                list.add(builder.toString());
4044199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                builder = new StringBuilder();
4054199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else {
4064199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                builder.append(ch);
4074199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
4084199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
4094199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        list.add(builder.toString());
4104199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return list;
4114199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
4124199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
4134199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static boolean containsOnlyPrintableAscii(final String...values) {
4144199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (values == null) {
4154199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return true;
4164199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
4174199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return containsOnlyPrintableAscii(Arrays.asList(values));
4184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
4194199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
4204199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static boolean containsOnlyPrintableAscii(final Collection<String> values) {
4214199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (values == null) {
4224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return true;
4234199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
4244199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        for (final String value : values) {
4254199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (TextUtils.isEmpty(value)) {
4264199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                continue;
4274199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
4289919ad2126c06dbf2eb54a11e6158f87f316bc22Daisuke Miyakawa            if (!TextUtilsPort.isPrintableAsciiOnly(value)) {
4294199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                return false;
4304199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
4314199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
4324199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return true;
4334199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
4344199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
4354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
4364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * <p>
4374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * This is useful when checking the string should be encoded into quoted-printable
4384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * or not, which is required by vCard 2.1.
4394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * </p>
4404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * <p>
4414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * See the definition of "7bit" in vCard 2.1 spec for more information.
4424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * </p>
4434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
4444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
4454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (values == null) {
4464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return true;
4474199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
4484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return containsOnlyNonCrLfPrintableAscii(Arrays.asList(values));
4494199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
4504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
4514199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static boolean containsOnlyNonCrLfPrintableAscii(final Collection<String> values) {
4524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (values == null) {
4534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return true;
4544199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
4554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final int asciiFirst = 0x20;
4564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final int asciiLast = 0x7E;  // included
4574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        for (final String value : values) {
4584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (TextUtils.isEmpty(value)) {
4594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                continue;
4604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
4614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final int length = value.length();
4624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
4634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                final int c = value.codePointAt(i);
4644199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                if (!(asciiFirst <= c && c <= asciiLast)) {
4654199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    return false;
4664199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
4674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
4684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
4694199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return true;
4704199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
4714199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
4724199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private static final Set<Character> sUnAcceptableAsciiInV21WordSet =
4734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));
4744199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
4754199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
4764199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * <p>
4774199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * This is useful since vCard 3.0 often requires the ("X-") properties and groups
4784199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * should contain only alphabets, digits, and hyphen.
4794199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * </p>
48002117b3d19787ff65486b9f9db8abd338ae4c9f9Daisuke Miyakawa     * <p>
4814199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * Note: It is already known some devices (wrongly) outputs properties with characters
4824199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     *       which should not be in the field. One example is "X-GOOGLE TALK". We accept
4834199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     *       such kind of input but must never output it unless the target is very specific
4844199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     *       to the device which is able to parse the malformed input.
4854199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * </p>
4864199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
4874199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static boolean containsOnlyAlphaDigitHyphen(final String...values) {
4884199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (values == null) {
4894199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return true;
4904199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
4914199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return containsOnlyAlphaDigitHyphen(Arrays.asList(values));
4924199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
4934199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
4944199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static boolean containsOnlyAlphaDigitHyphen(final Collection<String> values) {
4954199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (values == null) {
4964199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return true;
4974199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
4984199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final int upperAlphabetFirst = 0x41;  // A
4994199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final int upperAlphabetAfterLast = 0x5b;  // [
5004199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final int lowerAlphabetFirst = 0x61;  // a
5014199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final int lowerAlphabetAfterLast = 0x7b;  // {
5024199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final int digitFirst = 0x30;  // 0
5034199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final int digitAfterLast = 0x3A;  // :
5044199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final int hyphen = '-';
5054199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        for (final String str : values) {
5064199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (TextUtils.isEmpty(str)) {
5074199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                continue;
5084199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
5094199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final int length = str.length();
5104199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
5114199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                int codepoint = str.codePointAt(i);
5124199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetAfterLast) ||
5134199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    (upperAlphabetFirst <= codepoint && codepoint < upperAlphabetAfterLast) ||
5144199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    (digitFirst <= codepoint && codepoint < digitAfterLast) ||
5154199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    (codepoint == hyphen))) {
5164199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    return false;
5174199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
5184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
5194199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
5204199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return true;
5214199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
5224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
5232bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa    public static boolean containsOnlyWhiteSpaces(final String...values) {
5242bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        if (values == null) {
5252bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa            return true;
5262bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        }
5272bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        return containsOnlyWhiteSpaces(Arrays.asList(values));
5282bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa    }
5292bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa
5302bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa    public static boolean containsOnlyWhiteSpaces(final Collection<String> values) {
5312bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        if (values == null) {
5322bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa            return true;
5332bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        }
5342bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        for (final String str : values) {
5352bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa            if (TextUtils.isEmpty(str)) {
5362bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa                continue;
5372bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa            }
5382bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa            final int length = str.length();
5392bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa            for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
5402bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa                if (!Character.isWhitespace(str.codePointAt(i))) {
5412bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa                    return false;
5422bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa                }
5432bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa            }
5442bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        }
5452bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        return true;
5462bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa    }
5472bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa
5484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
5494199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * <p>
5504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * Returns true when the given String is categorized as "word" specified in vCard spec 2.1.
5514199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * </p>
5524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * <p>
5534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * vCard 2.1 specifies:<br />
5544199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * word = &lt;any printable 7bit us-ascii except []=:., &gt;
5554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * </p>
5564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
5574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static boolean isV21Word(final String value) {
5584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (TextUtils.isEmpty(value)) {
5594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return true;
5604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
5614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final int asciiFirst = 0x20;
5624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final int asciiLast = 0x7E;  // included
5634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final int length = value.length();
5644199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
5654199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final int c = value.codePointAt(i);
5664199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (!(asciiFirst <= c && c <= asciiLast) ||
5674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    sUnAcceptableAsciiInV21WordSet.contains((char)c)) {
5684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                return false;
5694199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
5704199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
5714199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return true;
5724199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
5734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
5743d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa    private static final int[] sEscapeIndicatorsV30 = new int[]{
5753d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa        ':', ';', ',', ' '
5763d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa    };
5773d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa
5783d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa    private static final int[] sEscapeIndicatorsV40 = new int[]{
5793d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa        ';', ':'
5803d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa    };
5813d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa
5822bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa    /**
5832bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa     * <P>
5842bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa     * Returns String available as parameter value in vCard 3.0.
5852bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa     * </P>
5862bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa     * <P>
5872bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa     * RFC 2426 requires vCard composer to quote parameter values when it contains
5882bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa     * semi-colon, for example (See RFC 2426 for more information).
5892bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa     * This method checks whether the given String can be used without quotes.
5902bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa     * </P>
5912bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa     * <P>
5923d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa     * Note: We remove DQUOTE inside the given value silently for now.
5932bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa     * </P>
5942bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa     */
5953d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa    public static String toStringAsV30ParamValue(String value) {
5963d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa        return toStringAsParamValue(value, sEscapeIndicatorsV30);
5973d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa    }
5983d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa
5993d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa    public static String toStringAsV40ParamValue(String value) {
6003d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa        return toStringAsParamValue(value, sEscapeIndicatorsV40);
6013d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa    }
6023d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa
6033d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa    private static String toStringAsParamValue(String value, final int[] escapeIndicators) {
6042bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        if (TextUtils.isEmpty(value)) {
6052bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa            value = "";
6062bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        }
6072bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        final int asciiFirst = 0x20;
6082bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        final int asciiLast = 0x7E;  // included
6092bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        final StringBuilder builder = new StringBuilder();
6102bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        final int length = value.length();
6112bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        boolean needQuote = false;
6122bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
6132bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa            final int codePoint = value.codePointAt(i);
6142bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa            if (codePoint < asciiFirst || codePoint == '"') {
6152bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa                // CTL characters and DQUOTE are never accepted. Remove them.
6162bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa                continue;
6172bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa            }
6182bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa            builder.appendCodePoint(codePoint);
6193d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa            for (int indicator : escapeIndicators) {
6203d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa                if (codePoint == indicator) {
6213d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa                    needQuote = true;
6223d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa                    break;
6233d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa                }
6242bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa            }
6252bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        }
6262bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa
6272bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        final String result = builder.toString();
6282bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa        return ((result.isEmpty() || VCardUtils.containsOnlyWhiteSpaces(result))
6292bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa                ? ""
6302bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa                : (needQuote ? ('"' + result + '"')
6312bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa                : result));
6322bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa    }
6332bf85a1a15a3175119ab8415fc590fd5fe3d0752Daisuke Miyakawa
6344199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static String toHalfWidthString(final String orgString) {
6354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (TextUtils.isEmpty(orgString)) {
6364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return null;
6374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
6384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final StringBuilder builder = new StringBuilder();
6394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final int length = orgString.length();
6404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        for (int i = 0; i < length; i = orgString.offsetByCodePoints(i, 1)) {
6414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            // All Japanese character is able to be expressed by char.
6424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            // Do not need to use String#codepPointAt().
6434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final char ch = orgString.charAt(i);
6444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final String halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
6454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (halfWidthText != null) {
6464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                builder.append(halfWidthText);
6474199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else {
6484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                builder.append(ch);
6494199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
6504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
6514199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return builder.toString();
6524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
6534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
6544199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
6554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * Guesses the format of input image. Currently just the first few bytes are used.
6564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * The type "GIF", "PNG", or "JPEG" is returned when possible. Returns null when
6574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * the guess failed.
6584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @param input Image as byte array.
6594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @return The image type or null when the type cannot be determined.
6604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
6614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static String guessImageType(final byte[] input) {
6624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (input == null) {
6634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return null;
6644199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
6654199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (input.length >= 3 && input[0] == 'G' && input[1] == 'I' && input[2] == 'F') {
6664199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return "GIF";
6674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } else if (input.length >= 4 && input[0] == (byte) 0x89
6684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                && input[1] == 'P' && input[2] == 'N' && input[3] == 'G') {
6694199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            // Note: vCard 2.1 officially does not support PNG, but we may have it and
6704199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            //       using X- word like "X-PNG" may not let importers know it is PNG.
6714199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            //       So we use the String "PNG" as is...
6724199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return "PNG";
6734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } else if (input.length >= 2 && input[0] == (byte) 0xff
6744199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                && input[1] == (byte) 0xd8) {
6754199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return "JPEG";
6764199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } else {
6774199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return null;
6784199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
6794199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
6804199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
6814199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
6824199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @return True when all the given values are null or empty Strings.
6834199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
6844199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public static boolean areAllEmpty(final String...values) {
6854199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (values == null) {
6864199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return true;
6874199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
6884199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
6894199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        for (final String value : values) {
6904199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (!TextUtils.isEmpty(value)) {
6914199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                return false;
6924199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
6934199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
6944199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return true;
6954199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
6964199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
697d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng    /**
698d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng     * Checks to see if a string looks like it could be an android generated quoted printable.
699d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng     *
700d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng     * Identification of quoted printable is not 100% reliable since it's just ascii.  But given
701d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng     * the high number and exact location of generated = signs, there is a high likely-hood that
702d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng     * it would be.
703d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng     *
704d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng     * @return True if it appears like android quoted printable.  False otherwise.
705d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng     */
706d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng    public static boolean appearsLikeAndroidVCardQuotedPrintable(String value) {
707d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng
708d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng        // Quoted printable is always in multiple of 3s. With optional 1 '=' at end.
709d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng        final int remainder = (value.length() % 3);
710d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng        if (value.length() < 2 || (remainder != 1 && remainder != 0)) {
711d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng            return false;
712d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng        }
713d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng        for (int i = 0; i < value.length(); i += 3) {
714d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng            if (value.charAt(i) != '=') {
715d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng                return false;
716d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng            }
717d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng        }
718d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng        return true;
719d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng    }
720d98c0fe9ab6a89129c31c510ccd629a2dca148afChiao Cheng
7214199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    //// The methods bellow may be used by unit test.
7224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
7234199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
72458610106ce61adad9b1caa1fe9f7925c3e938babDaisuke Miyakawa     * Unquotes given Quoted-Printable value. value must not be null.
7254199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
72658610106ce61adad9b1caa1fe9f7925c3e938babDaisuke Miyakawa    public static String parseQuotedPrintable(
72758610106ce61adad9b1caa1fe9f7925c3e938babDaisuke Miyakawa            final String value, boolean strictLineBreaking,
7284199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            String sourceCharset, String targetCharset) {
7294199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // "= " -> " ", "=\t" -> "\t".
7304199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // Previous code had done this replacement. Keep on the safe side.
7314199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final String quotedPrintable;
7324199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        {
7334199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final StringBuilder builder = new StringBuilder();
7344199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final int length = value.length();
7354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            for (int i = 0; i < length; i++) {
7364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                char ch = value.charAt(i);
7374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                if (ch == '=' && i < length - 1) {
7384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    char nextCh = value.charAt(i + 1);
7394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    if (nextCh == ' ' || nextCh == '\t') {
7404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        builder.append(nextCh);
7414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        i++;
7424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        continue;
7434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    }
7444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
7454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                builder.append(ch);
7464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
7474199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            quotedPrintable = builder.toString();
7484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
7494199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
7504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        String[] lines;
7514199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (strictLineBreaking) {
7524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            lines = quotedPrintable.split("\r\n");
7534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } else {
7544199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            StringBuilder builder = new StringBuilder();
7554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final int length = quotedPrintable.length();
7564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            ArrayList<String> list = new ArrayList<String>();
7574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            for (int i = 0; i < length; i++) {
7584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                char ch = quotedPrintable.charAt(i);
7594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                if (ch == '\n') {
7604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    list.add(builder.toString());
7614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    builder = new StringBuilder();
7624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                } else if (ch == '\r') {
7634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    list.add(builder.toString());
7644199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    builder = new StringBuilder();
7654199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    if (i < length - 1) {
7664199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        char nextCh = quotedPrintable.charAt(i + 1);
7674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        if (nextCh == '\n') {
7684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                            i++;
7694199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        }
7704199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    }
7714199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                } else {
7724199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    builder.append(ch);
7734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
7744199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
7754199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final String lastLine = builder.toString();
7764199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (lastLine.length() > 0) {
7774199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                list.add(lastLine);
7784199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
7794199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            lines = list.toArray(new String[0]);
7804199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
7814199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
7824199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final StringBuilder builder = new StringBuilder();
7834199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        for (String line : lines) {
7844199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (line.endsWith("=")) {
7854199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                line = line.substring(0, line.length() - 1);
7864199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
7874199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            builder.append(line);
7884199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
7894199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
7904199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final String rawString = builder.toString();
7914199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (TextUtils.isEmpty(rawString)) {
7924199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            Log.w(LOG_TAG, "Given raw string is empty.");
7934199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
7944199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
7954199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        byte[] rawBytes = null;
7964199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        try {
79702117b3d19787ff65486b9f9db8abd338ae4c9f9Daisuke Miyakawa            rawBytes = rawString.getBytes(sourceCharset);
7984199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } catch (UnsupportedEncodingException e) {
7994199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            Log.w(LOG_TAG, "Failed to decode: " + sourceCharset);
8004199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            rawBytes = rawString.getBytes();
8014199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
8024199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8034199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        byte[] decodedBytes = null;
8044199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        try {
8052c4b5f3a9df3eff8ed8a7b3d2dc7739277e07d41Daisuke Miyakawa            decodedBytes = QuotedPrintableCodecPort.decodeQuotedPrintable(rawBytes);
8064199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } catch (DecoderException e) {
8074199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            Log.e(LOG_TAG, "DecoderException is thrown.");
8084199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            decodedBytes = rawBytes;
8094199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
8104199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8114199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        try {
8124199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return new String(decodedBytes, targetCharset);
8134199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } catch (UnsupportedEncodingException e) {
8144199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
8154199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return new String(decodedBytes);
8164199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
8174199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
8184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
819be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa    public static final VCardParser getAppropriateParser(int vcardType)
820be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa            throws VCardException {
821be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        if (VCardConfig.isVersion21(vcardType)) {
822be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa            return new VCardParser_V21();
823be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        } else if (VCardConfig.isVersion30(vcardType)) {
824be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa            return new VCardParser_V30();
825be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        } else if (VCardConfig.isVersion40(vcardType)) {
826be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa            return new VCardParser_V40();
827be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        } else {
828be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa            throw new VCardException("Version is not specified");
829be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        }
830be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa    }
831be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa
83258ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa    public static final String convertStringCharset(
83358ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa            String originalString, String sourceCharset, String targetCharset) {
83458ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa        if (sourceCharset.equalsIgnoreCase(targetCharset)) {
83558ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa            return originalString;
83658ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa        }
83758ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa        final Charset charset = Charset.forName(sourceCharset);
83858ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa        final ByteBuffer byteBuffer = charset.encode(originalString);
83958ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa        // byteBuffer.array() "may" return byte array which is larger than
84058ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa        // byteBuffer.remaining(). Here, we keep on the safe side.
84158ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa        final byte[] bytes = new byte[byteBuffer.remaining()];
84258ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa        byteBuffer.get(bytes);
84358ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa        try {
84458ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa            return new String(bytes, targetCharset);
84558ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa        } catch (UnsupportedEncodingException e) {
84658ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa            Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
84758ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa            return null;
84858ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa        }
84958ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa    }
85058ca5f9943bb5c8aeeab3150ac96f1143dfd86baDaisuke Miyakawa
851be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa    // TODO: utilities for vCard 4.0: datetime, timestamp, integer, float, and boolean
852be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa
8534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private VCardUtils() {
8544199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
8554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa}
856