VCardUtils.java revision 2bf85a1a15a3175119ab8415fc590fd5fe3d0752
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package com.android.vcard; 17 18import com.android.vcard.exception.VCardException; 19 20import org.apache.commons.codec.DecoderException; 21import org.apache.commons.codec.net.QuotedPrintableCodec; 22 23import android.content.ContentProviderOperation; 24import android.provider.ContactsContract.Data; 25import android.provider.ContactsContract.CommonDataKinds.Im; 26import android.provider.ContactsContract.CommonDataKinds.Phone; 27import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 28import android.telephony.PhoneNumberUtils; 29import android.text.TextUtils; 30import android.util.Log; 31 32import java.io.UnsupportedEncodingException; 33import java.util.ArrayList; 34import java.util.Arrays; 35import java.util.Collection; 36import java.util.HashMap; 37import java.util.HashSet; 38import java.util.List; 39import java.util.Map; 40import java.util.Set; 41 42/** 43 * Utilities for VCard handling codes. 44 */ 45public class VCardUtils { 46 private static final String LOG_TAG = "VCardUtils"; 47 48 // Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is 49 // converted to two parameter Strings. These only contain some minor fields valid in both 50 // vCard and current (as of 2009-08-07) Contacts structure. 51 private static final Map<Integer, String> sKnownPhoneTypesMap_ItoS; 52 private static final Set<String> sPhoneTypesUnknownToContactsSet; 53 private static final Map<String, Integer> sKnownPhoneTypeMap_StoI; 54 private static final Map<Integer, String> sKnownImPropNameMap_ItoS; 55 private static final Set<String> sMobilePhoneLabelSet; 56 57 static { 58 sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>(); 59 sKnownPhoneTypeMap_StoI = new HashMap<String, Integer>(); 60 61 sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, VCardConstants.PARAM_TYPE_CAR); 62 sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CAR, Phone.TYPE_CAR); 63 sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, VCardConstants.PARAM_TYPE_PAGER); 64 sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_PAGER, Phone.TYPE_PAGER); 65 sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, VCardConstants.PARAM_TYPE_ISDN); 66 sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_ISDN, Phone.TYPE_ISDN); 67 68 sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_HOME, Phone.TYPE_HOME); 69 sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_WORK, Phone.TYPE_WORK); 70 sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CELL, Phone.TYPE_MOBILE); 71 72 sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER); 73 sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_CALLBACK, 74 Phone.TYPE_CALLBACK); 75 sKnownPhoneTypeMap_StoI.put( 76 VCardConstants.PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN); 77 sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO); 78 sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_TTY_TDD, 79 Phone.TYPE_TTY_TDD); 80 sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_ASSISTANT, 81 Phone.TYPE_ASSISTANT); 82 83 sPhoneTypesUnknownToContactsSet = new HashSet<String>(); 84 sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MODEM); 85 sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MSG); 86 sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_BBS); 87 sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_VIDEO); 88 89 sKnownImPropNameMap_ItoS = new HashMap<Integer, String>(); 90 sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM); 91 sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN); 92 sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO); 93 sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME); 94 sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK, 95 VCardConstants.PROPERTY_X_GOOGLE_TALK); 96 sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ); 97 sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER); 98 sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, VCardConstants.PROPERTY_X_QQ); 99 sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, VCardConstants.PROPERTY_X_NETMEETING); 100 101 // \u643A\u5E2F\u96FB\u8A71 = Full-width Hiragana "Keitai-Denwa" (mobile phone) 102 // \u643A\u5E2F = Full-width Hiragana "Keitai" (mobile phone) 103 // \u30B1\u30A4\u30BF\u30A4 = Full-width Katakana "Keitai" (mobile phone) 104 // \uFF79\uFF72\uFF80\uFF72 = Half-width Katakana "Keitai" (mobile phone) 105 sMobilePhoneLabelSet = new HashSet<String>(Arrays.asList( 106 "MOBILE", "\u643A\u5E2F\u96FB\u8A71", "\u643A\u5E2F", "\u30B1\u30A4\u30BF\u30A4", 107 "\uFF79\uFF72\uFF80\uFF72")); 108 } 109 110 public static String getPhoneTypeString(Integer type) { 111 return sKnownPhoneTypesMap_ItoS.get(type); 112 } 113 114 /** 115 * Returns Interger when the given types can be parsed as known type. Returns String object 116 * when not, which should be set to label. 117 */ 118 public static Object getPhoneTypeFromStrings(Collection<String> types, 119 String number) { 120 if (number == null) { 121 number = ""; 122 } 123 int type = -1; 124 String label = null; 125 boolean isFax = false; 126 boolean hasPref = false; 127 128 if (types != null) { 129 for (String typeString : types) { 130 if (typeString == null) { 131 continue; 132 } 133 typeString = typeString.toUpperCase(); 134 if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) { 135 hasPref = true; 136 } else if (typeString.equals(VCardConstants.PARAM_TYPE_FAX)) { 137 isFax = true; 138 } else { 139 if (typeString.startsWith("X-") && type < 0) { 140 typeString = typeString.substring(2); 141 } 142 if (typeString.length() == 0) { 143 continue; 144 } 145 final Integer tmp = sKnownPhoneTypeMap_StoI.get(typeString); 146 if (tmp != null) { 147 final int typeCandidate = tmp; 148 // TYPE_PAGER is prefered when the number contains @ surronded by 149 // a pager number and a domain name. 150 // e.g. 151 // o 1111@domain.com 152 // x @domain.com 153 // x 1111@ 154 final int indexOfAt = number.indexOf("@"); 155 if ((typeCandidate == Phone.TYPE_PAGER 156 && 0 < indexOfAt && indexOfAt < number.length() - 1) 157 || type < 0 158 || type == Phone.TYPE_CUSTOM) { 159 type = tmp; 160 } 161 } else if (type < 0) { 162 type = Phone.TYPE_CUSTOM; 163 label = typeString; 164 } 165 } 166 } 167 } 168 if (type < 0) { 169 if (hasPref) { 170 type = Phone.TYPE_MAIN; 171 } else { 172 // default to TYPE_HOME 173 type = Phone.TYPE_HOME; 174 } 175 } 176 if (isFax) { 177 if (type == Phone.TYPE_HOME) { 178 type = Phone.TYPE_FAX_HOME; 179 } else if (type == Phone.TYPE_WORK) { 180 type = Phone.TYPE_FAX_WORK; 181 } else if (type == Phone.TYPE_OTHER) { 182 type = Phone.TYPE_OTHER_FAX; 183 } 184 } 185 if (type == Phone.TYPE_CUSTOM) { 186 return label; 187 } else { 188 return type; 189 } 190 } 191 192 @SuppressWarnings("deprecation") 193 public static boolean isMobilePhoneLabel(final String label) { 194 // For backward compatibility. 195 // Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now. 196 // To support mobile type at that time, this custom label had been used. 197 return ("_AUTO_CELL".equals(label) || sMobilePhoneLabelSet.contains(label)); 198 } 199 200 public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) { 201 return sPhoneTypesUnknownToContactsSet.contains(label); 202 } 203 204 public static String getPropertyNameForIm(final int protocol) { 205 return sKnownImPropNameMap_ItoS.get(protocol); 206 } 207 208 public static String[] sortNameElements(final int vcardType, 209 final String familyName, final String middleName, final String givenName) { 210 final String[] list = new String[3]; 211 final int nameOrderType = VCardConfig.getNameOrderType(vcardType); 212 switch (nameOrderType) { 213 case VCardConfig.NAME_ORDER_JAPANESE: { 214 if (containsOnlyPrintableAscii(familyName) && 215 containsOnlyPrintableAscii(givenName)) { 216 list[0] = givenName; 217 list[1] = middleName; 218 list[2] = familyName; 219 } else { 220 list[0] = familyName; 221 list[1] = middleName; 222 list[2] = givenName; 223 } 224 break; 225 } 226 case VCardConfig.NAME_ORDER_EUROPE: { 227 list[0] = middleName; 228 list[1] = givenName; 229 list[2] = familyName; 230 break; 231 } 232 default: { 233 list[0] = givenName; 234 list[1] = middleName; 235 list[2] = familyName; 236 break; 237 } 238 } 239 return list; 240 } 241 242 public static int getPhoneNumberFormat(final int vcardType) { 243 if (VCardConfig.isJapaneseDevice(vcardType)) { 244 return PhoneNumberUtils.FORMAT_JAPAN; 245 } else { 246 return PhoneNumberUtils.FORMAT_NANP; 247 } 248 } 249 250 /** 251 * <p> 252 * Inserts postal data into the builder object. 253 * </p> 254 * <p> 255 * Note that the data structure of ContactsContract is different from that defined in vCard. 256 * So some conversion may be performed in this method. 257 * </p> 258 */ 259 public static void insertStructuredPostalDataUsingContactsStruct(int vcardType, 260 final ContentProviderOperation.Builder builder, 261 final VCardEntry.PostalData postalData) { 262 builder.withValueBackReference(StructuredPostal.RAW_CONTACT_ID, 0); 263 builder.withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE); 264 265 builder.withValue(StructuredPostal.TYPE, postalData.type); 266 if (postalData.type == StructuredPostal.TYPE_CUSTOM) { 267 builder.withValue(StructuredPostal.LABEL, postalData.label); 268 } 269 270 final String streetString; 271 if (TextUtils.isEmpty(postalData.street)) { 272 if (TextUtils.isEmpty(postalData.extendedAddress)) { 273 streetString = null; 274 } else { 275 streetString = postalData.extendedAddress; 276 } 277 } else { 278 if (TextUtils.isEmpty(postalData.extendedAddress)) { 279 streetString = postalData.street; 280 } else { 281 streetString = postalData.street + " " + postalData.extendedAddress; 282 } 283 } 284 builder.withValue(StructuredPostal.POBOX, postalData.pobox); 285 builder.withValue(StructuredPostal.STREET, streetString); 286 builder.withValue(StructuredPostal.CITY, postalData.localty); 287 builder.withValue(StructuredPostal.REGION, postalData.region); 288 builder.withValue(StructuredPostal.POSTCODE, postalData.postalCode); 289 builder.withValue(StructuredPostal.COUNTRY, postalData.country); 290 291 builder.withValue(StructuredPostal.FORMATTED_ADDRESS, 292 postalData.getFormattedAddress(vcardType)); 293 if (postalData.isPrimary) { 294 builder.withValue(Data.IS_PRIMARY, 1); 295 } 296 } 297 298 public static String constructNameFromElements(final int vcardType, 299 final String familyName, final String middleName, final String givenName) { 300 return constructNameFromElements(vcardType, familyName, middleName, givenName, 301 null, null); 302 } 303 304 public static String constructNameFromElements(final int vcardType, 305 final String familyName, final String middleName, final String givenName, 306 final String prefix, final String suffix) { 307 final StringBuilder builder = new StringBuilder(); 308 final String[] nameList = sortNameElements(vcardType, familyName, middleName, givenName); 309 boolean first = true; 310 if (!TextUtils.isEmpty(prefix)) { 311 first = false; 312 builder.append(prefix); 313 } 314 for (final String namePart : nameList) { 315 if (!TextUtils.isEmpty(namePart)) { 316 if (first) { 317 first = false; 318 } else { 319 builder.append(' '); 320 } 321 builder.append(namePart); 322 } 323 } 324 if (!TextUtils.isEmpty(suffix)) { 325 if (!first) { 326 builder.append(' '); 327 } 328 builder.append(suffix); 329 } 330 return builder.toString(); 331 } 332 333 public static List<String> constructListFromValue(final String value, 334 final boolean isV30) { 335 final List<String> list = new ArrayList<String>(); 336 StringBuilder builder = new StringBuilder(); 337 int length = value.length(); 338 for (int i = 0; i < length; i++) { 339 char ch = value.charAt(i); 340 if (ch == '\\' && i < length - 1) { 341 char nextCh = value.charAt(i + 1); 342 final String unescapedString = 343 (isV30 ? VCardParserImpl_V30.escapeCharacter(nextCh) : 344 VCardParserImpl_V21.unescapeCharacter(nextCh)); 345 if (unescapedString != null) { 346 builder.append(unescapedString); 347 i++; 348 } else { 349 builder.append(ch); 350 } 351 } else if (ch == ';') { 352 list.add(builder.toString()); 353 builder = new StringBuilder(); 354 } else { 355 builder.append(ch); 356 } 357 } 358 list.add(builder.toString()); 359 return list; 360 } 361 362 public static boolean containsOnlyPrintableAscii(final String...values) { 363 if (values == null) { 364 return true; 365 } 366 return containsOnlyPrintableAscii(Arrays.asList(values)); 367 } 368 369 public static boolean containsOnlyPrintableAscii(final Collection<String> values) { 370 if (values == null) { 371 return true; 372 } 373 for (final String value : values) { 374 if (TextUtils.isEmpty(value)) { 375 continue; 376 } 377 if (!TextUtils.isPrintableAsciiOnly(value)) { 378 return false; 379 } 380 } 381 return true; 382 } 383 384 /** 385 * <p> 386 * This is useful when checking the string should be encoded into quoted-printable 387 * or not, which is required by vCard 2.1. 388 * </p> 389 * <p> 390 * See the definition of "7bit" in vCard 2.1 spec for more information. 391 * </p> 392 */ 393 public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) { 394 if (values == null) { 395 return true; 396 } 397 return containsOnlyNonCrLfPrintableAscii(Arrays.asList(values)); 398 } 399 400 public static boolean containsOnlyNonCrLfPrintableAscii(final Collection<String> values) { 401 if (values == null) { 402 return true; 403 } 404 final int asciiFirst = 0x20; 405 final int asciiLast = 0x7E; // included 406 for (final String value : values) { 407 if (TextUtils.isEmpty(value)) { 408 continue; 409 } 410 final int length = value.length(); 411 for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) { 412 final int c = value.codePointAt(i); 413 if (!(asciiFirst <= c && c <= asciiLast)) { 414 return false; 415 } 416 } 417 } 418 return true; 419 } 420 421 private static final Set<Character> sUnAcceptableAsciiInV21WordSet = 422 new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' ')); 423 424 /** 425 * <p> 426 * This is useful since vCard 3.0 often requires the ("X-") properties and groups 427 * should contain only alphabets, digits, and hyphen. 428 * </p> 429 * <p> 430 * Note: It is already known some devices (wrongly) outputs properties with characters 431 * which should not be in the field. One example is "X-GOOGLE TALK". We accept 432 * such kind of input but must never output it unless the target is very specific 433 * to the device which is able to parse the malformed input. 434 * </p> 435 */ 436 public static boolean containsOnlyAlphaDigitHyphen(final String...values) { 437 if (values == null) { 438 return true; 439 } 440 return containsOnlyAlphaDigitHyphen(Arrays.asList(values)); 441 } 442 443 public static boolean containsOnlyAlphaDigitHyphen(final Collection<String> values) { 444 if (values == null) { 445 return true; 446 } 447 final int upperAlphabetFirst = 0x41; // A 448 final int upperAlphabetAfterLast = 0x5b; // [ 449 final int lowerAlphabetFirst = 0x61; // a 450 final int lowerAlphabetAfterLast = 0x7b; // { 451 final int digitFirst = 0x30; // 0 452 final int digitAfterLast = 0x3A; // : 453 final int hyphen = '-'; 454 for (final String str : values) { 455 if (TextUtils.isEmpty(str)) { 456 continue; 457 } 458 final int length = str.length(); 459 for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) { 460 int codepoint = str.codePointAt(i); 461 if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetAfterLast) || 462 (upperAlphabetFirst <= codepoint && codepoint < upperAlphabetAfterLast) || 463 (digitFirst <= codepoint && codepoint < digitAfterLast) || 464 (codepoint == hyphen))) { 465 return false; 466 } 467 } 468 } 469 return true; 470 } 471 472 public static boolean containsOnlyWhiteSpaces(final String...values) { 473 if (values == null) { 474 return true; 475 } 476 return containsOnlyWhiteSpaces(Arrays.asList(values)); 477 } 478 479 public static boolean containsOnlyWhiteSpaces(final Collection<String> values) { 480 if (values == null) { 481 return true; 482 } 483 for (final String str : values) { 484 if (TextUtils.isEmpty(str)) { 485 continue; 486 } 487 final int length = str.length(); 488 for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) { 489 if (!Character.isWhitespace(str.codePointAt(i))) { 490 return false; 491 } 492 } 493 } 494 return true; 495 } 496 497 /** 498 * <p> 499 * Returns true when the given String is categorized as "word" specified in vCard spec 2.1. 500 * </p> 501 * <p> 502 * vCard 2.1 specifies:<br /> 503 * word = <any printable 7bit us-ascii except []=:., > 504 * </p> 505 */ 506 public static boolean isV21Word(final String value) { 507 if (TextUtils.isEmpty(value)) { 508 return true; 509 } 510 final int asciiFirst = 0x20; 511 final int asciiLast = 0x7E; // included 512 final int length = value.length(); 513 for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) { 514 final int c = value.codePointAt(i); 515 if (!(asciiFirst <= c && c <= asciiLast) || 516 sUnAcceptableAsciiInV21WordSet.contains((char)c)) { 517 return false; 518 } 519 } 520 return true; 521 } 522 523 /** 524 * <P> 525 * Returns String available as parameter value in vCard 3.0. 526 * </P> 527 * <P> 528 * RFC 2426 requires vCard composer to quote parameter values when it contains 529 * semi-colon, for example (See RFC 2426 for more information). 530 * This method checks whether the given String can be used without quotes. 531 * </P> 532 * <P> 533 * Note: We remove DQUOTE silently for now. 534 * </P> 535 */ 536 public static String toStringAvailableAsV30ParamValue(String value) { 537 if (TextUtils.isEmpty(value)) { 538 value = ""; 539 } 540 final int asciiFirst = 0x20; 541 final int asciiLast = 0x7E; // included 542 final StringBuilder builder = new StringBuilder(); 543 final int length = value.length(); 544 boolean needQuote = false; 545 for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) { 546 final int codePoint = value.codePointAt(i); 547 if (codePoint < asciiFirst || codePoint == '"') { 548 // CTL characters and DQUOTE are never accepted. Remove them. 549 continue; 550 } 551 builder.appendCodePoint(codePoint); 552 if (codePoint == ':' || codePoint == ',' || codePoint == ' ') { 553 needQuote = true; 554 } 555 } 556 557 final String result = builder.toString(); 558 return ((result.isEmpty() || VCardUtils.containsOnlyWhiteSpaces(result)) 559 ? "" 560 : (needQuote ? ('"' + result + '"') 561 : result)); 562 } 563 564 public static String toHalfWidthString(final String orgString) { 565 if (TextUtils.isEmpty(orgString)) { 566 return null; 567 } 568 final StringBuilder builder = new StringBuilder(); 569 final int length = orgString.length(); 570 for (int i = 0; i < length; i = orgString.offsetByCodePoints(i, 1)) { 571 // All Japanese character is able to be expressed by char. 572 // Do not need to use String#codepPointAt(). 573 final char ch = orgString.charAt(i); 574 final String halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch); 575 if (halfWidthText != null) { 576 builder.append(halfWidthText); 577 } else { 578 builder.append(ch); 579 } 580 } 581 return builder.toString(); 582 } 583 584 /** 585 * Guesses the format of input image. Currently just the first few bytes are used. 586 * The type "GIF", "PNG", or "JPEG" is returned when possible. Returns null when 587 * the guess failed. 588 * @param input Image as byte array. 589 * @return The image type or null when the type cannot be determined. 590 */ 591 public static String guessImageType(final byte[] input) { 592 if (input == null) { 593 return null; 594 } 595 if (input.length >= 3 && input[0] == 'G' && input[1] == 'I' && input[2] == 'F') { 596 return "GIF"; 597 } else if (input.length >= 4 && input[0] == (byte) 0x89 598 && input[1] == 'P' && input[2] == 'N' && input[3] == 'G') { 599 // Note: vCard 2.1 officially does not support PNG, but we may have it and 600 // using X- word like "X-PNG" may not let importers know it is PNG. 601 // So we use the String "PNG" as is... 602 return "PNG"; 603 } else if (input.length >= 2 && input[0] == (byte) 0xff 604 && input[1] == (byte) 0xd8) { 605 return "JPEG"; 606 } else { 607 return null; 608 } 609 } 610 611 /** 612 * @return True when all the given values are null or empty Strings. 613 */ 614 public static boolean areAllEmpty(final String...values) { 615 if (values == null) { 616 return true; 617 } 618 619 for (final String value : values) { 620 if (!TextUtils.isEmpty(value)) { 621 return false; 622 } 623 } 624 return true; 625 } 626 627 //// The methods bellow may be used by unit test. 628 629 /** 630 * Unquotes given Quoted-Printable value. value must not be null. 631 */ 632 public static String parseQuotedPrintable( 633 final String value, boolean strictLineBreaking, 634 String sourceCharset, String targetCharset) { 635 // "= " -> " ", "=\t" -> "\t". 636 // Previous code had done this replacement. Keep on the safe side. 637 final String quotedPrintable; 638 { 639 final StringBuilder builder = new StringBuilder(); 640 final int length = value.length(); 641 for (int i = 0; i < length; i++) { 642 char ch = value.charAt(i); 643 if (ch == '=' && i < length - 1) { 644 char nextCh = value.charAt(i + 1); 645 if (nextCh == ' ' || nextCh == '\t') { 646 builder.append(nextCh); 647 i++; 648 continue; 649 } 650 } 651 builder.append(ch); 652 } 653 quotedPrintable = builder.toString(); 654 } 655 656 String[] lines; 657 if (strictLineBreaking) { 658 lines = quotedPrintable.split("\r\n"); 659 } else { 660 StringBuilder builder = new StringBuilder(); 661 final int length = quotedPrintable.length(); 662 ArrayList<String> list = new ArrayList<String>(); 663 for (int i = 0; i < length; i++) { 664 char ch = quotedPrintable.charAt(i); 665 if (ch == '\n') { 666 list.add(builder.toString()); 667 builder = new StringBuilder(); 668 } else if (ch == '\r') { 669 list.add(builder.toString()); 670 builder = new StringBuilder(); 671 if (i < length - 1) { 672 char nextCh = quotedPrintable.charAt(i + 1); 673 if (nextCh == '\n') { 674 i++; 675 } 676 } 677 } else { 678 builder.append(ch); 679 } 680 } 681 final String lastLine = builder.toString(); 682 if (lastLine.length() > 0) { 683 list.add(lastLine); 684 } 685 lines = list.toArray(new String[0]); 686 } 687 688 final StringBuilder builder = new StringBuilder(); 689 for (String line : lines) { 690 if (line.endsWith("=")) { 691 line = line.substring(0, line.length() - 1); 692 } 693 builder.append(line); 694 } 695 696 final String rawString = builder.toString(); 697 if (TextUtils.isEmpty(rawString)) { 698 Log.w(LOG_TAG, "Given raw string is empty."); 699 } 700 701 byte[] rawBytes = null; 702 try { 703 rawBytes = rawString.getBytes(sourceCharset); 704 } catch (UnsupportedEncodingException e) { 705 Log.w(LOG_TAG, "Failed to decode: " + sourceCharset); 706 rawBytes = rawString.getBytes(); 707 } 708 709 byte[] decodedBytes = null; 710 try { 711 decodedBytes = QuotedPrintableCodec.decodeQuotedPrintable(rawBytes); 712 } catch (DecoderException e) { 713 Log.e(LOG_TAG, "DecoderException is thrown."); 714 decodedBytes = rawBytes; 715 } 716 717 try { 718 return new String(decodedBytes, targetCharset); 719 } catch (UnsupportedEncodingException e) { 720 Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset); 721 return new String(decodedBytes); 722 } 723 } 724 725 public static final VCardParser getAppropriateParser(int vcardType) 726 throws VCardException { 727 if (VCardConfig.isVersion21(vcardType)) { 728 return new VCardParser_V21(); 729 } else if (VCardConfig.isVersion30(vcardType)) { 730 return new VCardParser_V30(); 731 } else if (VCardConfig.isVersion40(vcardType)) { 732 return new VCardParser_V40(); 733 } else { 734 throw new VCardException("Version is not specified"); 735 } 736 } 737 738 // TODO: utilities for vCard 4.0: datetime, timestamp, integer, float, and boolean 739 740 private VCardUtils() { 741 } 742} 743