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