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 */ 16 17 package com.android.vcard; 18 19 import com.android.vcard.VCardUtils.PhoneNumberUtilsPort; 20 21 import android.accounts.Account; 22 import android.content.ContentProviderOperation; 23 import android.content.ContentResolver; 24 import android.net.Uri; 25 import android.provider.ContactsContract; 26 import android.provider.ContactsContract.CommonDataKinds.Email; 27 import android.provider.ContactsContract.CommonDataKinds.Event; 28 import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 29 import android.provider.ContactsContract.CommonDataKinds.Im; 30 import android.provider.ContactsContract.CommonDataKinds.Nickname; 31 import android.provider.ContactsContract.CommonDataKinds.Note; 32 import android.provider.ContactsContract.CommonDataKinds.Organization; 33 import android.provider.ContactsContract.CommonDataKinds.Phone; 34 import android.provider.ContactsContract.CommonDataKinds.Photo; 35 import android.provider.ContactsContract.CommonDataKinds.SipAddress; 36 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 37 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 38 import android.provider.ContactsContract.CommonDataKinds.Website; 39 import android.provider.ContactsContract.Contacts; 40 import android.provider.ContactsContract.Data; 41 import android.provider.ContactsContract.RawContacts; 42 import android.telephony.PhoneNumberUtils; 43 import android.text.TextUtils; 44 import android.util.Log; 45 import android.util.Pair; 46 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.Collection; 50 import java.util.Collections; 51 import java.util.HashMap; 52 import java.util.List; 53 import java.util.Map; 54 55 /** 56 * Represents one vCard entry, which should start with "BEGIN:VCARD" and end 57 * with "END:VCARD". This class is for bridging between real vCard data and 58 * Android's {@link ContactsContract}, which means some aspects of vCard are 59 * dropped before this object being constructed. Raw vCard data should be first 60 * supplied with {@link #addProperty(VCardProperty)}. After supplying all data, 61 * user should call {@link #consolidateFields()} to prepare some additional 62 * information which is constructable from supplied raw data. TODO: preserve raw 63 * data using {@link VCardProperty}. If it may just waste memory, this at least 64 * should contain them when it cannot convert vCard as a string to Android's 65 * Contacts representation. Those raw properties should _not_ be used for 66 * {@link #isIgnorable()}. 67 */ 68 public class VCardEntry { 69 private static final String LOG_TAG = VCardConstants.LOG_TAG; 70 71 private static final int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK; 72 73 private static final Map<String, Integer> sImMap = new HashMap<String, Integer>(); 74 75 static { sImMap.put(VCardConstants.PROPERTY_X_AIM, Im.PROTOCOL_AIM)76 sImMap.put(VCardConstants.PROPERTY_X_AIM, Im.PROTOCOL_AIM); sImMap.put(VCardConstants.PROPERTY_X_MSN, Im.PROTOCOL_MSN)77 sImMap.put(VCardConstants.PROPERTY_X_MSN, Im.PROTOCOL_MSN); sImMap.put(VCardConstants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO)78 sImMap.put(VCardConstants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO); sImMap.put(VCardConstants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ)79 sImMap.put(VCardConstants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ); sImMap.put(VCardConstants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER)80 sImMap.put(VCardConstants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER); sImMap.put(VCardConstants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE)81 sImMap.put(VCardConstants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE); sImMap.put(VCardConstants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK)82 sImMap.put(VCardConstants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK); sImMap.put(VCardConstants.ImportOnly.PROPERTY_X_GOOGLE_TALK_WITH_SPACE, Im.PROTOCOL_GOOGLE_TALK)83 sImMap.put(VCardConstants.ImportOnly.PROPERTY_X_GOOGLE_TALK_WITH_SPACE, 84 Im.PROTOCOL_GOOGLE_TALK); 85 } 86 87 public enum EntryLabel { 88 NAME, 89 PHONE, 90 EMAIL, 91 POSTAL_ADDRESS, 92 ORGANIZATION, 93 IM, 94 PHOTO, 95 WEBSITE, 96 SIP, 97 NICKNAME, 98 NOTE, 99 BIRTHDAY, 100 ANNIVERSARY, 101 ANDROID_CUSTOM 102 } 103 104 public static interface EntryElement { 105 // Also need to inherit toString(), equals(). getEntryLabel()106 public EntryLabel getEntryLabel(); 107 constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)108 public void constructInsertOperation(List<ContentProviderOperation> operationList, 109 int backReferenceIndex); 110 isEmpty()111 public boolean isEmpty(); 112 } 113 114 // TODO: vCard 4.0 logically has multiple formatted names and we need to 115 // select the most preferable one using PREF parameter. 116 // 117 // e.g. (based on rev.13) 118 // FN;PREF=1:John M. Doe 119 // FN;PREF=2:John Doe 120 // FN;PREF=3;John 121 public static class NameData implements EntryElement { 122 private String mFamily; 123 private String mGiven; 124 private String mMiddle; 125 private String mPrefix; 126 private String mSuffix; 127 128 // Used only when no family nor given name is found. 129 private String mFormatted; 130 131 private String mPhoneticFamily; 132 private String mPhoneticGiven; 133 private String mPhoneticMiddle; 134 135 // For "SORT-STRING" in vCard 3.0. 136 private String mSortString; 137 138 /** 139 * Not in vCard but for {@link StructuredName#DISPLAY_NAME}. This field 140 * is constructed by VCardEntry on demand. Consider using 141 * {@link VCardEntry#getDisplayName()}. 142 */ 143 // This field should reflect the other Elem fields like Email, 144 // PostalAddress, etc., while 145 // This is static class which cannot see other data. Thus we ask 146 // VCardEntry to populate it. 147 public String displayName; 148 emptyStructuredName()149 public boolean emptyStructuredName() { 150 return TextUtils.isEmpty(mFamily) && TextUtils.isEmpty(mGiven) 151 && TextUtils.isEmpty(mMiddle) && TextUtils.isEmpty(mPrefix) 152 && TextUtils.isEmpty(mSuffix); 153 } 154 emptyPhoneticStructuredName()155 public boolean emptyPhoneticStructuredName() { 156 return TextUtils.isEmpty(mPhoneticFamily) && TextUtils.isEmpty(mPhoneticGiven) 157 && TextUtils.isEmpty(mPhoneticMiddle); 158 } 159 160 @Override constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)161 public void constructInsertOperation(List<ContentProviderOperation> operationList, 162 int backReferenceIndex) { 163 final ContentProviderOperation.Builder builder = ContentProviderOperation 164 .newInsert(Data.CONTENT_URI); 165 builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, backReferenceIndex); 166 builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); 167 168 if (!TextUtils.isEmpty(mGiven)) { 169 builder.withValue(StructuredName.GIVEN_NAME, mGiven); 170 } 171 if (!TextUtils.isEmpty(mFamily)) { 172 builder.withValue(StructuredName.FAMILY_NAME, mFamily); 173 } 174 if (!TextUtils.isEmpty(mMiddle)) { 175 builder.withValue(StructuredName.MIDDLE_NAME, mMiddle); 176 } 177 if (!TextUtils.isEmpty(mPrefix)) { 178 builder.withValue(StructuredName.PREFIX, mPrefix); 179 } 180 if (!TextUtils.isEmpty(mSuffix)) { 181 builder.withValue(StructuredName.SUFFIX, mSuffix); 182 } 183 184 boolean phoneticNameSpecified = false; 185 186 if (!TextUtils.isEmpty(mPhoneticGiven)) { 187 builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGiven); 188 phoneticNameSpecified = true; 189 } 190 if (!TextUtils.isEmpty(mPhoneticFamily)) { 191 builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamily); 192 phoneticNameSpecified = true; 193 } 194 if (!TextUtils.isEmpty(mPhoneticMiddle)) { 195 builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddle); 196 phoneticNameSpecified = true; 197 } 198 199 // SORT-STRING is used only when phonetic names aren't specified in 200 // the original vCard. 201 if (!phoneticNameSpecified) { 202 builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mSortString); 203 } 204 205 builder.withValue(StructuredName.DISPLAY_NAME, displayName); 206 operationList.add(builder.build()); 207 } 208 209 @Override isEmpty()210 public boolean isEmpty() { 211 return (TextUtils.isEmpty(mFamily) && TextUtils.isEmpty(mMiddle) 212 && TextUtils.isEmpty(mGiven) && TextUtils.isEmpty(mPrefix) 213 && TextUtils.isEmpty(mSuffix) && TextUtils.isEmpty(mFormatted) 214 && TextUtils.isEmpty(mPhoneticFamily) && TextUtils.isEmpty(mPhoneticMiddle) 215 && TextUtils.isEmpty(mPhoneticGiven) && TextUtils.isEmpty(mSortString)); 216 } 217 218 @Override equals(Object obj)219 public boolean equals(Object obj) { 220 if (this == obj) { 221 return true; 222 } 223 if (!(obj instanceof NameData)) { 224 return false; 225 } 226 NameData nameData = (NameData) obj; 227 228 return (TextUtils.equals(mFamily, nameData.mFamily) 229 && TextUtils.equals(mMiddle, nameData.mMiddle) 230 && TextUtils.equals(mGiven, nameData.mGiven) 231 && TextUtils.equals(mPrefix, nameData.mPrefix) 232 && TextUtils.equals(mSuffix, nameData.mSuffix) 233 && TextUtils.equals(mFormatted, nameData.mFormatted) 234 && TextUtils.equals(mPhoneticFamily, nameData.mPhoneticFamily) 235 && TextUtils.equals(mPhoneticMiddle, nameData.mPhoneticMiddle) 236 && TextUtils.equals(mPhoneticGiven, nameData.mPhoneticGiven) 237 && TextUtils.equals(mSortString, nameData.mSortString)); 238 } 239 240 @Override hashCode()241 public int hashCode() { 242 final String[] hashTargets = new String[] {mFamily, mMiddle, mGiven, mPrefix, mSuffix, 243 mFormatted, mPhoneticFamily, mPhoneticMiddle, 244 mPhoneticGiven, mSortString}; 245 int hash = 0; 246 for (String hashTarget : hashTargets) { 247 hash = hash * 31 + (hashTarget != null ? hashTarget.hashCode() : 0); 248 } 249 return hash; 250 } 251 252 @Override toString()253 public String toString() { 254 return String.format("family: %s, given: %s, middle: %s, prefix: %s, suffix: %s", 255 mFamily, mGiven, mMiddle, mPrefix, mSuffix); 256 } 257 258 @Override getEntryLabel()259 public final EntryLabel getEntryLabel() { 260 return EntryLabel.NAME; 261 } 262 getFamily()263 public String getFamily() { 264 return mFamily; 265 } 266 getMiddle()267 public String getMiddle() { 268 return mMiddle; 269 } 270 getGiven()271 public String getGiven() { 272 return mGiven; 273 } 274 getPrefix()275 public String getPrefix() { 276 return mPrefix; 277 } 278 getSuffix()279 public String getSuffix() { 280 return mSuffix; 281 } 282 getFormatted()283 public String getFormatted() { 284 return mFormatted; 285 } 286 getSortString()287 public String getSortString() { 288 return mSortString; 289 } 290 291 /** @hide Just for testing. */ setFamily(String family)292 public void setFamily(String family) { mFamily = family; } 293 /** @hide Just for testing. */ setMiddle(String middle)294 public void setMiddle(String middle) { mMiddle = middle; } 295 /** @hide Just for testing. */ setGiven(String given)296 public void setGiven(String given) { mGiven = given; } 297 /** @hide Just for testing. */ setPrefix(String prefix)298 public void setPrefix(String prefix) { mPrefix = prefix; } 299 /** @hide Just for testing. */ setSuffix(String suffix)300 public void setSuffix(String suffix) { mSuffix = suffix; } 301 } 302 303 public static class PhoneData implements EntryElement { 304 private final String mNumber; 305 private final int mType; 306 private final String mLabel; 307 308 // isPrimary is (not final but) changable, only when there's no 309 // appropriate one existing 310 // in the original VCard. 311 private boolean mIsPrimary; 312 PhoneData(String data, int type, String label, boolean isPrimary)313 public PhoneData(String data, int type, String label, boolean isPrimary) { 314 mNumber = data; 315 mType = type; 316 mLabel = label; 317 mIsPrimary = isPrimary; 318 } 319 320 @Override constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)321 public void constructInsertOperation(List<ContentProviderOperation> operationList, 322 int backReferenceIndex) { 323 final ContentProviderOperation.Builder builder = ContentProviderOperation 324 .newInsert(Data.CONTENT_URI); 325 builder.withValueBackReference(Phone.RAW_CONTACT_ID, backReferenceIndex); 326 builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 327 328 builder.withValue(Phone.TYPE, mType); 329 if (mType == Phone.TYPE_CUSTOM) { 330 builder.withValue(Phone.LABEL, mLabel); 331 } 332 builder.withValue(Phone.NUMBER, mNumber); 333 if (mIsPrimary) { 334 builder.withValue(Phone.IS_PRIMARY, 1); 335 } 336 operationList.add(builder.build()); 337 } 338 339 @Override isEmpty()340 public boolean isEmpty() { 341 return TextUtils.isEmpty(mNumber); 342 } 343 344 @Override equals(Object obj)345 public boolean equals(Object obj) { 346 if (this == obj) { 347 return true; 348 } 349 if (!(obj instanceof PhoneData)) { 350 return false; 351 } 352 PhoneData phoneData = (PhoneData) obj; 353 return (mType == phoneData.mType 354 && TextUtils.equals(mNumber, phoneData.mNumber) 355 && TextUtils.equals(mLabel, phoneData.mLabel) 356 && (mIsPrimary == phoneData.mIsPrimary)); 357 } 358 359 @Override hashCode()360 public int hashCode() { 361 int hash = mType; 362 hash = hash * 31 + (mNumber != null ? mNumber.hashCode() : 0); 363 hash = hash * 31 + (mLabel != null ? mLabel.hashCode() : 0); 364 hash = hash * 31 + (mIsPrimary ? 1231 : 1237); 365 return hash; 366 } 367 368 @Override toString()369 public String toString() { 370 return String.format("type: %d, data: %s, label: %s, isPrimary: %s", mType, mNumber, 371 mLabel, mIsPrimary); 372 } 373 374 @Override getEntryLabel()375 public final EntryLabel getEntryLabel() { 376 return EntryLabel.PHONE; 377 } 378 getNumber()379 public String getNumber() { 380 return mNumber; 381 } 382 getType()383 public int getType() { 384 return mType; 385 } 386 getLabel()387 public String getLabel() { 388 return mLabel; 389 } 390 isPrimary()391 public boolean isPrimary() { 392 return mIsPrimary; 393 } 394 } 395 396 public static class EmailData implements EntryElement { 397 private final String mAddress; 398 private final int mType; 399 // Used only when TYPE is TYPE_CUSTOM. 400 private final String mLabel; 401 private final boolean mIsPrimary; 402 EmailData(String data, int type, String label, boolean isPrimary)403 public EmailData(String data, int type, String label, boolean isPrimary) { 404 mType = type; 405 mAddress = data; 406 mLabel = label; 407 mIsPrimary = isPrimary; 408 } 409 410 @Override constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)411 public void constructInsertOperation(List<ContentProviderOperation> operationList, 412 int backReferenceIndex) { 413 final ContentProviderOperation.Builder builder = ContentProviderOperation 414 .newInsert(Data.CONTENT_URI); 415 builder.withValueBackReference(Email.RAW_CONTACT_ID, backReferenceIndex); 416 builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); 417 418 builder.withValue(Email.TYPE, mType); 419 if (mType == Email.TYPE_CUSTOM) { 420 builder.withValue(Email.LABEL, mLabel); 421 } 422 builder.withValue(Email.DATA, mAddress); 423 if (mIsPrimary) { 424 builder.withValue(Data.IS_PRIMARY, 1); 425 } 426 operationList.add(builder.build()); 427 } 428 429 @Override isEmpty()430 public boolean isEmpty() { 431 return TextUtils.isEmpty(mAddress); 432 } 433 434 @Override equals(Object obj)435 public boolean equals(Object obj) { 436 if (this == obj) { 437 return true; 438 } 439 if (!(obj instanceof EmailData)) { 440 return false; 441 } 442 EmailData emailData = (EmailData) obj; 443 return (mType == emailData.mType 444 && TextUtils.equals(mAddress, emailData.mAddress) 445 && TextUtils.equals(mLabel, emailData.mLabel) 446 && (mIsPrimary == emailData.mIsPrimary)); 447 } 448 449 @Override hashCode()450 public int hashCode() { 451 int hash = mType; 452 hash = hash * 31 + (mAddress != null ? mAddress.hashCode() : 0); 453 hash = hash * 31 + (mLabel != null ? mLabel.hashCode() : 0); 454 hash = hash * 31 + (mIsPrimary ? 1231 : 1237); 455 return hash; 456 } 457 458 @Override toString()459 public String toString() { 460 return String.format("type: %d, data: %s, label: %s, isPrimary: %s", mType, mAddress, 461 mLabel, mIsPrimary); 462 } 463 464 @Override getEntryLabel()465 public final EntryLabel getEntryLabel() { 466 return EntryLabel.EMAIL; 467 } 468 getAddress()469 public String getAddress() { 470 return mAddress; 471 } 472 getType()473 public int getType() { 474 return mType; 475 } 476 getLabel()477 public String getLabel() { 478 return mLabel; 479 } 480 isPrimary()481 public boolean isPrimary() { 482 return mIsPrimary; 483 } 484 } 485 486 public static class PostalData implements EntryElement { 487 // Determined by vCard specification. 488 // - PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name 489 private static final int ADDR_MAX_DATA_SIZE = 7; 490 private final String mPobox; 491 private final String mExtendedAddress; 492 private final String mStreet; 493 private final String mLocalty; 494 private final String mRegion; 495 private final String mPostalCode; 496 private final String mCountry; 497 private final int mType; 498 private final String mLabel; 499 private boolean mIsPrimary; 500 501 /** We keep this for {@link StructuredPostal#FORMATTED_ADDRESS} */ 502 // TODO: need better way to construct formatted address. 503 private int mVCardType; 504 PostalData(String pobox, String extendedAddress, String street, String localty, String region, String postalCode, String country, int type, String label, boolean isPrimary, int vcardType)505 public PostalData(String pobox, String extendedAddress, String street, String localty, 506 String region, String postalCode, String country, int type, String label, 507 boolean isPrimary, int vcardType) { 508 mType = type; 509 mPobox = pobox; 510 mExtendedAddress = extendedAddress; 511 mStreet = street; 512 mLocalty = localty; 513 mRegion = region; 514 mPostalCode = postalCode; 515 mCountry = country; 516 mLabel = label; 517 mIsPrimary = isPrimary; 518 mVCardType = vcardType; 519 } 520 521 /** 522 * Accepts raw propertyValueList in vCard and constructs PostalData. 523 */ constructPostalData(final List<String> propValueList, final int type, final String label, boolean isPrimary, int vcardType)524 public static PostalData constructPostalData(final List<String> propValueList, 525 final int type, final String label, boolean isPrimary, int vcardType) { 526 final String[] dataArray = new String[ADDR_MAX_DATA_SIZE]; 527 528 int size = propValueList.size(); 529 if (size > ADDR_MAX_DATA_SIZE) { 530 size = ADDR_MAX_DATA_SIZE; 531 } 532 533 // adr-value = 0*6(text-value ";") text-value 534 // ; PO Box, Extended Address, Street, Locality, Region, Postal Code, Country Name 535 // 536 // Use Iterator assuming List may be LinkedList, though actually it is 537 // always ArrayList in the current implementation. 538 int i = 0; 539 for (String addressElement : propValueList) { 540 dataArray[i] = addressElement; 541 if (++i >= size) { 542 break; 543 } 544 } 545 while (i < ADDR_MAX_DATA_SIZE) { 546 dataArray[i++] = null; 547 } 548 549 return new PostalData(dataArray[0], dataArray[1], dataArray[2], dataArray[3], 550 dataArray[4], dataArray[5], dataArray[6], type, label, isPrimary, vcardType); 551 } 552 553 @Override constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)554 public void constructInsertOperation(List<ContentProviderOperation> operationList, 555 int backReferenceIndex) { 556 final ContentProviderOperation.Builder builder = ContentProviderOperation 557 .newInsert(Data.CONTENT_URI); 558 builder.withValueBackReference(StructuredPostal.RAW_CONTACT_ID, backReferenceIndex); 559 builder.withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE); 560 561 builder.withValue(StructuredPostal.TYPE, mType); 562 if (mType == StructuredPostal.TYPE_CUSTOM) { 563 builder.withValue(StructuredPostal.LABEL, mLabel); 564 } 565 566 final String streetString; 567 if (TextUtils.isEmpty(mStreet)) { 568 if (TextUtils.isEmpty(mExtendedAddress)) { 569 streetString = null; 570 } else { 571 streetString = mExtendedAddress; 572 } 573 } else { 574 if (TextUtils.isEmpty(mExtendedAddress)) { 575 streetString = mStreet; 576 } else { 577 streetString = mStreet + " " + mExtendedAddress; 578 } 579 } 580 builder.withValue(StructuredPostal.POBOX, mPobox); 581 builder.withValue(StructuredPostal.STREET, streetString); 582 builder.withValue(StructuredPostal.CITY, mLocalty); 583 builder.withValue(StructuredPostal.REGION, mRegion); 584 builder.withValue(StructuredPostal.POSTCODE, mPostalCode); 585 builder.withValue(StructuredPostal.COUNTRY, mCountry); 586 587 builder.withValue(StructuredPostal.FORMATTED_ADDRESS, getFormattedAddress(mVCardType)); 588 if (mIsPrimary) { 589 builder.withValue(Data.IS_PRIMARY, 1); 590 } 591 operationList.add(builder.build()); 592 } 593 getFormattedAddress(final int vcardType)594 public String getFormattedAddress(final int vcardType) { 595 StringBuilder builder = new StringBuilder(); 596 boolean empty = true; 597 final String[] dataArray = new String[] { 598 mPobox, mExtendedAddress, mStreet, mLocalty, mRegion, mPostalCode, mCountry 599 }; 600 if (VCardConfig.isJapaneseDevice(vcardType)) { 601 // In Japan, the order is reversed. 602 for (int i = ADDR_MAX_DATA_SIZE - 1; i >= 0; i--) { 603 String addressPart = dataArray[i]; 604 if (!TextUtils.isEmpty(addressPart)) { 605 if (!empty) { 606 builder.append(' '); 607 } else { 608 empty = false; 609 } 610 builder.append(addressPart); 611 } 612 } 613 } else { 614 for (int i = 0; i < ADDR_MAX_DATA_SIZE; i++) { 615 String addressPart = dataArray[i]; 616 if (!TextUtils.isEmpty(addressPart)) { 617 if (!empty) { 618 builder.append(' '); 619 } else { 620 empty = false; 621 } 622 builder.append(addressPart); 623 } 624 } 625 } 626 627 return builder.toString().trim(); 628 } 629 630 @Override isEmpty()631 public boolean isEmpty() { 632 return (TextUtils.isEmpty(mPobox) 633 && TextUtils.isEmpty(mExtendedAddress) 634 && TextUtils.isEmpty(mStreet) 635 && TextUtils.isEmpty(mLocalty) 636 && TextUtils.isEmpty(mRegion) 637 && TextUtils.isEmpty(mPostalCode) 638 && TextUtils.isEmpty(mCountry)); 639 } 640 641 @Override equals(Object obj)642 public boolean equals(Object obj) { 643 if (this == obj) { 644 return true; 645 } 646 if (!(obj instanceof PostalData)) { 647 return false; 648 } 649 final PostalData postalData = (PostalData) obj; 650 return (mType == postalData.mType) 651 && (mType == StructuredPostal.TYPE_CUSTOM ? TextUtils.equals(mLabel, 652 postalData.mLabel) : true) 653 && (mIsPrimary == postalData.mIsPrimary) 654 && TextUtils.equals(mPobox, postalData.mPobox) 655 && TextUtils.equals(mExtendedAddress, postalData.mExtendedAddress) 656 && TextUtils.equals(mStreet, postalData.mStreet) 657 && TextUtils.equals(mLocalty, postalData.mLocalty) 658 && TextUtils.equals(mRegion, postalData.mRegion) 659 && TextUtils.equals(mPostalCode, postalData.mPostalCode) 660 && TextUtils.equals(mCountry, postalData.mCountry); 661 } 662 663 @Override hashCode()664 public int hashCode() { 665 int hash = mType; 666 hash = hash * 31 + (mLabel != null ? mLabel.hashCode() : 0); 667 hash = hash * 31 + (mIsPrimary ? 1231 : 1237); 668 669 final String[] hashTargets = new String[] {mPobox, mExtendedAddress, mStreet, 670 mLocalty, mRegion, mPostalCode, mCountry}; 671 for (String hashTarget : hashTargets) { 672 hash = hash * 31 + (hashTarget != null ? hashTarget.hashCode() : 0); 673 } 674 return hash; 675 } 676 677 @Override toString()678 public String toString() { 679 return String.format("type: %d, label: %s, isPrimary: %s, pobox: %s, " 680 + "extendedAddress: %s, street: %s, localty: %s, region: %s, postalCode %s, " 681 + "country: %s", mType, mLabel, mIsPrimary, mPobox, mExtendedAddress, mStreet, 682 mLocalty, mRegion, mPostalCode, mCountry); 683 } 684 685 @Override getEntryLabel()686 public final EntryLabel getEntryLabel() { 687 return EntryLabel.POSTAL_ADDRESS; 688 } 689 getPobox()690 public String getPobox() { 691 return mPobox; 692 } 693 getExtendedAddress()694 public String getExtendedAddress() { 695 return mExtendedAddress; 696 } 697 getStreet()698 public String getStreet() { 699 return mStreet; 700 } 701 getLocalty()702 public String getLocalty() { 703 return mLocalty; 704 } 705 getRegion()706 public String getRegion() { 707 return mRegion; 708 } 709 getPostalCode()710 public String getPostalCode() { 711 return mPostalCode; 712 } 713 getCountry()714 public String getCountry() { 715 return mCountry; 716 } 717 getType()718 public int getType() { 719 return mType; 720 } 721 getLabel()722 public String getLabel() { 723 return mLabel; 724 } 725 isPrimary()726 public boolean isPrimary() { 727 return mIsPrimary; 728 } 729 } 730 731 public static class OrganizationData implements EntryElement { 732 // non-final is Intentional: we may change the values since this info is separated into 733 // two parts in vCard: "ORG" + "TITLE", and we have to cope with each field in different 734 // timing. 735 private String mOrganizationName; 736 private String mDepartmentName; 737 private String mTitle; 738 private final String mPhoneticName; // We won't have this in "TITLE" property. 739 private final int mType; 740 private boolean mIsPrimary; 741 OrganizationData(final String organizationName, final String departmentName, final String titleName, final String phoneticName, int type, final boolean isPrimary)742 public OrganizationData(final String organizationName, final String departmentName, 743 final String titleName, final String phoneticName, int type, 744 final boolean isPrimary) { 745 mType = type; 746 mOrganizationName = organizationName; 747 mDepartmentName = departmentName; 748 mTitle = titleName; 749 mPhoneticName = phoneticName; 750 mIsPrimary = isPrimary; 751 } 752 getFormattedString()753 public String getFormattedString() { 754 final StringBuilder builder = new StringBuilder(); 755 if (!TextUtils.isEmpty(mOrganizationName)) { 756 builder.append(mOrganizationName); 757 } 758 759 if (!TextUtils.isEmpty(mDepartmentName)) { 760 if (builder.length() > 0) { 761 builder.append(", "); 762 } 763 builder.append(mDepartmentName); 764 } 765 766 if (!TextUtils.isEmpty(mTitle)) { 767 if (builder.length() > 0) { 768 builder.append(", "); 769 } 770 builder.append(mTitle); 771 } 772 773 return builder.toString(); 774 } 775 776 @Override constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)777 public void constructInsertOperation(List<ContentProviderOperation> operationList, 778 int backReferenceIndex) { 779 final ContentProviderOperation.Builder builder = ContentProviderOperation 780 .newInsert(Data.CONTENT_URI); 781 builder.withValueBackReference(Organization.RAW_CONTACT_ID, backReferenceIndex); 782 builder.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE); 783 builder.withValue(Organization.TYPE, mType); 784 if (mOrganizationName != null) { 785 builder.withValue(Organization.COMPANY, mOrganizationName); 786 } 787 if (mDepartmentName != null) { 788 builder.withValue(Organization.DEPARTMENT, mDepartmentName); 789 } 790 if (mTitle != null) { 791 builder.withValue(Organization.TITLE, mTitle); 792 } 793 if (mPhoneticName != null) { 794 builder.withValue(Organization.PHONETIC_NAME, mPhoneticName); 795 } 796 if (mIsPrimary) { 797 builder.withValue(Organization.IS_PRIMARY, 1); 798 } 799 operationList.add(builder.build()); 800 } 801 802 @Override isEmpty()803 public boolean isEmpty() { 804 return TextUtils.isEmpty(mOrganizationName) && TextUtils.isEmpty(mDepartmentName) 805 && TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mPhoneticName); 806 } 807 808 @Override equals(Object obj)809 public boolean equals(Object obj) { 810 if (this == obj) { 811 return true; 812 } 813 if (!(obj instanceof OrganizationData)) { 814 return false; 815 } 816 OrganizationData organization = (OrganizationData) obj; 817 return (mType == organization.mType 818 && TextUtils.equals(mOrganizationName, organization.mOrganizationName) 819 && TextUtils.equals(mDepartmentName, organization.mDepartmentName) 820 && TextUtils.equals(mTitle, organization.mTitle) 821 && (mIsPrimary == organization.mIsPrimary)); 822 } 823 824 @Override hashCode()825 public int hashCode() { 826 int hash = mType; 827 hash = hash * 31 + (mOrganizationName != null ? mOrganizationName.hashCode() : 0); 828 hash = hash * 31 + (mDepartmentName != null ? mDepartmentName.hashCode() : 0); 829 hash = hash * 31 + (mTitle != null ? mTitle.hashCode() : 0); 830 hash = hash * 31 + (mIsPrimary ? 1231 : 1237); 831 return hash; 832 } 833 834 @Override toString()835 public String toString() { 836 return String.format( 837 "type: %d, organization: %s, department: %s, title: %s, isPrimary: %s", mType, 838 mOrganizationName, mDepartmentName, mTitle, mIsPrimary); 839 } 840 841 @Override getEntryLabel()842 public final EntryLabel getEntryLabel() { 843 return EntryLabel.ORGANIZATION; 844 } 845 getOrganizationName()846 public String getOrganizationName() { 847 return mOrganizationName; 848 } 849 getDepartmentName()850 public String getDepartmentName() { 851 return mDepartmentName; 852 } 853 getTitle()854 public String getTitle() { 855 return mTitle; 856 } 857 getPhoneticName()858 public String getPhoneticName() { 859 return mPhoneticName; 860 } 861 getType()862 public int getType() { 863 return mType; 864 } 865 isPrimary()866 public boolean isPrimary() { 867 return mIsPrimary; 868 } 869 } 870 871 public static class ImData implements EntryElement { 872 private final String mAddress; 873 private final int mProtocol; 874 private final String mCustomProtocol; 875 private final int mType; 876 private final boolean mIsPrimary; 877 ImData(final int protocol, final String customProtocol, final String address, final int type, final boolean isPrimary)878 public ImData(final int protocol, final String customProtocol, final String address, 879 final int type, final boolean isPrimary) { 880 mProtocol = protocol; 881 mCustomProtocol = customProtocol; 882 mType = type; 883 mAddress = address; 884 mIsPrimary = isPrimary; 885 } 886 887 @Override constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)888 public void constructInsertOperation(List<ContentProviderOperation> operationList, 889 int backReferenceIndex) { 890 final ContentProviderOperation.Builder builder = ContentProviderOperation 891 .newInsert(Data.CONTENT_URI); 892 builder.withValueBackReference(Im.RAW_CONTACT_ID, backReferenceIndex); 893 builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE); 894 builder.withValue(Im.TYPE, mType); 895 builder.withValue(Im.PROTOCOL, mProtocol); 896 builder.withValue(Im.DATA, mAddress); 897 if (mProtocol == Im.PROTOCOL_CUSTOM) { 898 builder.withValue(Im.CUSTOM_PROTOCOL, mCustomProtocol); 899 } 900 if (mIsPrimary) { 901 builder.withValue(Data.IS_PRIMARY, 1); 902 } 903 operationList.add(builder.build()); 904 } 905 906 @Override isEmpty()907 public boolean isEmpty() { 908 return TextUtils.isEmpty(mAddress); 909 } 910 911 @Override equals(Object obj)912 public boolean equals(Object obj) { 913 if (this == obj) { 914 return true; 915 } 916 if (!(obj instanceof ImData)) { 917 return false; 918 } 919 ImData imData = (ImData) obj; 920 return (mType == imData.mType 921 && mProtocol == imData.mProtocol 922 && TextUtils.equals(mCustomProtocol, imData.mCustomProtocol) 923 && TextUtils.equals(mAddress, imData.mAddress) 924 && (mIsPrimary == imData.mIsPrimary)); 925 } 926 927 @Override hashCode()928 public int hashCode() { 929 int hash = mType; 930 hash = hash * 31 + mProtocol; 931 hash = hash * 31 + (mCustomProtocol != null ? mCustomProtocol.hashCode() : 0); 932 hash = hash * 31 + (mAddress != null ? mAddress.hashCode() : 0); 933 hash = hash * 31 + (mIsPrimary ? 1231 : 1237); 934 return hash; 935 } 936 937 @Override toString()938 public String toString() { 939 return String.format( 940 "type: %d, protocol: %d, custom_protcol: %s, data: %s, isPrimary: %s", mType, 941 mProtocol, mCustomProtocol, mAddress, mIsPrimary); 942 } 943 944 @Override getEntryLabel()945 public final EntryLabel getEntryLabel() { 946 return EntryLabel.IM; 947 } 948 getAddress()949 public String getAddress() { 950 return mAddress; 951 } 952 953 /** 954 * One of the value available for {@link Im#PROTOCOL}. e.g. 955 * {@link Im#PROTOCOL_GOOGLE_TALK} 956 */ getProtocol()957 public int getProtocol() { 958 return mProtocol; 959 } 960 getCustomProtocol()961 public String getCustomProtocol() { 962 return mCustomProtocol; 963 } 964 getType()965 public int getType() { 966 return mType; 967 } 968 isPrimary()969 public boolean isPrimary() { 970 return mIsPrimary; 971 } 972 } 973 974 public static class PhotoData implements EntryElement { 975 // private static final String FORMAT_FLASH = "SWF"; 976 977 // used when type is not defined in ContactsContract. 978 private final String mFormat; 979 private final boolean mIsPrimary; 980 981 private final byte[] mBytes; 982 983 private Integer mHashCode = null; 984 PhotoData(String format, byte[] photoBytes, boolean isPrimary)985 public PhotoData(String format, byte[] photoBytes, boolean isPrimary) { 986 mFormat = format; 987 mBytes = photoBytes; 988 mIsPrimary = isPrimary; 989 } 990 991 @Override constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)992 public void constructInsertOperation(List<ContentProviderOperation> operationList, 993 int backReferenceIndex) { 994 final ContentProviderOperation.Builder builder = ContentProviderOperation 995 .newInsert(Data.CONTENT_URI); 996 builder.withValueBackReference(Photo.RAW_CONTACT_ID, backReferenceIndex); 997 builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); 998 builder.withValue(Photo.PHOTO, mBytes); 999 if (mIsPrimary) { 1000 builder.withValue(Photo.IS_PRIMARY, 1); 1001 } 1002 operationList.add(builder.build()); 1003 } 1004 1005 @Override isEmpty()1006 public boolean isEmpty() { 1007 return mBytes == null || mBytes.length == 0; 1008 } 1009 1010 @Override equals(Object obj)1011 public boolean equals(Object obj) { 1012 if (this == obj) { 1013 return true; 1014 } 1015 if (!(obj instanceof PhotoData)) { 1016 return false; 1017 } 1018 PhotoData photoData = (PhotoData) obj; 1019 return (TextUtils.equals(mFormat, photoData.mFormat) 1020 && Arrays.equals(mBytes, photoData.mBytes) 1021 && (mIsPrimary == photoData.mIsPrimary)); 1022 } 1023 1024 @Override hashCode()1025 public int hashCode() { 1026 if (mHashCode != null) { 1027 return mHashCode; 1028 } 1029 1030 int hash = mFormat != null ? mFormat.hashCode() : 0; 1031 hash = hash * 31; 1032 if (mBytes != null) { 1033 for (byte b : mBytes) { 1034 hash += b; 1035 } 1036 } 1037 1038 hash = hash * 31 + (mIsPrimary ? 1231 : 1237); 1039 mHashCode = hash; 1040 return hash; 1041 } 1042 1043 @Override toString()1044 public String toString() { 1045 return String.format("format: %s: size: %d, isPrimary: %s", mFormat, mBytes.length, 1046 mIsPrimary); 1047 } 1048 1049 @Override getEntryLabel()1050 public final EntryLabel getEntryLabel() { 1051 return EntryLabel.PHOTO; 1052 } 1053 getFormat()1054 public String getFormat() { 1055 return mFormat; 1056 } 1057 getBytes()1058 public byte[] getBytes() { 1059 return mBytes; 1060 } 1061 isPrimary()1062 public boolean isPrimary() { 1063 return mIsPrimary; 1064 } 1065 } 1066 1067 public static class NicknameData implements EntryElement { 1068 private final String mNickname; 1069 NicknameData(String nickname)1070 public NicknameData(String nickname) { 1071 mNickname = nickname; 1072 } 1073 1074 @Override constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)1075 public void constructInsertOperation(List<ContentProviderOperation> operationList, 1076 int backReferenceIndex) { 1077 final ContentProviderOperation.Builder builder = ContentProviderOperation 1078 .newInsert(Data.CONTENT_URI); 1079 builder.withValueBackReference(Nickname.RAW_CONTACT_ID, backReferenceIndex); 1080 builder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE); 1081 builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT); 1082 builder.withValue(Nickname.NAME, mNickname); 1083 operationList.add(builder.build()); 1084 } 1085 1086 @Override isEmpty()1087 public boolean isEmpty() { 1088 return TextUtils.isEmpty(mNickname); 1089 } 1090 1091 @Override equals(Object obj)1092 public boolean equals(Object obj) { 1093 if (!(obj instanceof NicknameData)) { 1094 return false; 1095 } 1096 NicknameData nicknameData = (NicknameData) obj; 1097 return TextUtils.equals(mNickname, nicknameData.mNickname); 1098 } 1099 1100 @Override hashCode()1101 public int hashCode() { 1102 return mNickname != null ? mNickname.hashCode() : 0; 1103 } 1104 1105 @Override toString()1106 public String toString() { 1107 return "nickname: " + mNickname; 1108 } 1109 1110 @Override getEntryLabel()1111 public EntryLabel getEntryLabel() { 1112 return EntryLabel.NICKNAME; 1113 } 1114 getNickname()1115 public String getNickname() { 1116 return mNickname; 1117 } 1118 } 1119 1120 public static class NoteData implements EntryElement { 1121 public final String mNote; 1122 NoteData(String note)1123 public NoteData(String note) { 1124 mNote = note; 1125 } 1126 1127 @Override constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)1128 public void constructInsertOperation(List<ContentProviderOperation> operationList, 1129 int backReferenceIndex) { 1130 final ContentProviderOperation.Builder builder = ContentProviderOperation 1131 .newInsert(Data.CONTENT_URI); 1132 builder.withValueBackReference(Note.RAW_CONTACT_ID, backReferenceIndex); 1133 builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE); 1134 builder.withValue(Note.NOTE, mNote); 1135 operationList.add(builder.build()); 1136 } 1137 1138 @Override isEmpty()1139 public boolean isEmpty() { 1140 return TextUtils.isEmpty(mNote); 1141 } 1142 1143 @Override equals(Object obj)1144 public boolean equals(Object obj) { 1145 if (this == obj) { 1146 return true; 1147 } 1148 if (!(obj instanceof NoteData)) { 1149 return false; 1150 } 1151 NoteData noteData = (NoteData) obj; 1152 return TextUtils.equals(mNote, noteData.mNote); 1153 } 1154 1155 @Override hashCode()1156 public int hashCode() { 1157 return mNote != null ? mNote.hashCode() : 0; 1158 } 1159 1160 @Override toString()1161 public String toString() { 1162 return "note: " + mNote; 1163 } 1164 1165 @Override getEntryLabel()1166 public EntryLabel getEntryLabel() { 1167 return EntryLabel.NOTE; 1168 } 1169 getNote()1170 public String getNote() { 1171 return mNote; 1172 } 1173 } 1174 1175 public static class WebsiteData implements EntryElement { 1176 private final String mWebsite; 1177 WebsiteData(String website)1178 public WebsiteData(String website) { 1179 mWebsite = website; 1180 } 1181 1182 @Override constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)1183 public void constructInsertOperation(List<ContentProviderOperation> operationList, 1184 int backReferenceIndex) { 1185 final ContentProviderOperation.Builder builder = ContentProviderOperation 1186 .newInsert(Data.CONTENT_URI); 1187 builder.withValueBackReference(Website.RAW_CONTACT_ID, backReferenceIndex); 1188 builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE); 1189 builder.withValue(Website.URL, mWebsite); 1190 // There's no information about the type of URL in vCard. 1191 // We use TYPE_HOMEPAGE for safety. 1192 builder.withValue(Website.TYPE, Website.TYPE_HOMEPAGE); 1193 operationList.add(builder.build()); 1194 } 1195 1196 @Override isEmpty()1197 public boolean isEmpty() { 1198 return TextUtils.isEmpty(mWebsite); 1199 } 1200 1201 @Override equals(Object obj)1202 public boolean equals(Object obj) { 1203 if (this == obj) { 1204 return true; 1205 } 1206 if (!(obj instanceof WebsiteData)) { 1207 return false; 1208 } 1209 WebsiteData websiteData = (WebsiteData) obj; 1210 return TextUtils.equals(mWebsite, websiteData.mWebsite); 1211 } 1212 1213 @Override hashCode()1214 public int hashCode() { 1215 return mWebsite != null ? mWebsite.hashCode() : 0; 1216 } 1217 1218 @Override toString()1219 public String toString() { 1220 return "website: " + mWebsite; 1221 } 1222 1223 @Override getEntryLabel()1224 public EntryLabel getEntryLabel() { 1225 return EntryLabel.WEBSITE; 1226 } 1227 getWebsite()1228 public String getWebsite() { 1229 return mWebsite; 1230 } 1231 } 1232 1233 public static class BirthdayData implements EntryElement { 1234 private final String mBirthday; 1235 BirthdayData(String birthday)1236 public BirthdayData(String birthday) { 1237 mBirthday = birthday; 1238 } 1239 1240 @Override constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)1241 public void constructInsertOperation(List<ContentProviderOperation> operationList, 1242 int backReferenceIndex) { 1243 final ContentProviderOperation.Builder builder = ContentProviderOperation 1244 .newInsert(Data.CONTENT_URI); 1245 builder.withValueBackReference(Event.RAW_CONTACT_ID, backReferenceIndex); 1246 builder.withValue(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE); 1247 builder.withValue(Event.START_DATE, mBirthday); 1248 builder.withValue(Event.TYPE, Event.TYPE_BIRTHDAY); 1249 operationList.add(builder.build()); 1250 } 1251 1252 @Override isEmpty()1253 public boolean isEmpty() { 1254 return TextUtils.isEmpty(mBirthday); 1255 } 1256 1257 @Override equals(Object obj)1258 public boolean equals(Object obj) { 1259 if (this == obj) { 1260 return true; 1261 } 1262 if (!(obj instanceof BirthdayData)) { 1263 return false; 1264 } 1265 BirthdayData birthdayData = (BirthdayData) obj; 1266 return TextUtils.equals(mBirthday, birthdayData.mBirthday); 1267 } 1268 1269 @Override hashCode()1270 public int hashCode() { 1271 return mBirthday != null ? mBirthday.hashCode() : 0; 1272 } 1273 1274 @Override toString()1275 public String toString() { 1276 return "birthday: " + mBirthday; 1277 } 1278 1279 @Override getEntryLabel()1280 public EntryLabel getEntryLabel() { 1281 return EntryLabel.BIRTHDAY; 1282 } 1283 getBirthday()1284 public String getBirthday() { 1285 return mBirthday; 1286 } 1287 } 1288 1289 public static class AnniversaryData implements EntryElement { 1290 private final String mAnniversary; 1291 AnniversaryData(String anniversary)1292 public AnniversaryData(String anniversary) { 1293 mAnniversary = anniversary; 1294 } 1295 1296 @Override constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)1297 public void constructInsertOperation(List<ContentProviderOperation> operationList, 1298 int backReferenceIndex) { 1299 final ContentProviderOperation.Builder builder = ContentProviderOperation 1300 .newInsert(Data.CONTENT_URI); 1301 builder.withValueBackReference(Event.RAW_CONTACT_ID, backReferenceIndex); 1302 builder.withValue(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE); 1303 builder.withValue(Event.START_DATE, mAnniversary); 1304 builder.withValue(Event.TYPE, Event.TYPE_ANNIVERSARY); 1305 operationList.add(builder.build()); 1306 } 1307 1308 @Override isEmpty()1309 public boolean isEmpty() { 1310 return TextUtils.isEmpty(mAnniversary); 1311 } 1312 1313 @Override equals(Object obj)1314 public boolean equals(Object obj) { 1315 if (this == obj) { 1316 return true; 1317 } 1318 if (!(obj instanceof AnniversaryData)) { 1319 return false; 1320 } 1321 AnniversaryData anniversaryData = (AnniversaryData) obj; 1322 return TextUtils.equals(mAnniversary, anniversaryData.mAnniversary); 1323 } 1324 1325 @Override hashCode()1326 public int hashCode() { 1327 return mAnniversary != null ? mAnniversary.hashCode() : 0; 1328 } 1329 1330 @Override toString()1331 public String toString() { 1332 return "anniversary: " + mAnniversary; 1333 } 1334 1335 @Override getEntryLabel()1336 public EntryLabel getEntryLabel() { 1337 return EntryLabel.ANNIVERSARY; 1338 } 1339 getAnniversary()1340 public String getAnniversary() { return mAnniversary; } 1341 } 1342 1343 public static class SipData implements EntryElement { 1344 /** 1345 * Note that schema part ("sip:") is automatically removed. e.g. 1346 * "sip:username:password@host:port" becomes 1347 * "username:password@host:port" 1348 */ 1349 private final String mAddress; 1350 private final int mType; 1351 private final String mLabel; 1352 private final boolean mIsPrimary; 1353 SipData(String rawSip, int type, String label, boolean isPrimary)1354 public SipData(String rawSip, int type, String label, boolean isPrimary) { 1355 if (rawSip.startsWith("sip:")) { 1356 mAddress = rawSip.substring(4); 1357 } else { 1358 mAddress = rawSip; 1359 } 1360 mType = type; 1361 mLabel = label; 1362 mIsPrimary = isPrimary; 1363 } 1364 1365 @Override constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)1366 public void constructInsertOperation(List<ContentProviderOperation> operationList, 1367 int backReferenceIndex) { 1368 final ContentProviderOperation.Builder builder = ContentProviderOperation 1369 .newInsert(Data.CONTENT_URI); 1370 builder.withValueBackReference(SipAddress.RAW_CONTACT_ID, backReferenceIndex); 1371 builder.withValue(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE); 1372 builder.withValue(SipAddress.SIP_ADDRESS, mAddress); 1373 builder.withValue(SipAddress.TYPE, mType); 1374 if (mType == SipAddress.TYPE_CUSTOM) { 1375 builder.withValue(SipAddress.LABEL, mLabel); 1376 } 1377 if (mIsPrimary) { 1378 builder.withValue(SipAddress.IS_PRIMARY, mIsPrimary); 1379 } 1380 operationList.add(builder.build()); 1381 } 1382 1383 @Override isEmpty()1384 public boolean isEmpty() { 1385 return TextUtils.isEmpty(mAddress); 1386 } 1387 1388 @Override equals(Object obj)1389 public boolean equals(Object obj) { 1390 if (this == obj) { 1391 return true; 1392 } 1393 if (!(obj instanceof SipData)) { 1394 return false; 1395 } 1396 SipData sipData = (SipData) obj; 1397 return (mType == sipData.mType 1398 && TextUtils.equals(mLabel, sipData.mLabel) 1399 && TextUtils.equals(mAddress, sipData.mAddress) 1400 && (mIsPrimary == sipData.mIsPrimary)); 1401 } 1402 1403 @Override hashCode()1404 public int hashCode() { 1405 int hash = mType; 1406 hash = hash * 31 + (mLabel != null ? mLabel.hashCode() : 0); 1407 hash = hash * 31 + (mAddress != null ? mAddress.hashCode() : 0); 1408 hash = hash * 31 + (mIsPrimary ? 1231 : 1237); 1409 return hash; 1410 } 1411 1412 @Override toString()1413 public String toString() { 1414 return "sip: " + mAddress; 1415 } 1416 1417 @Override getEntryLabel()1418 public EntryLabel getEntryLabel() { 1419 return EntryLabel.SIP; 1420 } 1421 1422 /** 1423 * @return Address part of the sip data. The schema ("sip:") isn't contained here. 1424 */ getAddress()1425 public String getAddress() { return mAddress; } getType()1426 public int getType() { return mType; } getLabel()1427 public String getLabel() { return mLabel; } 1428 } 1429 1430 /** 1431 * Some Contacts data in Android cannot be converted to vCard 1432 * representation. VCardEntry preserves those data using this class. 1433 */ 1434 public static class AndroidCustomData implements EntryElement { 1435 private final String mMimeType; 1436 1437 private final List<String> mDataList; // 1 .. VCardConstants.MAX_DATA_COLUMN 1438 AndroidCustomData(String mimeType, List<String> dataList)1439 public AndroidCustomData(String mimeType, List<String> dataList) { 1440 mMimeType = mimeType; 1441 mDataList = dataList; 1442 } 1443 constructAndroidCustomData(List<String> list)1444 public static AndroidCustomData constructAndroidCustomData(List<String> list) { 1445 String mimeType; 1446 List<String> dataList; 1447 1448 if (list == null) { 1449 mimeType = null; 1450 dataList = null; 1451 } else if (list.size() < 2) { 1452 mimeType = list.get(0); 1453 dataList = null; 1454 } else { 1455 final int max = (list.size() < VCardConstants.MAX_DATA_COLUMN + 1) ? list.size() 1456 : VCardConstants.MAX_DATA_COLUMN + 1; 1457 mimeType = list.get(0); 1458 dataList = list.subList(1, max); 1459 } 1460 1461 return new AndroidCustomData(mimeType, dataList); 1462 } 1463 1464 @Override constructInsertOperation(List<ContentProviderOperation> operationList, int backReferenceIndex)1465 public void constructInsertOperation(List<ContentProviderOperation> operationList, 1466 int backReferenceIndex) { 1467 final ContentProviderOperation.Builder builder = ContentProviderOperation 1468 .newInsert(Data.CONTENT_URI); 1469 builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, backReferenceIndex); 1470 builder.withValue(Data.MIMETYPE, mMimeType); 1471 for (int i = 0; i < mDataList.size(); i++) { 1472 String value = mDataList.get(i); 1473 if (!TextUtils.isEmpty(value)) { 1474 // 1-origin 1475 builder.withValue("data" + (i + 1), value); 1476 } 1477 } 1478 operationList.add(builder.build()); 1479 } 1480 1481 @Override isEmpty()1482 public boolean isEmpty() { 1483 return TextUtils.isEmpty(mMimeType) || mDataList == null || mDataList.size() == 0; 1484 } 1485 1486 @Override equals(Object obj)1487 public boolean equals(Object obj) { 1488 if (this == obj) { 1489 return true; 1490 } 1491 if (!(obj instanceof AndroidCustomData)) { 1492 return false; 1493 } 1494 AndroidCustomData data = (AndroidCustomData) obj; 1495 if (!TextUtils.equals(mMimeType, data.mMimeType)) { 1496 return false; 1497 } 1498 if (mDataList == null) { 1499 return data.mDataList == null; 1500 } else { 1501 final int size = mDataList.size(); 1502 if (size != data.mDataList.size()) { 1503 return false; 1504 } 1505 for (int i = 0; i < size; i++) { 1506 if (!TextUtils.equals(mDataList.get(i), data.mDataList.get(i))) { 1507 return false; 1508 } 1509 } 1510 return true; 1511 } 1512 } 1513 1514 @Override hashCode()1515 public int hashCode() { 1516 int hash = mMimeType != null ? mMimeType.hashCode() : 0; 1517 if (mDataList != null) { 1518 for (String data : mDataList) { 1519 hash = hash * 31 + (data != null ? data.hashCode() : 0); 1520 } 1521 } 1522 return hash; 1523 } 1524 1525 @Override toString()1526 public String toString() { 1527 final StringBuilder builder = new StringBuilder(); 1528 builder.append("android-custom: " + mMimeType + ", data: "); 1529 builder.append(mDataList == null ? "null" : Arrays.toString(mDataList.toArray())); 1530 return builder.toString(); 1531 } 1532 1533 @Override getEntryLabel()1534 public EntryLabel getEntryLabel() { 1535 return EntryLabel.ANDROID_CUSTOM; 1536 } 1537 getMimeType()1538 public String getMimeType() { return mMimeType; } getDataList()1539 public List<String> getDataList() { return mDataList; } 1540 } 1541 1542 private final NameData mNameData = new NameData(); 1543 private List<PhoneData> mPhoneList; 1544 private List<EmailData> mEmailList; 1545 private List<PostalData> mPostalList; 1546 private List<OrganizationData> mOrganizationList; 1547 private List<ImData> mImList; 1548 private List<PhotoData> mPhotoList; 1549 private List<WebsiteData> mWebsiteList; 1550 private List<SipData> mSipList; 1551 private List<NicknameData> mNicknameList; 1552 private List<NoteData> mNoteList; 1553 private List<AndroidCustomData> mAndroidCustomDataList; 1554 private BirthdayData mBirthday; 1555 private AnniversaryData mAnniversary; 1556 private List<Pair<String, String>> mUnknownXData; 1557 1558 /** 1559 * Inner iterator interface. 1560 */ 1561 public interface EntryElementIterator { onIterationStarted()1562 public void onIterationStarted(); 1563 onIterationEnded()1564 public void onIterationEnded(); 1565 1566 /** 1567 * Called when there are one or more {@link EntryElement} instances 1568 * associated with {@link EntryLabel}. 1569 */ onElementGroupStarted(EntryLabel label)1570 public void onElementGroupStarted(EntryLabel label); 1571 1572 /** 1573 * Called after all {@link EntryElement} instances for 1574 * {@link EntryLabel} provided on {@link #onElementGroupStarted(EntryLabel)} 1575 * being processed by {@link #onElement(EntryElement)} 1576 */ onElementGroupEnded()1577 public void onElementGroupEnded(); 1578 1579 /** 1580 * @return should be true when child wants to continue the operation. 1581 * False otherwise. 1582 */ onElement(EntryElement elem)1583 public boolean onElement(EntryElement elem); 1584 } 1585 iterateAllData(EntryElementIterator iterator)1586 public final void iterateAllData(EntryElementIterator iterator) { 1587 iterator.onIterationStarted(); 1588 iterator.onElementGroupStarted(mNameData.getEntryLabel()); 1589 iterator.onElement(mNameData); 1590 iterator.onElementGroupEnded(); 1591 1592 iterateOneList(mPhoneList, iterator); 1593 iterateOneList(mEmailList, iterator); 1594 iterateOneList(mPostalList, iterator); 1595 iterateOneList(mOrganizationList, iterator); 1596 iterateOneList(mImList, iterator); 1597 iterateOneList(mPhotoList, iterator); 1598 iterateOneList(mWebsiteList, iterator); 1599 iterateOneList(mSipList, iterator); 1600 iterateOneList(mNicknameList, iterator); 1601 iterateOneList(mNoteList, iterator); 1602 iterateOneList(mAndroidCustomDataList, iterator); 1603 1604 if (mBirthday != null) { 1605 iterator.onElementGroupStarted(mBirthday.getEntryLabel()); 1606 iterator.onElement(mBirthday); 1607 iterator.onElementGroupEnded(); 1608 } 1609 if (mAnniversary != null) { 1610 iterator.onElementGroupStarted(mAnniversary.getEntryLabel()); 1611 iterator.onElement(mAnniversary); 1612 iterator.onElementGroupEnded(); 1613 } 1614 iterator.onIterationEnded(); 1615 } 1616 iterateOneList(List<? extends EntryElement> elemList, EntryElementIterator iterator)1617 private void iterateOneList(List<? extends EntryElement> elemList, 1618 EntryElementIterator iterator) { 1619 if (elemList != null && elemList.size() > 0) { 1620 iterator.onElementGroupStarted(elemList.get(0).getEntryLabel()); 1621 for (EntryElement elem : elemList) { 1622 iterator.onElement(elem); 1623 } 1624 iterator.onElementGroupEnded(); 1625 } 1626 } 1627 1628 private class IsIgnorableIterator implements EntryElementIterator { 1629 private boolean mEmpty = true; 1630 1631 @Override onIterationStarted()1632 public void onIterationStarted() { 1633 } 1634 1635 @Override onIterationEnded()1636 public void onIterationEnded() { 1637 } 1638 1639 @Override onElementGroupStarted(EntryLabel label)1640 public void onElementGroupStarted(EntryLabel label) { 1641 } 1642 1643 @Override onElementGroupEnded()1644 public void onElementGroupEnded() { 1645 } 1646 1647 @Override onElement(EntryElement elem)1648 public boolean onElement(EntryElement elem) { 1649 if (!elem.isEmpty()) { 1650 mEmpty = false; 1651 // exit now 1652 return false; 1653 } else { 1654 return true; 1655 } 1656 } 1657 getResult()1658 public boolean getResult() { 1659 return mEmpty; 1660 } 1661 } 1662 1663 private class ToStringIterator implements EntryElementIterator { 1664 private StringBuilder mBuilder; 1665 1666 private boolean mFirstElement; 1667 1668 @Override onIterationStarted()1669 public void onIterationStarted() { 1670 mBuilder = new StringBuilder(); 1671 mBuilder.append("[[hash: " + VCardEntry.this.hashCode() + "\n"); 1672 } 1673 1674 @Override onElementGroupStarted(EntryLabel label)1675 public void onElementGroupStarted(EntryLabel label) { 1676 mBuilder.append(label.toString() + ": "); 1677 mFirstElement = true; 1678 } 1679 1680 @Override onElement(EntryElement elem)1681 public boolean onElement(EntryElement elem) { 1682 if (!mFirstElement) { 1683 mBuilder.append(", "); 1684 mFirstElement = false; 1685 } 1686 mBuilder.append("[").append(elem.toString()).append("]"); 1687 return true; 1688 } 1689 1690 @Override onElementGroupEnded()1691 public void onElementGroupEnded() { 1692 mBuilder.append("\n"); 1693 } 1694 1695 @Override onIterationEnded()1696 public void onIterationEnded() { 1697 mBuilder.append("]]\n"); 1698 } 1699 1700 @Override toString()1701 public String toString() { 1702 return mBuilder.toString(); 1703 } 1704 } 1705 1706 private class InsertOperationConstrutor implements EntryElementIterator { 1707 private final List<ContentProviderOperation> mOperationList; 1708 1709 private final int mBackReferenceIndex; 1710 InsertOperationConstrutor(List<ContentProviderOperation> operationList, int backReferenceIndex)1711 public InsertOperationConstrutor(List<ContentProviderOperation> operationList, 1712 int backReferenceIndex) { 1713 mOperationList = operationList; 1714 mBackReferenceIndex = backReferenceIndex; 1715 } 1716 1717 @Override onIterationStarted()1718 public void onIterationStarted() { 1719 } 1720 1721 @Override onIterationEnded()1722 public void onIterationEnded() { 1723 } 1724 1725 @Override onElementGroupStarted(EntryLabel label)1726 public void onElementGroupStarted(EntryLabel label) { 1727 } 1728 1729 @Override onElementGroupEnded()1730 public void onElementGroupEnded() { 1731 } 1732 1733 @Override onElement(EntryElement elem)1734 public boolean onElement(EntryElement elem) { 1735 if (!elem.isEmpty()) { 1736 elem.constructInsertOperation(mOperationList, mBackReferenceIndex); 1737 } 1738 return true; 1739 } 1740 } 1741 1742 private final int mVCardType; 1743 private final Account mAccount; 1744 1745 private List<VCardEntry> mChildren; 1746 1747 @Override toString()1748 public String toString() { 1749 ToStringIterator iterator = new ToStringIterator(); 1750 iterateAllData(iterator); 1751 return iterator.toString(); 1752 } 1753 VCardEntry()1754 public VCardEntry() { 1755 this(VCardConfig.VCARD_TYPE_V21_GENERIC); 1756 } 1757 VCardEntry(int vcardType)1758 public VCardEntry(int vcardType) { 1759 this(vcardType, null); 1760 } 1761 VCardEntry(int vcardType, Account account)1762 public VCardEntry(int vcardType, Account account) { 1763 mVCardType = vcardType; 1764 mAccount = account; 1765 } 1766 addPhone(int type, String data, String label, boolean isPrimary)1767 private void addPhone(int type, String data, String label, boolean isPrimary) { 1768 if (mPhoneList == null) { 1769 mPhoneList = new ArrayList<PhoneData>(); 1770 } 1771 final StringBuilder builder = new StringBuilder(); 1772 final String trimmed = data.trim(); 1773 final String formattedNumber; 1774 if (type == Phone.TYPE_PAGER || VCardConfig.refrainPhoneNumberFormatting(mVCardType)) { 1775 formattedNumber = trimmed; 1776 } else { 1777 // TODO: from the view of vCard spec these auto conversions should be removed. 1778 // Note that some other codes (like the phone number formatter) or modules expect this 1779 // auto conversion (bug 5178723), so just omitting this code won't be preferable enough 1780 // (bug 4177894) 1781 boolean hasPauseOrWait = false; 1782 final int length = trimmed.length(); 1783 for (int i = 0; i < length; i++) { 1784 char ch = trimmed.charAt(i); 1785 // See RFC 3601 and docs for PhoneNumberUtils for more info. 1786 if (ch == 'p' || ch == 'P') { 1787 builder.append(PhoneNumberUtils.PAUSE); 1788 hasPauseOrWait = true; 1789 } else if (ch == 'w' || ch == 'W') { 1790 builder.append(PhoneNumberUtils.WAIT); 1791 hasPauseOrWait = true; 1792 } else if (('0' <= ch && ch <= '9') || (i == 0 && ch == '+')) { 1793 builder.append(ch); 1794 } 1795 } 1796 if (!hasPauseOrWait) { 1797 final int formattingType = VCardUtils.getPhoneNumberFormat(mVCardType); 1798 formattedNumber = PhoneNumberUtilsPort.formatNumber( 1799 builder.toString(), formattingType); 1800 } else { 1801 formattedNumber = builder.toString(); 1802 } 1803 } 1804 PhoneData phoneData = new PhoneData(formattedNumber, type, label, isPrimary); 1805 mPhoneList.add(phoneData); 1806 } 1807 addSip(String sipData, int type, String label, boolean isPrimary)1808 private void addSip(String sipData, int type, String label, boolean isPrimary) { 1809 if (mSipList == null) { 1810 mSipList = new ArrayList<SipData>(); 1811 } 1812 mSipList.add(new SipData(sipData, type, label, isPrimary)); 1813 } 1814 addNickName(final String nickName)1815 private void addNickName(final String nickName) { 1816 if (mNicknameList == null) { 1817 mNicknameList = new ArrayList<NicknameData>(); 1818 } 1819 mNicknameList.add(new NicknameData(nickName)); 1820 } 1821 addEmail(int type, String data, String label, boolean isPrimary)1822 private void addEmail(int type, String data, String label, boolean isPrimary) { 1823 if (mEmailList == null) { 1824 mEmailList = new ArrayList<EmailData>(); 1825 } 1826 mEmailList.add(new EmailData(data, type, label, isPrimary)); 1827 } 1828 addPostal(int type, List<String> propValueList, String label, boolean isPrimary)1829 private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary) { 1830 if (mPostalList == null) { 1831 mPostalList = new ArrayList<PostalData>(0); 1832 } 1833 mPostalList.add(PostalData.constructPostalData(propValueList, type, label, isPrimary, 1834 mVCardType)); 1835 } 1836 1837 /** 1838 * Should be called via {@link #handleOrgValue(int, List, Map, boolean)} or 1839 * {@link #handleTitleValue(String)}. 1840 */ addNewOrganization(final String organizationName, final String departmentName, final String titleName, final String phoneticName, int type, final boolean isPrimary)1841 private void addNewOrganization(final String organizationName, final String departmentName, 1842 final String titleName, final String phoneticName, int type, final boolean isPrimary) { 1843 if (mOrganizationList == null) { 1844 mOrganizationList = new ArrayList<OrganizationData>(); 1845 } 1846 mOrganizationList.add(new OrganizationData(organizationName, departmentName, titleName, 1847 phoneticName, type, isPrimary)); 1848 } 1849 1850 private static final List<String> sEmptyList = Collections 1851 .unmodifiableList(new ArrayList<String>(0)); 1852 buildSinglePhoneticNameFromSortAsParam(Map<String, Collection<String>> paramMap)1853 private String buildSinglePhoneticNameFromSortAsParam(Map<String, Collection<String>> paramMap) { 1854 final Collection<String> sortAsCollection = paramMap.get(VCardConstants.PARAM_SORT_AS); 1855 if (sortAsCollection != null && sortAsCollection.size() != 0) { 1856 if (sortAsCollection.size() > 1) { 1857 Log.w(LOG_TAG, 1858 "Incorrect multiple SORT_AS parameters detected: " 1859 + Arrays.toString(sortAsCollection.toArray())); 1860 } 1861 final List<String> sortNames = VCardUtils.constructListFromValue(sortAsCollection 1862 .iterator().next(), mVCardType); 1863 final StringBuilder builder = new StringBuilder(); 1864 for (final String elem : sortNames) { 1865 builder.append(elem); 1866 } 1867 return builder.toString(); 1868 } else { 1869 return null; 1870 } 1871 } 1872 1873 /** 1874 * Set "ORG" related values to the appropriate data. If there's more than 1875 * one {@link OrganizationData} objects, this input data are attached to the 1876 * last one which does not have valid values (not including empty but only 1877 * null). If there's no {@link OrganizationData} object, a new 1878 * {@link OrganizationData} is created, whose title is set to null. 1879 */ handleOrgValue(final int type, List<String> orgList, Map<String, Collection<String>> paramMap, boolean isPrimary)1880 private void handleOrgValue(final int type, List<String> orgList, 1881 Map<String, Collection<String>> paramMap, boolean isPrimary) { 1882 final String phoneticName = buildSinglePhoneticNameFromSortAsParam(paramMap); 1883 if (orgList == null) { 1884 orgList = sEmptyList; 1885 } 1886 final String organizationName; 1887 final String departmentName; 1888 final int size = orgList.size(); 1889 switch (size) { 1890 case 0: { 1891 organizationName = ""; 1892 departmentName = null; 1893 break; 1894 } 1895 case 1: { 1896 organizationName = orgList.get(0); 1897 departmentName = null; 1898 break; 1899 } 1900 default: { // More than 1. 1901 organizationName = orgList.get(0); 1902 // We're not sure which is the correct string for department. 1903 // In order to keep all the data, concatinate the rest of elements. 1904 StringBuilder builder = new StringBuilder(); 1905 for (int i = 1; i < size; i++) { 1906 if (i > 1) { 1907 builder.append(' '); 1908 } 1909 builder.append(orgList.get(i)); 1910 } 1911 departmentName = builder.toString(); 1912 } 1913 } 1914 if (mOrganizationList == null) { 1915 // Create new first organization entry, with "null" title which may be 1916 // added via handleTitleValue(). 1917 addNewOrganization(organizationName, departmentName, null, phoneticName, type, 1918 isPrimary); 1919 return; 1920 } 1921 for (OrganizationData organizationData : mOrganizationList) { 1922 // Not use TextUtils.isEmpty() since ORG was set but the elements might be empty. 1923 // e.g. "ORG;PREF:;" -> Both companyName and departmentName become empty but not null. 1924 if (organizationData.mOrganizationName == null 1925 && organizationData.mDepartmentName == null) { 1926 // Probably the "TITLE" property comes before the "ORG" property via 1927 // handleTitleLine(). 1928 organizationData.mOrganizationName = organizationName; 1929 organizationData.mDepartmentName = departmentName; 1930 organizationData.mIsPrimary = isPrimary; 1931 return; 1932 } 1933 } 1934 // No OrganizatioData is available. Create another one, with "null" title, which may be 1935 // added via handleTitleValue(). 1936 addNewOrganization(organizationName, departmentName, null, phoneticName, type, isPrimary); 1937 } 1938 1939 /** 1940 * Set "title" value to the appropriate data. If there's more than one 1941 * OrganizationData objects, this input is attached to the last one which 1942 * does not have valid title value (not including empty but only null). If 1943 * there's no OrganizationData object, a new OrganizationData is created, 1944 * whose company name is set to null. 1945 */ handleTitleValue(final String title)1946 private void handleTitleValue(final String title) { 1947 if (mOrganizationList == null) { 1948 // Create new first organization entry, with "null" other info, which may be 1949 // added via handleOrgValue(). 1950 addNewOrganization(null, null, title, null, DEFAULT_ORGANIZATION_TYPE, false); 1951 return; 1952 } 1953 for (OrganizationData organizationData : mOrganizationList) { 1954 if (organizationData.mTitle == null) { 1955 organizationData.mTitle = title; 1956 return; 1957 } 1958 } 1959 // No Organization is available. Create another one, with "null" other info, which may be 1960 // added via handleOrgValue(). 1961 addNewOrganization(null, null, title, null, DEFAULT_ORGANIZATION_TYPE, false); 1962 } 1963 addIm(int protocol, String customProtocol, String propValue, int type, boolean isPrimary)1964 private void addIm(int protocol, String customProtocol, String propValue, int type, 1965 boolean isPrimary) { 1966 if (mImList == null) { 1967 mImList = new ArrayList<ImData>(); 1968 } 1969 mImList.add(new ImData(protocol, customProtocol, propValue, type, isPrimary)); 1970 } 1971 addNote(final String note)1972 private void addNote(final String note) { 1973 if (mNoteList == null) { 1974 mNoteList = new ArrayList<NoteData>(1); 1975 } 1976 mNoteList.add(new NoteData(note)); 1977 } 1978 addPhotoBytes(String formatName, byte[] photoBytes, boolean isPrimary)1979 private void addPhotoBytes(String formatName, byte[] photoBytes, boolean isPrimary) { 1980 if (mPhotoList == null) { 1981 mPhotoList = new ArrayList<PhotoData>(1); 1982 } 1983 final PhotoData photoData = new PhotoData(formatName, photoBytes, isPrimary); 1984 mPhotoList.add(photoData); 1985 } 1986 1987 /** 1988 * Tries to extract paramMap, constructs SORT-AS parameter values, and store 1989 * them in appropriate phonetic name variables. This method does not care 1990 * the vCard version. Even when we have SORT-AS parameters in invalid 1991 * versions (i.e. 2.1 and 3.0), we scilently accept them so that we won't 1992 * drop meaningful information. If we had this parameter in the N field of 1993 * vCard 3.0, and the contact data also have SORT-STRING, we will prefer 1994 * SORT-STRING, since it is regitimate property to be understood. 1995 */ tryHandleSortAsName(final Map<String, Collection<String>> paramMap)1996 private void tryHandleSortAsName(final Map<String, Collection<String>> paramMap) { 1997 if (VCardConfig.isVersion30(mVCardType) 1998 && !(TextUtils.isEmpty(mNameData.mPhoneticFamily) 1999 && TextUtils.isEmpty(mNameData.mPhoneticMiddle) && TextUtils 2000 .isEmpty(mNameData.mPhoneticGiven))) { 2001 return; 2002 } 2003 2004 final Collection<String> sortAsCollection = paramMap.get(VCardConstants.PARAM_SORT_AS); 2005 if (sortAsCollection != null && sortAsCollection.size() != 0) { 2006 if (sortAsCollection.size() > 1) { 2007 Log.w(LOG_TAG, 2008 "Incorrect multiple SORT_AS parameters detected: " 2009 + Arrays.toString(sortAsCollection.toArray())); 2010 } 2011 final List<String> sortNames = VCardUtils.constructListFromValue(sortAsCollection 2012 .iterator().next(), mVCardType); 2013 int size = sortNames.size(); 2014 if (size > 3) { 2015 size = 3; 2016 } 2017 switch (size) { 2018 case 3: 2019 mNameData.mPhoneticMiddle = sortNames.get(2); //$FALL-THROUGH$ 2020 case 2: 2021 mNameData.mPhoneticGiven = sortNames.get(1); //$FALL-THROUGH$ 2022 default: 2023 mNameData.mPhoneticFamily = sortNames.get(0); 2024 break; 2025 } 2026 } 2027 } 2028 2029 @SuppressWarnings("fallthrough") handleNProperty(final List<String> paramValues, Map<String, Collection<String>> paramMap)2030 private void handleNProperty(final List<String> paramValues, 2031 Map<String, Collection<String>> paramMap) { 2032 // in vCard 4.0, SORT-AS parameter is available. 2033 tryHandleSortAsName(paramMap); 2034 2035 // Family, Given, Middle, Prefix, Suffix. (1 - 5) 2036 int size; 2037 if (paramValues == null || (size = paramValues.size()) < 1) { 2038 return; 2039 } 2040 if (size > 5) { 2041 size = 5; 2042 } 2043 2044 switch (size) { 2045 // Fall-through. 2046 case 5: 2047 mNameData.mSuffix = paramValues.get(4); 2048 case 4: 2049 mNameData.mPrefix = paramValues.get(3); 2050 case 3: 2051 mNameData.mMiddle = paramValues.get(2); 2052 case 2: 2053 mNameData.mGiven = paramValues.get(1); 2054 default: 2055 mNameData.mFamily = paramValues.get(0); 2056 } 2057 } 2058 2059 /** 2060 * Note: Some Japanese mobile phones use this field for phonetic name, since 2061 * vCard 2.1 does not have "SORT-STRING" type. Also, in some cases, the 2062 * field has some ';'s in it. Assume the ';' means the same meaning in N 2063 * property 2064 */ 2065 @SuppressWarnings("fallthrough") handlePhoneticNameFromSound(List<String> elems)2066 private void handlePhoneticNameFromSound(List<String> elems) { 2067 if (!(TextUtils.isEmpty(mNameData.mPhoneticFamily) 2068 && TextUtils.isEmpty(mNameData.mPhoneticMiddle) && TextUtils 2069 .isEmpty(mNameData.mPhoneticGiven))) { 2070 // This means the other properties like "X-PHONETIC-FIRST-NAME" was already found. 2071 // Ignore "SOUND;X-IRMC-N". 2072 return; 2073 } 2074 2075 int size; 2076 if (elems == null || (size = elems.size()) < 1) { 2077 return; 2078 } 2079 2080 // Assume that the order is "Family, Given, Middle". 2081 // This is not from specification but mere assumption. Some Japanese 2082 // phones use this order. 2083 if (size > 3) { 2084 size = 3; 2085 } 2086 2087 if (elems.get(0).length() > 0) { 2088 boolean onlyFirstElemIsNonEmpty = true; 2089 for (int i = 1; i < size; i++) { 2090 if (elems.get(i).length() > 0) { 2091 onlyFirstElemIsNonEmpty = false; 2092 break; 2093 } 2094 } 2095 if (onlyFirstElemIsNonEmpty) { 2096 final String[] namesArray = elems.get(0).split(" "); 2097 final int nameArrayLength = namesArray.length; 2098 if (nameArrayLength == 3) { 2099 // Assume the string is "Family Middle Given". 2100 mNameData.mPhoneticFamily = namesArray[0]; 2101 mNameData.mPhoneticMiddle = namesArray[1]; 2102 mNameData.mPhoneticGiven = namesArray[2]; 2103 } else if (nameArrayLength == 2) { 2104 // Assume the string is "Family Given" based on the Japanese mobile 2105 // phones' preference. 2106 mNameData.mPhoneticFamily = namesArray[0]; 2107 mNameData.mPhoneticGiven = namesArray[1]; 2108 } else { 2109 mNameData.mPhoneticGiven = elems.get(0); 2110 } 2111 return; 2112 } 2113 } 2114 2115 switch (size) { 2116 // fallthrough 2117 case 3: 2118 mNameData.mPhoneticMiddle = elems.get(2); 2119 case 2: 2120 mNameData.mPhoneticGiven = elems.get(1); 2121 default: 2122 mNameData.mPhoneticFamily = elems.get(0); 2123 } 2124 } 2125 addProperty(final VCardProperty property)2126 public void addProperty(final VCardProperty property) { 2127 final String propertyName = property.getName(); 2128 final Map<String, Collection<String>> paramMap = property.getParameterMap(); 2129 final List<String> propertyValueList = property.getValueList(); 2130 byte[] propertyBytes = property.getByteValue(); 2131 2132 if ((propertyValueList == null || propertyValueList.size() == 0) 2133 && propertyBytes == null) { 2134 return; 2135 } 2136 final String propValue = (propertyValueList != null 2137 ? listToString(propertyValueList).trim() 2138 : null); 2139 2140 if (propertyName.equals(VCardConstants.PROPERTY_VERSION)) { 2141 // vCard version. Ignore this. 2142 } else if (propertyName.equals(VCardConstants.PROPERTY_FN)) { 2143 mNameData.mFormatted = propValue; 2144 } else if (propertyName.equals(VCardConstants.PROPERTY_NAME)) { 2145 // Only in vCard 3.0. Use this if FN doesn't exist though it is 2146 // required in vCard 3.0. 2147 if (TextUtils.isEmpty(mNameData.mFormatted)) { 2148 mNameData.mFormatted = propValue; 2149 } 2150 } else if (propertyName.equals(VCardConstants.PROPERTY_N)) { 2151 handleNProperty(propertyValueList, paramMap); 2152 } else if (propertyName.equals(VCardConstants.PROPERTY_SORT_STRING)) { 2153 mNameData.mSortString = propValue; 2154 } else if (propertyName.equals(VCardConstants.PROPERTY_NICKNAME) 2155 || propertyName.equals(VCardConstants.ImportOnly.PROPERTY_X_NICKNAME)) { 2156 addNickName(propValue); 2157 } else if (propertyName.equals(VCardConstants.PROPERTY_SOUND)) { 2158 Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE); 2159 if (typeCollection != null 2160 && typeCollection.contains(VCardConstants.PARAM_TYPE_X_IRMC_N)) { 2161 // As of 2009-10-08, Parser side does not split a property value into separated 2162 // values using ';' (in other words, propValueList.size() == 1), 2163 // which is correct behavior from the view of vCard 2.1. 2164 // But we want it to be separated, so do the separation here. 2165 final List<String> phoneticNameList = VCardUtils.constructListFromValue(propValue, 2166 mVCardType); 2167 handlePhoneticNameFromSound(phoneticNameList); 2168 } else { 2169 // Ignore this field since Android cannot understand what it is. 2170 } 2171 } else if (propertyName.equals(VCardConstants.PROPERTY_ADR)) { 2172 boolean valuesAreAllEmpty = true; 2173 for (String value : propertyValueList) { 2174 if (!TextUtils.isEmpty(value)) { 2175 valuesAreAllEmpty = false; 2176 break; 2177 } 2178 } 2179 if (valuesAreAllEmpty) { 2180 return; 2181 } 2182 2183 int type = -1; 2184 String label = null; 2185 boolean isPrimary = false; 2186 final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE); 2187 if (typeCollection != null) { 2188 for (final String typeStringOrg : typeCollection) { 2189 final String typeStringUpperCase = typeStringOrg.toUpperCase(); 2190 if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_PREF)) { 2191 isPrimary = true; 2192 } else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_HOME)) { 2193 type = StructuredPostal.TYPE_HOME; 2194 label = null; 2195 } else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_WORK) 2196 || typeStringUpperCase 2197 .equalsIgnoreCase(VCardConstants.PARAM_EXTRA_TYPE_COMPANY)) { 2198 // "COMPANY" seems emitted by Windows Mobile, which is not 2199 // specifically supported by vCard 2.1. We assume this is same 2200 // as "WORK". 2201 type = StructuredPostal.TYPE_WORK; 2202 label = null; 2203 } else if (typeStringUpperCase.equals(VCardConstants.PARAM_ADR_TYPE_PARCEL) 2204 || typeStringUpperCase.equals(VCardConstants.PARAM_ADR_TYPE_DOM) 2205 || typeStringUpperCase.equals(VCardConstants.PARAM_ADR_TYPE_INTL)) { 2206 // We do not have any appropriate way to store this information. 2207 } else if (type < 0) { // If no other type is specified before. 2208 type = StructuredPostal.TYPE_CUSTOM; 2209 if (typeStringUpperCase.startsWith("X-")) { // If X- or x- 2210 label = typeStringOrg.substring(2); 2211 } else { 2212 label = typeStringOrg; 2213 } 2214 } 2215 } 2216 } 2217 // We use "HOME" as default 2218 if (type < 0) { 2219 type = StructuredPostal.TYPE_HOME; 2220 } 2221 2222 addPostal(type, propertyValueList, label, isPrimary); 2223 } else if (propertyName.equals(VCardConstants.PROPERTY_EMAIL)) { 2224 int type = -1; 2225 String label = null; 2226 boolean isPrimary = false; 2227 final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE); 2228 if (typeCollection != null) { 2229 for (final String typeStringOrg : typeCollection) { 2230 final String typeStringUpperCase = typeStringOrg.toUpperCase(); 2231 if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_PREF)) { 2232 isPrimary = true; 2233 } else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_HOME)) { 2234 type = Email.TYPE_HOME; 2235 } else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_WORK)) { 2236 type = Email.TYPE_WORK; 2237 } else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_CELL)) { 2238 type = Email.TYPE_MOBILE; 2239 } else if (type < 0) { // If no other type is specified before 2240 if (typeStringUpperCase.startsWith("X-")) { // If X- or x- 2241 label = typeStringOrg.substring(2); 2242 } else { 2243 label = typeStringOrg; 2244 } 2245 type = Email.TYPE_CUSTOM; 2246 } 2247 } 2248 } 2249 if (type < 0) { 2250 type = Email.TYPE_OTHER; 2251 } 2252 addEmail(type, propValue, label, isPrimary); 2253 } else if (propertyName.equals(VCardConstants.PROPERTY_ORG)) { 2254 // vCard specification does not specify other types. 2255 final int type = Organization.TYPE_WORK; 2256 boolean isPrimary = false; 2257 Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE); 2258 if (typeCollection != null) { 2259 for (String typeString : typeCollection) { 2260 if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) { 2261 isPrimary = true; 2262 } 2263 } 2264 } 2265 handleOrgValue(type, propertyValueList, paramMap, isPrimary); 2266 } else if (propertyName.equals(VCardConstants.PROPERTY_TITLE)) { 2267 handleTitleValue(propValue); 2268 } else if (propertyName.equals(VCardConstants.PROPERTY_ROLE)) { 2269 // This conflicts with TITLE. Ignore for now... 2270 // handleTitleValue(propValue); 2271 } else if (propertyName.equals(VCardConstants.PROPERTY_PHOTO) 2272 || propertyName.equals(VCardConstants.PROPERTY_LOGO)) { 2273 Collection<String> paramMapValue = paramMap.get("VALUE"); 2274 if (paramMapValue != null && paramMapValue.contains("URL")) { 2275 // Currently we do not have appropriate example for testing this case. 2276 } else { 2277 final Collection<String> typeCollection = paramMap.get("TYPE"); 2278 String formatName = null; 2279 boolean isPrimary = false; 2280 if (typeCollection != null) { 2281 for (String typeValue : typeCollection) { 2282 if (VCardConstants.PARAM_TYPE_PREF.equals(typeValue)) { 2283 isPrimary = true; 2284 } else if (formatName == null) { 2285 formatName = typeValue; 2286 } 2287 } 2288 } 2289 addPhotoBytes(formatName, propertyBytes, isPrimary); 2290 } 2291 } else if (propertyName.equals(VCardConstants.PROPERTY_TEL)) { 2292 String phoneNumber = null; 2293 boolean isSip = false; 2294 if (VCardConfig.isVersion40(mVCardType)) { 2295 // Given propValue is in URI format, not in phone number format used until 2296 // vCard 3.0. 2297 if (propValue.startsWith("sip:")) { 2298 isSip = true; 2299 } else if (propValue.startsWith("tel:")) { 2300 phoneNumber = propValue.substring(4); 2301 } else { 2302 // We don't know appropriate way to handle the other schemas. Also, 2303 // we may still have non-URI phone number. To keep given data as much as 2304 // we can, just save original value here. 2305 phoneNumber = propValue; 2306 } 2307 } else { 2308 phoneNumber = propValue; 2309 } 2310 2311 if (isSip) { 2312 final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE); 2313 handleSipCase(propValue, typeCollection); 2314 } else { 2315 if (propValue.length() == 0) { 2316 return; 2317 } 2318 2319 final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE); 2320 final Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection, 2321 phoneNumber); 2322 final int type; 2323 final String label; 2324 if (typeObject instanceof Integer) { 2325 type = (Integer) typeObject; 2326 label = null; 2327 } else { 2328 type = Phone.TYPE_CUSTOM; 2329 label = typeObject.toString(); 2330 } 2331 2332 final boolean isPrimary; 2333 if (typeCollection != null && 2334 typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) { 2335 isPrimary = true; 2336 } else { 2337 isPrimary = false; 2338 } 2339 2340 addPhone(type, phoneNumber, label, isPrimary); 2341 } 2342 } else if (propertyName.equals(VCardConstants.PROPERTY_X_SKYPE_PSTNNUMBER)) { 2343 // The phone number available via Skype. 2344 Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE); 2345 final int type = Phone.TYPE_OTHER; 2346 final boolean isPrimary; 2347 if (typeCollection != null 2348 && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) { 2349 isPrimary = true; 2350 } else { 2351 isPrimary = false; 2352 } 2353 addPhone(type, propValue, null, isPrimary); 2354 } else if (sImMap.containsKey(propertyName)) { 2355 final int protocol = sImMap.get(propertyName); 2356 boolean isPrimary = false; 2357 int type = -1; 2358 final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE); 2359 if (typeCollection != null) { 2360 for (String typeString : typeCollection) { 2361 if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) { 2362 isPrimary = true; 2363 } else if (type < 0) { 2364 if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_HOME)) { 2365 type = Im.TYPE_HOME; 2366 } else if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_WORK)) { 2367 type = Im.TYPE_WORK; 2368 } 2369 } 2370 } 2371 } 2372 if (type < 0) { 2373 type = Im.TYPE_HOME; 2374 } 2375 addIm(protocol, null, propValue, type, isPrimary); 2376 } else if (propertyName.equals(VCardConstants.PROPERTY_NOTE)) { 2377 addNote(propValue); 2378 } else if (propertyName.equals(VCardConstants.PROPERTY_URL)) { 2379 if (mWebsiteList == null) { 2380 mWebsiteList = new ArrayList<WebsiteData>(1); 2381 } 2382 mWebsiteList.add(new WebsiteData(propValue)); 2383 } else if (propertyName.equals(VCardConstants.PROPERTY_BDAY)) { 2384 mBirthday = new BirthdayData(propValue); 2385 } else if (propertyName.equals(VCardConstants.PROPERTY_ANNIVERSARY)) { 2386 mAnniversary = new AnniversaryData(propValue); 2387 } else if (propertyName.equals(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME)) { 2388 mNameData.mPhoneticGiven = propValue; 2389 } else if (propertyName.equals(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) { 2390 mNameData.mPhoneticMiddle = propValue; 2391 } else if (propertyName.equals(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME)) { 2392 mNameData.mPhoneticFamily = propValue; 2393 } else if (propertyName.equals(VCardConstants.PROPERTY_IMPP)) { 2394 // See also RFC 4770 (for vCard 3.0) 2395 if (propValue.startsWith("sip:")) { 2396 final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE); 2397 handleSipCase(propValue, typeCollection); 2398 } 2399 } else if (propertyName.equals(VCardConstants.PROPERTY_X_SIP)) { 2400 if (!TextUtils.isEmpty(propValue)) { 2401 final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE); 2402 handleSipCase(propValue, typeCollection); 2403 } 2404 } else if (propertyName.equals(VCardConstants.PROPERTY_X_ANDROID_CUSTOM)) { 2405 final List<String> customPropertyList = VCardUtils.constructListFromValue(propValue, 2406 mVCardType); 2407 handleAndroidCustomProperty(customPropertyList); 2408 } else if (propertyName.toUpperCase().startsWith("X-")) { 2409 // Catch all for X- properties. The caller can decide what to do with these. 2410 if (mUnknownXData == null) { 2411 mUnknownXData = new ArrayList<Pair<String, String>>(); 2412 } 2413 mUnknownXData.add(new Pair<String, String>(propertyName, propValue)); 2414 } else { 2415 } 2416 // Be careful when adding some logic here, as some blocks above may use "return". 2417 } 2418 2419 /** 2420 * @param propValue may contain "sip:" at the beginning. 2421 * @param typeCollection 2422 */ handleSipCase(String propValue, Collection<String> typeCollection)2423 private void handleSipCase(String propValue, Collection<String> typeCollection) { 2424 if (TextUtils.isEmpty(propValue)) { 2425 return; 2426 } 2427 if (propValue.startsWith("sip:")) { 2428 propValue = propValue.substring(4); 2429 if (propValue.length() == 0) { 2430 return; 2431 } 2432 } 2433 2434 int type = -1; 2435 String label = null; 2436 boolean isPrimary = false; 2437 if (typeCollection != null) { 2438 for (final String typeStringOrg : typeCollection) { 2439 final String typeStringUpperCase = typeStringOrg.toUpperCase(); 2440 if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_PREF)) { 2441 isPrimary = true; 2442 } else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_HOME)) { 2443 type = SipAddress.TYPE_HOME; 2444 } else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_WORK)) { 2445 type = SipAddress.TYPE_WORK; 2446 } else if (type < 0) { // If no other type is specified before 2447 if (typeStringUpperCase.startsWith("X-")) { // If X- or x- 2448 label = typeStringOrg.substring(2); 2449 } else { 2450 label = typeStringOrg; 2451 } 2452 type = SipAddress.TYPE_CUSTOM; 2453 } 2454 } 2455 } 2456 if (type < 0) { 2457 type = SipAddress.TYPE_OTHER; 2458 } 2459 addSip(propValue, type, label, isPrimary); 2460 } 2461 addChild(VCardEntry child)2462 public void addChild(VCardEntry child) { 2463 if (mChildren == null) { 2464 mChildren = new ArrayList<VCardEntry>(); 2465 } 2466 mChildren.add(child); 2467 } 2468 handleAndroidCustomProperty(final List<String> customPropertyList)2469 private void handleAndroidCustomProperty(final List<String> customPropertyList) { 2470 if (mAndroidCustomDataList == null) { 2471 mAndroidCustomDataList = new ArrayList<AndroidCustomData>(); 2472 } 2473 mAndroidCustomDataList 2474 .add(AndroidCustomData.constructAndroidCustomData(customPropertyList)); 2475 } 2476 2477 /** 2478 * Construct the display name. The constructed data must not be null. 2479 */ constructDisplayName()2480 private String constructDisplayName() { 2481 String displayName = null; 2482 // FullName (created via "FN" or "NAME" field) is prefered. 2483 if (!TextUtils.isEmpty(mNameData.mFormatted)) { 2484 displayName = mNameData.mFormatted; 2485 } else if (!mNameData.emptyStructuredName()) { 2486 displayName = VCardUtils.constructNameFromElements(mVCardType, mNameData.mFamily, 2487 mNameData.mMiddle, mNameData.mGiven, mNameData.mPrefix, mNameData.mSuffix); 2488 } else if (!mNameData.emptyPhoneticStructuredName()) { 2489 displayName = VCardUtils.constructNameFromElements(mVCardType, 2490 mNameData.mPhoneticFamily, mNameData.mPhoneticMiddle, mNameData.mPhoneticGiven); 2491 } else if (mEmailList != null && mEmailList.size() > 0) { 2492 displayName = mEmailList.get(0).mAddress; 2493 } else if (mPhoneList != null && mPhoneList.size() > 0) { 2494 displayName = mPhoneList.get(0).mNumber; 2495 } else if (mPostalList != null && mPostalList.size() > 0) { 2496 displayName = mPostalList.get(0).getFormattedAddress(mVCardType); 2497 } else if (mOrganizationList != null && mOrganizationList.size() > 0) { 2498 displayName = mOrganizationList.get(0).getFormattedString(); 2499 } 2500 if (displayName == null) { 2501 displayName = ""; 2502 } 2503 return displayName; 2504 } 2505 2506 /** 2507 * Consolidate several fielsds (like mName) using name candidates, 2508 */ consolidateFields()2509 public void consolidateFields() { 2510 mNameData.displayName = constructDisplayName(); 2511 } 2512 2513 /** 2514 * @return true when this object has nothing meaningful for Android's 2515 * Contacts, and thus is "ignorable" for Android's Contacts. This 2516 * does not mean an original vCard is really empty. Even when the 2517 * original vCard has some fields, this may ignore it if those 2518 * fields cannot be transcoded into Android's Contacts 2519 * representation. 2520 */ isIgnorable()2521 public boolean isIgnorable() { 2522 IsIgnorableIterator iterator = new IsIgnorableIterator(); 2523 iterateAllData(iterator); 2524 return iterator.getResult(); 2525 } 2526 2527 /** 2528 * Constructs the list of insert operation for this object. When the 2529 * operationList argument is null, this method creates a new ArrayList and 2530 * return it. The returned object is filled with new insert operations for 2531 * this object. When operationList argument is not null, this method appends 2532 * those new operations into the object instead of creating a new ArrayList. 2533 * 2534 * @param resolver {@link ContentResolver} object to be used in this method. 2535 * @param operationList object to be filled. You can use this argument to 2536 * concatinate operation lists. If null, this method creates a 2537 * new array object. 2538 * @return If operationList argument is null, new object with new insert 2539 * operations. If it is not null, the operationList object with 2540 * operations inserted by this method. 2541 */ constructInsertOperations(ContentResolver resolver, ArrayList<ContentProviderOperation> operationList)2542 public ArrayList<ContentProviderOperation> constructInsertOperations(ContentResolver resolver, 2543 ArrayList<ContentProviderOperation> operationList) { 2544 if (operationList == null) { 2545 operationList = new ArrayList<ContentProviderOperation>(); 2546 } 2547 2548 if (isIgnorable()) { 2549 return operationList; 2550 } 2551 2552 final int backReferenceIndex = operationList.size(); 2553 2554 // After applying the batch the first result's Uri is returned so it is important that 2555 // the RawContact is the first operation that gets inserted into the list. 2556 ContentProviderOperation.Builder builder = ContentProviderOperation 2557 .newInsert(RawContacts.CONTENT_URI); 2558 if (mAccount != null) { 2559 builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name); 2560 builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type); 2561 } else { 2562 builder.withValue(RawContacts.ACCOUNT_NAME, null); 2563 builder.withValue(RawContacts.ACCOUNT_TYPE, null); 2564 } 2565 operationList.add(builder.build()); 2566 2567 int start = operationList.size(); 2568 iterateAllData(new InsertOperationConstrutor(operationList, backReferenceIndex)); 2569 int end = operationList.size(); 2570 2571 return operationList; 2572 } 2573 buildFromResolver(ContentResolver resolver)2574 public static VCardEntry buildFromResolver(ContentResolver resolver) { 2575 return buildFromResolver(resolver, Contacts.CONTENT_URI); 2576 } 2577 buildFromResolver(ContentResolver resolver, Uri uri)2578 public static VCardEntry buildFromResolver(ContentResolver resolver, Uri uri) { 2579 return null; 2580 } 2581 listToString(List<String> list)2582 private String listToString(List<String> list) { 2583 final int size = list.size(); 2584 if (size > 1) { 2585 StringBuilder builder = new StringBuilder(); 2586 int i = 0; 2587 for (String type : list) { 2588 builder.append(type); 2589 if (i < size - 1) { 2590 builder.append(";"); 2591 } 2592 } 2593 return builder.toString(); 2594 } else if (size == 1) { 2595 return list.get(0); 2596 } else { 2597 return ""; 2598 } 2599 } 2600 getNameData()2601 public final NameData getNameData() { 2602 return mNameData; 2603 } 2604 getNickNameList()2605 public final List<NicknameData> getNickNameList() { 2606 return mNicknameList; 2607 } 2608 getBirthday()2609 public final String getBirthday() { 2610 return mBirthday != null ? mBirthday.mBirthday : null; 2611 } 2612 getNotes()2613 public final List<NoteData> getNotes() { 2614 return mNoteList; 2615 } 2616 getPhoneList()2617 public final List<PhoneData> getPhoneList() { 2618 return mPhoneList; 2619 } 2620 getEmailList()2621 public final List<EmailData> getEmailList() { 2622 return mEmailList; 2623 } 2624 getPostalList()2625 public final List<PostalData> getPostalList() { 2626 return mPostalList; 2627 } 2628 getOrganizationList()2629 public final List<OrganizationData> getOrganizationList() { 2630 return mOrganizationList; 2631 } 2632 getImList()2633 public final List<ImData> getImList() { 2634 return mImList; 2635 } 2636 getPhotoList()2637 public final List<PhotoData> getPhotoList() { 2638 return mPhotoList; 2639 } 2640 getWebsiteList()2641 public final List<WebsiteData> getWebsiteList() { 2642 return mWebsiteList; 2643 } 2644 2645 /** 2646 * @hide this interface may be changed for better support of vCard 4.0 (UID) 2647 */ getChildlen()2648 public final List<VCardEntry> getChildlen() { 2649 return mChildren; 2650 } 2651 getDisplayName()2652 public String getDisplayName() { 2653 if (mNameData.displayName == null) { 2654 mNameData.displayName = constructDisplayName(); 2655 } 2656 return mNameData.displayName; 2657 } 2658 getUnknownXData()2659 public List<Pair<String, String>> getUnknownXData() { 2660 return mUnknownXData; 2661 } 2662 } 2663