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 package android.pim.vcard; 17 18 import android.accounts.Account; 19 import android.content.ContentProviderOperation; 20 import android.content.ContentResolver; 21 import android.content.OperationApplicationException; 22 import android.database.Cursor; 23 import android.os.RemoteException; 24 import android.provider.ContactsContract; 25 import android.provider.ContactsContract.Data; 26 import android.provider.ContactsContract.Groups; 27 import android.provider.ContactsContract.RawContacts; 28 import android.provider.ContactsContract.CommonDataKinds.Email; 29 import android.provider.ContactsContract.CommonDataKinds.Event; 30 import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 31 import android.provider.ContactsContract.CommonDataKinds.Im; 32 import android.provider.ContactsContract.CommonDataKinds.Nickname; 33 import android.provider.ContactsContract.CommonDataKinds.Note; 34 import android.provider.ContactsContract.CommonDataKinds.Organization; 35 import android.provider.ContactsContract.CommonDataKinds.Phone; 36 import android.provider.ContactsContract.CommonDataKinds.Photo; 37 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 38 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 39 import android.provider.ContactsContract.CommonDataKinds.Website; 40 import android.telephony.PhoneNumberUtils; 41 import android.text.TextUtils; 42 import android.util.Log; 43 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collection; 47 import java.util.HashMap; 48 import java.util.HashSet; 49 import java.util.Iterator; 50 import java.util.List; 51 import java.util.Map; 52 53 /** 54 * This class bridges between data structure of Contact app and VCard data. 55 */ 56 public class ContactStruct { 57 private static final String LOG_TAG = "vcard.ContactStruct"; 58 59 // Key: the name shown in VCard. e.g. "X-AIM", "X-ICQ" 60 // Value: the result of {@link Contacts.ContactMethods#encodePredefinedImProtocol} 61 private static final Map<String, Integer> sImMap = new HashMap<String, Integer>(); 62 63 static { sImMap.put(Constants.PROPERTY_X_AIM, Im.PROTOCOL_AIM)64 sImMap.put(Constants.PROPERTY_X_AIM, Im.PROTOCOL_AIM); sImMap.put(Constants.PROPERTY_X_MSN, Im.PROTOCOL_MSN)65 sImMap.put(Constants.PROPERTY_X_MSN, Im.PROTOCOL_MSN); sImMap.put(Constants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO)66 sImMap.put(Constants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO); sImMap.put(Constants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ)67 sImMap.put(Constants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ); sImMap.put(Constants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER)68 sImMap.put(Constants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER); sImMap.put(Constants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE)69 sImMap.put(Constants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE); sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK)70 sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK); sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK_WITH_SPACE, Im.PROTOCOL_GOOGLE_TALK)71 sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK_WITH_SPACE, Im.PROTOCOL_GOOGLE_TALK); 72 } 73 74 /** 75 * @hide only for testing 76 */ 77 static public class PhoneData { 78 public final int type; 79 public final String data; 80 public final String label; 81 // isPrimary is changable only when there's no appropriate one existing in 82 // the original VCard. 83 public boolean isPrimary; PhoneData(int type, String data, String label, boolean isPrimary)84 public PhoneData(int type, String data, String label, boolean isPrimary) { 85 this.type = type; 86 this.data = data; 87 this.label = label; 88 this.isPrimary = isPrimary; 89 } 90 91 @Override equals(Object obj)92 public boolean equals(Object obj) { 93 if (obj instanceof PhoneData) { 94 return false; 95 } 96 PhoneData phoneData = (PhoneData)obj; 97 return (type == phoneData.type && data.equals(phoneData.data) && 98 label.equals(phoneData.label) && isPrimary == phoneData.isPrimary); 99 } 100 101 @Override toString()102 public String toString() { 103 return String.format("type: %d, data: %s, label: %s, isPrimary: %s", 104 type, data, label, isPrimary); 105 } 106 } 107 108 /** 109 * @hide only for testing 110 */ 111 static public class EmailData { 112 public final int type; 113 public final String data; 114 // Used only when TYPE is TYPE_CUSTOM. 115 public final String label; 116 // isPrimary is changable only when there's no appropriate one existing in 117 // the original VCard. 118 public boolean isPrimary; EmailData(int type, String data, String label, boolean isPrimary)119 public EmailData(int type, String data, String label, boolean isPrimary) { 120 this.type = type; 121 this.data = data; 122 this.label = label; 123 this.isPrimary = isPrimary; 124 } 125 126 @Override equals(Object obj)127 public boolean equals(Object obj) { 128 if (obj instanceof EmailData) { 129 return false; 130 } 131 EmailData emailData = (EmailData)obj; 132 return (type == emailData.type && data.equals(emailData.data) && 133 label.equals(emailData.label) && isPrimary == emailData.isPrimary); 134 } 135 136 @Override toString()137 public String toString() { 138 return String.format("type: %d, data: %s, label: %s, isPrimary: %s", 139 type, data, label, isPrimary); 140 } 141 } 142 143 static public class PostalData { 144 // Determined by vCard spec. 145 // PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name 146 public static final int ADDR_MAX_DATA_SIZE = 7; 147 private final String[] dataArray; 148 public final String pobox; 149 public final String extendedAddress; 150 public final String street; 151 public final String localty; 152 public final String region; 153 public final String postalCode; 154 public final String country; 155 156 public final int type; 157 158 // Used only when type variable is TYPE_CUSTOM. 159 public final String label; 160 161 // isPrimary is changable only when there's no appropriate one existing in 162 // the original VCard. 163 public boolean isPrimary; PostalData(int type, List<String> propValueList, String label, boolean isPrimary)164 public PostalData(int type, List<String> propValueList, 165 String label, boolean isPrimary) { 166 this.type = type; 167 dataArray = new String[ADDR_MAX_DATA_SIZE]; 168 169 int size = propValueList.size(); 170 if (size > ADDR_MAX_DATA_SIZE) { 171 size = ADDR_MAX_DATA_SIZE; 172 } 173 174 // adr-value = 0*6(text-value ";") text-value 175 // ; PO Box, Extended Address, Street, Locality, Region, Postal 176 // ; Code, Country Name 177 // 178 // Use Iterator assuming List may be LinkedList, though actually it is 179 // always ArrayList in the current implementation. 180 int i = 0; 181 for (String addressElement : propValueList) { 182 dataArray[i] = addressElement; 183 if (++i >= size) { 184 break; 185 } 186 } 187 while (i < ADDR_MAX_DATA_SIZE) { 188 dataArray[i++] = null; 189 } 190 191 this.pobox = dataArray[0]; 192 this.extendedAddress = dataArray[1]; 193 this.street = dataArray[2]; 194 this.localty = dataArray[3]; 195 this.region = dataArray[4]; 196 this.postalCode = dataArray[5]; 197 this.country = dataArray[6]; 198 199 this.label = label; 200 this.isPrimary = isPrimary; 201 } 202 203 @Override equals(Object obj)204 public boolean equals(Object obj) { 205 if (obj instanceof PostalData) { 206 return false; 207 } 208 PostalData postalData = (PostalData)obj; 209 return (Arrays.equals(dataArray, postalData.dataArray) && 210 (type == postalData.type && 211 (type == StructuredPostal.TYPE_CUSTOM ? 212 (label == postalData.label) : true)) && 213 (isPrimary == postalData.isPrimary)); 214 } 215 getFormattedAddress(int vcardType)216 public String getFormattedAddress(int vcardType) { 217 StringBuilder builder = new StringBuilder(); 218 boolean empty = true; 219 if (VCardConfig.isJapaneseDevice(vcardType)) { 220 // In Japan, the order is reversed. 221 for (int i = ADDR_MAX_DATA_SIZE - 1; i >= 0; i--) { 222 String addressPart = dataArray[i]; 223 if (!TextUtils.isEmpty(addressPart)) { 224 if (!empty) { 225 builder.append(' '); 226 } 227 builder.append(addressPart); 228 empty = false; 229 } 230 } 231 } else { 232 for (int i = 0; i < ADDR_MAX_DATA_SIZE; i++) { 233 String addressPart = dataArray[i]; 234 if (!TextUtils.isEmpty(addressPart)) { 235 if (!empty) { 236 builder.append(' '); 237 } 238 builder.append(addressPart); 239 empty = false; 240 } 241 } 242 } 243 244 return builder.toString().trim(); 245 } 246 247 @Override toString()248 public String toString() { 249 return String.format("type: %d, label: %s, isPrimary: %s", 250 type, label, isPrimary); 251 } 252 } 253 254 /** 255 * @hide only for testing. 256 */ 257 static public class OrganizationData { 258 public final int type; 259 public final String companyName; 260 // can be changed in some VCard format. 261 public String positionName; 262 // isPrimary is changable only when there's no appropriate one existing in 263 // the original VCard. 264 public boolean isPrimary; OrganizationData(int type, String companyName, String positionName, boolean isPrimary)265 public OrganizationData(int type, String companyName, String positionName, 266 boolean isPrimary) { 267 this.type = type; 268 this.companyName = companyName; 269 this.positionName = positionName; 270 this.isPrimary = isPrimary; 271 } 272 273 @Override equals(Object obj)274 public boolean equals(Object obj) { 275 if (obj instanceof OrganizationData) { 276 return false; 277 } 278 OrganizationData organization = (OrganizationData)obj; 279 return (type == organization.type && companyName.equals(organization.companyName) && 280 positionName.equals(organization.positionName) && 281 isPrimary == organization.isPrimary); 282 } 283 284 @Override toString()285 public String toString() { 286 return String.format("type: %d, company: %s, position: %s, isPrimary: %s", 287 type, companyName, positionName, isPrimary); 288 } 289 } 290 291 static public class ImData { 292 public final int type; 293 public final String data; 294 public final String label; 295 public final boolean isPrimary; 296 297 // TODO: ContactsConstant#PROTOCOL, ContactsConstant#CUSTOM_PROTOCOL should be used? ImData(int type, String data, String label, boolean isPrimary)298 public ImData(int type, String data, String label, boolean isPrimary) { 299 this.type = type; 300 this.data = data; 301 this.label = label; 302 this.isPrimary = isPrimary; 303 } 304 305 @Override equals(Object obj)306 public boolean equals(Object obj) { 307 if (obj instanceof ImData) { 308 return false; 309 } 310 ImData imData = (ImData)obj; 311 return (type == imData.type && data.equals(imData.data) && 312 label.equals(imData.label) && isPrimary == imData.isPrimary); 313 } 314 315 @Override toString()316 public String toString() { 317 return String.format("type: %d, data: %s, label: %s, isPrimary: %s", 318 type, data, label, isPrimary); 319 } 320 } 321 322 /** 323 * @hide only for testing. 324 */ 325 static public class PhotoData { 326 public static final String FORMAT_FLASH = "SWF"; 327 public final int type; 328 public final String formatName; // used when type is not defined in ContactsContract. 329 public final byte[] photoBytes; 330 PhotoData(int type, String formatName, byte[] photoBytes)331 public PhotoData(int type, String formatName, byte[] photoBytes) { 332 this.type = type; 333 this.formatName = formatName; 334 this.photoBytes = photoBytes; 335 } 336 } 337 338 static /* package */ class Property { 339 private String mPropertyName; 340 private Map<String, Collection<String>> mParameterMap = 341 new HashMap<String, Collection<String>>(); 342 private List<String> mPropertyValueList = new ArrayList<String>(); 343 private byte[] mPropertyBytes; 344 Property()345 public Property() { 346 clear(); 347 } 348 setPropertyName(final String propertyName)349 public void setPropertyName(final String propertyName) { 350 mPropertyName = propertyName; 351 } 352 addParameter(final String paramName, final String paramValue)353 public void addParameter(final String paramName, final String paramValue) { 354 Collection<String> values; 355 if (!mParameterMap.containsKey(paramName)) { 356 if (paramName.equals("TYPE")) { 357 values = new HashSet<String>(); 358 } else { 359 values = new ArrayList<String>(); 360 } 361 mParameterMap.put(paramName, values); 362 } else { 363 values = mParameterMap.get(paramName); 364 } 365 values.add(paramValue); 366 } 367 addToPropertyValueList(final String propertyValue)368 public void addToPropertyValueList(final String propertyValue) { 369 mPropertyValueList.add(propertyValue); 370 } 371 setPropertyBytes(final byte[] propertyBytes)372 public void setPropertyBytes(final byte[] propertyBytes) { 373 mPropertyBytes = propertyBytes; 374 } 375 getParameters(String type)376 public final Collection<String> getParameters(String type) { 377 return mParameterMap.get(type); 378 } 379 getPropertyValueList()380 public final List<String> getPropertyValueList() { 381 return mPropertyValueList; 382 } 383 clear()384 public void clear() { 385 mPropertyName = null; 386 mParameterMap.clear(); 387 mPropertyValueList.clear(); 388 } 389 } 390 391 private String mFamilyName; 392 private String mGivenName; 393 private String mMiddleName; 394 private String mPrefix; 395 private String mSuffix; 396 397 // Used only when no family nor given name is found. 398 private String mFullName; 399 400 private String mPhoneticFamilyName; 401 private String mPhoneticGivenName; 402 private String mPhoneticMiddleName; 403 404 private String mPhoneticFullName; 405 406 private List<String> mNickNameList; 407 408 private String mDisplayName; 409 410 private String mBirthday; 411 412 private List<String> mNoteList; 413 private List<PhoneData> mPhoneList; 414 private List<EmailData> mEmailList; 415 private List<PostalData> mPostalList; 416 private List<OrganizationData> mOrganizationList; 417 private List<ImData> mImList; 418 private List<PhotoData> mPhotoList; 419 private List<String> mWebsiteList; 420 421 private final int mVCardType; 422 private final Account mAccount; 423 424 // Each Column of four properties has ISPRIMARY field 425 // (See android.provider.Contacts) 426 // If false even after the parsing loop, we choose the first entry as a "primary" 427 // entry. 428 private boolean mPrefIsSet_Address; 429 private boolean mPrefIsSet_Phone; 430 private boolean mPrefIsSet_Email; 431 private boolean mPrefIsSet_Organization; 432 ContactStruct()433 public ContactStruct() { 434 this(VCardConfig.VCARD_TYPE_V21_GENERIC); 435 } 436 ContactStruct(int vcardType)437 public ContactStruct(int vcardType) { 438 this(vcardType, null); 439 } 440 ContactStruct(int vcardType, Account account)441 public ContactStruct(int vcardType, Account account) { 442 mVCardType = vcardType; 443 mAccount = account; 444 } 445 446 /** 447 * @hide only for testing. 448 */ ContactStruct(String givenName, String familyName, String middleName, String prefix, String suffix, String phoneticGivenName, String pheneticFamilyName, String phoneticMiddleName, List<byte[]> photoBytesList, List<String> notes, List<PhoneData> phoneList, List<EmailData> emailList, List<PostalData> postalList, List<OrganizationData> organizationList, List<PhotoData> photoList, List<String> websiteList)449 public ContactStruct(String givenName, 450 String familyName, 451 String middleName, 452 String prefix, 453 String suffix, 454 String phoneticGivenName, 455 String pheneticFamilyName, 456 String phoneticMiddleName, 457 List<byte[]> photoBytesList, 458 List<String> notes, 459 List<PhoneData> phoneList, 460 List<EmailData> emailList, 461 List<PostalData> postalList, 462 List<OrganizationData> organizationList, 463 List<PhotoData> photoList, 464 List<String> websiteList) { 465 this(VCardConfig.VCARD_TYPE_DEFAULT); 466 mGivenName = givenName; 467 mFamilyName = familyName; 468 mPrefix = prefix; 469 mSuffix = suffix; 470 mPhoneticGivenName = givenName; 471 mPhoneticFamilyName = familyName; 472 mPhoneticMiddleName = middleName; 473 mEmailList = emailList; 474 mPostalList = postalList; 475 mOrganizationList = organizationList; 476 mPhotoList = photoList; 477 mWebsiteList = websiteList; 478 } 479 480 // All getter methods should be used carefully, since they may change 481 // in the future as of 2009-09-24, on which I cannot be sure this structure 482 // is completely consolidated. 483 // When we are sure we will no longer change them, we'll be happy to 484 // make it complete public (withouth @hide tag) 485 // 486 // Also note that these getter methods should be used only after 487 // all properties being pushed into this object. If not, incorrect 488 // value will "be stored in the local cache and" be returned to you. 489 490 /** 491 * @hide 492 */ getFamilyName()493 public String getFamilyName() { 494 return mFamilyName; 495 } 496 497 /** 498 * @hide 499 */ getGivenName()500 public String getGivenName() { 501 return mGivenName; 502 } 503 504 /** 505 * @hide 506 */ getMiddleName()507 public String getMiddleName() { 508 return mMiddleName; 509 } 510 511 /** 512 * @hide 513 */ getPrefix()514 public String getPrefix() { 515 return mPrefix; 516 } 517 518 /** 519 * @hide 520 */ getSuffix()521 public String getSuffix() { 522 return mSuffix; 523 } 524 525 /** 526 * @hide 527 */ getFullName()528 public String getFullName() { 529 return mFullName; 530 } 531 532 /** 533 * @hide 534 */ getPhoneticFamilyName()535 public String getPhoneticFamilyName() { 536 return mPhoneticFamilyName; 537 } 538 539 /** 540 * @hide 541 */ getPhoneticGivenName()542 public String getPhoneticGivenName() { 543 return mPhoneticGivenName; 544 } 545 546 /** 547 * @hide 548 */ getPhoneticMiddleName()549 public String getPhoneticMiddleName() { 550 return mPhoneticMiddleName; 551 } 552 553 /** 554 * @hide 555 */ getPhoneticFullName()556 public String getPhoneticFullName() { 557 return mPhoneticFullName; 558 } 559 560 /** 561 * @hide 562 */ getNickNameList()563 public final List<String> getNickNameList() { 564 return mNickNameList; 565 } 566 567 /** 568 * @hide 569 */ getDisplayName()570 public String getDisplayName() { 571 if (mDisplayName == null) { 572 constructDisplayName(); 573 } 574 return mDisplayName; 575 } 576 577 /** 578 * @hide 579 */ getBirthday()580 public String getBirthday() { 581 return mBirthday; 582 } 583 584 /** 585 * @hide 586 */ getPhotoList()587 public final List<PhotoData> getPhotoList() { 588 return mPhotoList; 589 } 590 591 /** 592 * @hide 593 */ getNotes()594 public final List<String> getNotes() { 595 return mNoteList; 596 } 597 598 /** 599 * @hide 600 */ getPhoneList()601 public final List<PhoneData> getPhoneList() { 602 return mPhoneList; 603 } 604 605 /** 606 * @hide 607 */ getEmailList()608 public final List<EmailData> getEmailList() { 609 return mEmailList; 610 } 611 612 /** 613 * @hide 614 */ getPostalList()615 public final List<PostalData> getPostalList() { 616 return mPostalList; 617 } 618 619 /** 620 * @hide 621 */ getOrganizationList()622 public final List<OrganizationData> getOrganizationList() { 623 return mOrganizationList; 624 } 625 626 /** 627 * Add a phone info to phoneList. 628 * @param data phone number 629 * @param type type col of content://contacts/phones 630 * @param label lable col of content://contacts/phones 631 */ addPhone(int type, String data, String label, boolean isPrimary)632 private void addPhone(int type, String data, String label, boolean isPrimary){ 633 if (mPhoneList == null) { 634 mPhoneList = new ArrayList<PhoneData>(); 635 } 636 StringBuilder builder = new StringBuilder(); 637 String trimed = data.trim(); 638 int length = trimed.length(); 639 for (int i = 0; i < length; i++) { 640 char ch = trimed.charAt(i); 641 if (('0' <= ch && ch <= '9') || (i == 0 && ch == '+')) { 642 builder.append(ch); 643 } 644 } 645 646 PhoneData phoneData = new PhoneData(type, 647 PhoneNumberUtils.formatNumber(builder.toString()), 648 label, isPrimary); 649 650 mPhoneList.add(phoneData); 651 } 652 addNickName(final String nickName)653 private void addNickName(final String nickName) { 654 if (mNickNameList == null) { 655 mNickNameList = new ArrayList<String>(); 656 } 657 mNickNameList.add(nickName); 658 } 659 addEmail(int type, String data, String label, boolean isPrimary)660 private void addEmail(int type, String data, String label, boolean isPrimary){ 661 if (mEmailList == null) { 662 mEmailList = new ArrayList<EmailData>(); 663 } 664 mEmailList.add(new EmailData(type, data, label, isPrimary)); 665 } 666 addPostal(int type, List<String> propValueList, String label, boolean isPrimary)667 private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){ 668 if (mPostalList == null) { 669 mPostalList = new ArrayList<PostalData>(); 670 } 671 mPostalList.add(new PostalData(type, propValueList, label, isPrimary)); 672 } 673 addOrganization(int type, final String companyName, final String positionName, boolean isPrimary)674 private void addOrganization(int type, final String companyName, 675 final String positionName, boolean isPrimary) { 676 if (mOrganizationList == null) { 677 mOrganizationList = new ArrayList<OrganizationData>(); 678 } 679 mOrganizationList.add(new OrganizationData(type, companyName, positionName, isPrimary)); 680 } 681 addIm(int type, String data, String label, boolean isPrimary)682 private void addIm(int type, String data, String label, boolean isPrimary) { 683 if (mImList == null) { 684 mImList = new ArrayList<ImData>(); 685 } 686 mImList.add(new ImData(type, data, label, isPrimary)); 687 } 688 addNote(final String note)689 private void addNote(final String note) { 690 if (mNoteList == null) { 691 mNoteList = new ArrayList<String>(1); 692 } 693 mNoteList.add(note); 694 } 695 addPhotoBytes(String formatName, byte[] photoBytes)696 private void addPhotoBytes(String formatName, byte[] photoBytes) { 697 if (mPhotoList == null) { 698 mPhotoList = new ArrayList<PhotoData>(1); 699 } 700 final PhotoData photoData = new PhotoData(0, null, photoBytes); 701 mPhotoList.add(photoData); 702 } 703 704 /** 705 * Set "position" value to the appropriate data. If there's more than one 706 * OrganizationData objects, the value is set to the last one. If there's no 707 * OrganizationData object, a new OrganizationData is created, whose company name is 708 * empty. 709 * 710 * TODO: incomplete logic. fix this: 711 * 712 * e.g. This assumes ORG comes earlier, but TITLE may come earlier like this, though we do not 713 * know how to handle it in general cases... 714 * ---- 715 * TITLE:Software Engineer 716 * ORG:Google 717 * ---- 718 */ setPosition(String positionValue)719 private void setPosition(String positionValue) { 720 if (mOrganizationList == null) { 721 mOrganizationList = new ArrayList<OrganizationData>(); 722 } 723 int size = mOrganizationList.size(); 724 if (size == 0) { 725 addOrganization(ContactsContract.CommonDataKinds.Organization.TYPE_OTHER, 726 "", null, false); 727 size = 1; 728 } 729 OrganizationData lastData = mOrganizationList.get(size - 1); 730 lastData.positionName = positionValue; 731 } 732 733 @SuppressWarnings("fallthrough") handleNProperty(List<String> elems)734 private void handleNProperty(List<String> elems) { 735 // Family, Given, Middle, Prefix, Suffix. (1 - 5) 736 int size; 737 if (elems == null || (size = elems.size()) < 1) { 738 return; 739 } 740 if (size > 5) { 741 size = 5; 742 } 743 744 switch (size) { 745 // fallthrough 746 case 5: 747 mSuffix = elems.get(4); 748 case 4: 749 mPrefix = elems.get(3); 750 case 3: 751 mMiddleName = elems.get(2); 752 case 2: 753 mGivenName = elems.get(1); 754 default: 755 mFamilyName = elems.get(0); 756 } 757 } 758 759 /** 760 * Some Japanese mobile phones use this field for phonetic name, 761 * since vCard 2.1 does not have "SORT-STRING" type. 762 * Also, in some cases, the field has some ';'s in it. 763 * Assume the ';' means the same meaning in N property 764 */ 765 @SuppressWarnings("fallthrough") handlePhoneticNameFromSound(List<String> elems)766 private void handlePhoneticNameFromSound(List<String> elems) { 767 // Family, Given, Middle. (1-3) 768 // This is not from specification but mere assumption. Some Japanese phones use this order. 769 int size; 770 if (elems == null || (size = elems.size()) < 1) { 771 return; 772 } 773 if (size > 3) { 774 size = 3; 775 } 776 777 switch (size) { 778 // fallthrough 779 case 3: 780 mPhoneticMiddleName = elems.get(2); 781 case 2: 782 mPhoneticGivenName = elems.get(1); 783 default: 784 mPhoneticFamilyName = elems.get(0); 785 } 786 } 787 addProperty(Property property)788 public void addProperty(Property property) { 789 String propName = property.mPropertyName; 790 final Map<String, Collection<String>> paramMap = property.mParameterMap; 791 final List<String> propValueList = property.mPropertyValueList; 792 byte[] propBytes = property.mPropertyBytes; 793 794 if (propValueList.size() == 0) { 795 return; 796 } 797 final String propValue = listToString(propValueList).trim(); 798 799 if (propName.equals("VERSION")) { 800 // vCard version. Ignore this. 801 } else if (propName.equals("FN")) { 802 mFullName = propValue; 803 } else if (propName.equals("NAME") && mFullName == null) { 804 // Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not 805 // actually exist in the real vCard data, does not exist. 806 mFullName = propValue; 807 } else if (propName.equals("N")) { 808 handleNProperty(propValueList); 809 } else if (propName.equals("SORT-STRING")) { 810 mPhoneticFullName = propValue; 811 } else if (propName.equals("NICKNAME") || propName.equals("X-NICKNAME")) { 812 addNickName(propValue); 813 } else if (propName.equals("SOUND")) { 814 Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE); 815 if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_X_IRMC_N)) { 816 handlePhoneticNameFromSound(propValueList); 817 } else { 818 // Ignore this field since Android cannot understand what it is. 819 } 820 } else if (propName.equals("ADR")) { 821 boolean valuesAreAllEmpty = true; 822 for (String value : propValueList) { 823 if (value.length() > 0) { 824 valuesAreAllEmpty = false; 825 break; 826 } 827 } 828 if (valuesAreAllEmpty) { 829 return; 830 } 831 832 int type = -1; 833 String label = ""; 834 boolean isPrimary = false; 835 Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE); 836 if (typeCollection != null) { 837 for (String typeString : typeCollection) { 838 typeString = typeString.toUpperCase(); 839 if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Address) { 840 // Only first "PREF" is considered. 841 mPrefIsSet_Address = true; 842 isPrimary = true; 843 } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) { 844 type = StructuredPostal.TYPE_HOME; 845 label = ""; 846 } else if (typeString.equals(Constants.ATTR_TYPE_WORK) || 847 typeString.equalsIgnoreCase("COMPANY")) { 848 // "COMPANY" seems emitted by Windows Mobile, which is not 849 // specifically supported by vCard 2.1. We assume this is same 850 // as "WORK". 851 type = StructuredPostal.TYPE_WORK; 852 label = ""; 853 } else if (typeString.equals("PARCEL") || 854 typeString.equals("DOM") || 855 typeString.equals("INTL")) { 856 // We do not have any appropriate way to store this information. 857 } else { 858 if (typeString.startsWith("X-") && type < 0) { 859 typeString = typeString.substring(2); 860 } 861 // vCard 3.0 allows iana-token. Also some vCard 2.1 exporters 862 // emit non-standard types. We do not handle their values now. 863 type = StructuredPostal.TYPE_CUSTOM; 864 label = typeString; 865 } 866 } 867 } 868 // We use "HOME" as default 869 if (type < 0) { 870 type = StructuredPostal.TYPE_HOME; 871 } 872 873 addPostal(type, propValueList, label, isPrimary); 874 } else if (propName.equals("EMAIL")) { 875 int type = -1; 876 String label = null; 877 boolean isPrimary = false; 878 Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE); 879 if (typeCollection != null) { 880 for (String typeString : typeCollection) { 881 typeString = typeString.toUpperCase(); 882 if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Email) { 883 // Only first "PREF" is considered. 884 mPrefIsSet_Email = true; 885 isPrimary = true; 886 } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) { 887 type = Email.TYPE_HOME; 888 } else if (typeString.equals(Constants.ATTR_TYPE_WORK)) { 889 type = Email.TYPE_WORK; 890 } else if (typeString.equals(Constants.ATTR_TYPE_CELL)) { 891 type = Email.TYPE_MOBILE; 892 } else { 893 if (typeString.startsWith("X-") && type < 0) { 894 typeString = typeString.substring(2); 895 } 896 // vCard 3.0 allows iana-token. 897 // We may have INTERNET (specified in vCard spec), 898 // SCHOOL, etc. 899 type = Email.TYPE_CUSTOM; 900 label = typeString; 901 } 902 } 903 } 904 if (type < 0) { 905 type = Email.TYPE_OTHER; 906 } 907 addEmail(type, propValue, label, isPrimary); 908 } else if (propName.equals("ORG")) { 909 // vCard specification does not specify other types. 910 int type = Organization.TYPE_WORK; 911 boolean isPrimary = false; 912 913 Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE); 914 if (typeCollection != null) { 915 for (String typeString : typeCollection) { 916 if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Organization) { 917 // vCard specification officially does not have PREF in ORG. 918 // This is just for safety. 919 mPrefIsSet_Organization = true; 920 isPrimary = true; 921 } 922 } 923 } 924 925 StringBuilder builder = new StringBuilder(); 926 for (Iterator<String> iter = propValueList.iterator(); iter.hasNext();) { 927 builder.append(iter.next()); 928 if (iter.hasNext()) { 929 builder.append(' '); 930 } 931 } 932 addOrganization(type, builder.toString(), "", isPrimary); 933 } else if (propName.equals("TITLE")) { 934 setPosition(propValue); 935 } else if (propName.equals("ROLE")) { 936 setPosition(propValue); 937 } else if (propName.equals("PHOTO") || propName.equals("LOGO")) { 938 String formatName = null; 939 Collection<String> typeCollection = paramMap.get("TYPE"); 940 if (typeCollection != null) { 941 formatName = typeCollection.iterator().next(); 942 } 943 Collection<String> paramMapValue = paramMap.get("VALUE"); 944 if (paramMapValue != null && paramMapValue.contains("URL")) { 945 // Currently we do not have appropriate example for testing this case. 946 } else { 947 addPhotoBytes(formatName, propBytes); 948 } 949 } else if (propName.equals("TEL")) { 950 Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE); 951 Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection); 952 final int type; 953 final String label; 954 if (typeObject instanceof Integer) { 955 type = (Integer)typeObject; 956 label = null; 957 } else { 958 type = Phone.TYPE_CUSTOM; 959 label = typeObject.toString(); 960 } 961 962 final boolean isPrimary; 963 if (!mPrefIsSet_Phone && typeCollection != null && 964 typeCollection.contains(Constants.ATTR_TYPE_PREF)) { 965 mPrefIsSet_Phone = true; 966 isPrimary = true; 967 } else { 968 isPrimary = false; 969 } 970 addPhone(type, propValue, label, isPrimary); 971 } else if (propName.equals(Constants.PROPERTY_X_SKYPE_PSTNNUMBER)) { 972 // The phone number available via Skype. 973 Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE); 974 // XXX: should use TYPE_CUSTOM + the label "Skype"? (which may need localization) 975 int type = Phone.TYPE_OTHER; 976 final String label = null; 977 final boolean isPrimary; 978 if (!mPrefIsSet_Phone && typeCollection != null && 979 typeCollection.contains(Constants.ATTR_TYPE_PREF)) { 980 mPrefIsSet_Phone = true; 981 isPrimary = true; 982 } else { 983 isPrimary = false; 984 } 985 addPhone(type, propValue, label, isPrimary); 986 } else if (sImMap.containsKey(propName)){ 987 int type = sImMap.get(propName); 988 boolean isPrimary = false; 989 final Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE); 990 if (typeCollection != null) { 991 for (String typeString : typeCollection) { 992 if (typeString.equals(Constants.ATTR_TYPE_PREF)) { 993 isPrimary = true; 994 } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_HOME)) { 995 type = Phone.TYPE_HOME; 996 } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_WORK)) { 997 type = Phone.TYPE_WORK; 998 } 999 } 1000 } 1001 if (type < 0) { 1002 type = Phone.TYPE_HOME; 1003 } 1004 addIm(type, propValue, null, isPrimary); 1005 } else if (propName.equals("NOTE")) { 1006 addNote(propValue); 1007 } else if (propName.equals("URL")) { 1008 if (mWebsiteList == null) { 1009 mWebsiteList = new ArrayList<String>(1); 1010 } 1011 mWebsiteList.add(propValue); 1012 } else if (propName.equals("X-PHONETIC-FIRST-NAME")) { 1013 mPhoneticGivenName = propValue; 1014 } else if (propName.equals("X-PHONETIC-MIDDLE-NAME")) { 1015 mPhoneticMiddleName = propValue; 1016 } else if (propName.equals("X-PHONETIC-LAST-NAME")) { 1017 mPhoneticFamilyName = propValue; 1018 } else if (propName.equals("BDAY")) { 1019 mBirthday = propValue; 1020 /*} else if (propName.equals("REV")) { 1021 // Revision of this VCard entry. I think we can ignore this. 1022 } else if (propName.equals("UID")) { 1023 } else if (propName.equals("KEY")) { 1024 // Type is X509 or PGP? I don't know how to handle this... 1025 } else if (propName.equals("MAILER")) { 1026 } else if (propName.equals("TZ")) { 1027 } else if (propName.equals("GEO")) { 1028 } else if (propName.equals("CLASS")) { 1029 // vCard 3.0 only. 1030 // e.g. CLASS:CONFIDENTIAL 1031 } else if (propName.equals("PROFILE")) { 1032 // VCard 3.0 only. Must be "VCARD". I think we can ignore this. 1033 } else if (propName.equals("CATEGORIES")) { 1034 // VCard 3.0 only. 1035 // e.g. CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY 1036 } else if (propName.equals("SOURCE")) { 1037 // VCard 3.0 only. 1038 } else if (propName.equals("PRODID")) { 1039 // VCard 3.0 only. 1040 // To specify the identifier for the product that created 1041 // the vCard object.*/ 1042 } else { 1043 // Unknown X- words and IANA token. 1044 } 1045 } 1046 1047 /** 1048 * Construct the display name. The constructed data must not be null. 1049 */ constructDisplayName()1050 private void constructDisplayName() { 1051 if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) { 1052 StringBuilder builder = new StringBuilder(); 1053 List<String> nameList; 1054 switch (VCardConfig.getNameOrderType(mVCardType)) { 1055 case VCardConfig.NAME_ORDER_JAPANESE: 1056 if (VCardUtils.containsOnlyPrintableAscii(mFamilyName) && 1057 VCardUtils.containsOnlyPrintableAscii(mGivenName)) { 1058 nameList = Arrays.asList(mPrefix, mGivenName, mMiddleName, mFamilyName, mSuffix); 1059 } else { 1060 nameList = Arrays.asList(mPrefix, mFamilyName, mMiddleName, mGivenName, mSuffix); 1061 } 1062 break; 1063 case VCardConfig.NAME_ORDER_EUROPE: 1064 nameList = Arrays.asList(mPrefix, mMiddleName, mGivenName, mFamilyName, mSuffix); 1065 break; 1066 default: 1067 nameList = Arrays.asList(mPrefix, mGivenName, mMiddleName, mFamilyName, mSuffix); 1068 break; 1069 } 1070 boolean first = true; 1071 for (String namePart : nameList) { 1072 if (!TextUtils.isEmpty(namePart)) { 1073 if (first) { 1074 first = false; 1075 } else { 1076 builder.append(' '); 1077 } 1078 builder.append(namePart); 1079 } 1080 } 1081 mDisplayName = builder.toString(); 1082 } else if (!TextUtils.isEmpty(mFullName)) { 1083 mDisplayName = mFullName; 1084 } else if (!(TextUtils.isEmpty(mPhoneticFamilyName) && 1085 TextUtils.isEmpty(mPhoneticGivenName))) { 1086 mDisplayName = VCardUtils.constructNameFromElements(mVCardType, 1087 mPhoneticFamilyName, mPhoneticMiddleName, mPhoneticGivenName); 1088 } else if (mEmailList != null && mEmailList.size() > 0) { 1089 mDisplayName = mEmailList.get(0).data; 1090 } else if (mPhoneList != null && mPhoneList.size() > 0) { 1091 mDisplayName = mPhoneList.get(0).data; 1092 } else if (mPostalList != null && mPostalList.size() > 0) { 1093 mDisplayName = mPostalList.get(0).getFormattedAddress(mVCardType); 1094 } 1095 1096 if (mDisplayName == null) { 1097 mDisplayName = ""; 1098 } 1099 } 1100 1101 /** 1102 * Consolidate several fielsds (like mName) using name candidates, 1103 */ consolidateFields()1104 public void consolidateFields() { 1105 constructDisplayName(); 1106 1107 if (mPhoneticFullName != null) { 1108 mPhoneticFullName = mPhoneticFullName.trim(); 1109 } 1110 1111 // If there is no "PREF", we choose the first entries as primary. 1112 if (!mPrefIsSet_Phone && mPhoneList != null && mPhoneList.size() > 0) { 1113 mPhoneList.get(0).isPrimary = true; 1114 } 1115 1116 if (!mPrefIsSet_Address && mPostalList != null && mPostalList.size() > 0) { 1117 mPostalList.get(0).isPrimary = true; 1118 } 1119 if (!mPrefIsSet_Email && mEmailList != null && mEmailList.size() > 0) { 1120 mEmailList.get(0).isPrimary = true; 1121 } 1122 if (!mPrefIsSet_Organization && mOrganizationList != null && mOrganizationList.size() > 0) { 1123 mOrganizationList.get(0).isPrimary = true; 1124 } 1125 } 1126 1127 // From GoogleSource.java in Contacts app. 1128 private static final String ACCOUNT_TYPE_GOOGLE = "com.google"; 1129 private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts"; 1130 pushIntoContentResolver(ContentResolver resolver)1131 public void pushIntoContentResolver(ContentResolver resolver) { 1132 ArrayList<ContentProviderOperation> operationList = 1133 new ArrayList<ContentProviderOperation>(); 1134 ContentProviderOperation.Builder builder = 1135 ContentProviderOperation.newInsert(RawContacts.CONTENT_URI); 1136 String myGroupsId = null; 1137 if (mAccount != null) { 1138 builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name); 1139 builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type); 1140 1141 // Assume that caller side creates this group if it does not exist. 1142 // TODO: refactor this code along with the change in GoogleSource.java 1143 if (ACCOUNT_TYPE_GOOGLE.equals(mAccount.type)) { 1144 final Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] { 1145 Groups.SOURCE_ID }, 1146 Groups.TITLE + "=?", new String[] { 1147 GOOGLE_MY_CONTACTS_GROUP }, null); 1148 try { 1149 if (cursor != null && cursor.moveToFirst()) { 1150 myGroupsId = cursor.getString(0); 1151 } 1152 } finally { 1153 if (cursor != null) { 1154 cursor.close(); 1155 } 1156 } 1157 } 1158 } else { 1159 builder.withValue(RawContacts.ACCOUNT_NAME, null); 1160 builder.withValue(RawContacts.ACCOUNT_TYPE, null); 1161 } 1162 operationList.add(builder.build()); 1163 1164 { 1165 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 1166 builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0); 1167 builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); 1168 1169 builder.withValue(StructuredName.GIVEN_NAME, mGivenName); 1170 builder.withValue(StructuredName.FAMILY_NAME, mFamilyName); 1171 builder.withValue(StructuredName.MIDDLE_NAME, mMiddleName); 1172 builder.withValue(StructuredName.PREFIX, mPrefix); 1173 builder.withValue(StructuredName.SUFFIX, mSuffix); 1174 1175 builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGivenName); 1176 builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamilyName); 1177 builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddleName); 1178 1179 builder.withValue(StructuredName.DISPLAY_NAME, getDisplayName()); 1180 operationList.add(builder.build()); 1181 } 1182 1183 if (mNickNameList != null && mNickNameList.size() > 0) { 1184 boolean first = true; 1185 for (String nickName : mNickNameList) { 1186 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 1187 builder.withValueBackReference(Nickname.RAW_CONTACT_ID, 0); 1188 builder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE); 1189 1190 builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT); 1191 builder.withValue(Nickname.NAME, nickName); 1192 if (first) { 1193 builder.withValue(Data.IS_PRIMARY, 1); 1194 first = false; 1195 } 1196 operationList.add(builder.build()); 1197 } 1198 } 1199 1200 if (mPhoneList != null) { 1201 for (PhoneData phoneData : mPhoneList) { 1202 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 1203 builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0); 1204 builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 1205 1206 builder.withValue(Phone.TYPE, phoneData.type); 1207 if (phoneData.type == Phone.TYPE_CUSTOM) { 1208 builder.withValue(Phone.LABEL, phoneData.label); 1209 } 1210 builder.withValue(Phone.NUMBER, phoneData.data); 1211 if (phoneData.isPrimary) { 1212 builder.withValue(Data.IS_PRIMARY, 1); 1213 } 1214 operationList.add(builder.build()); 1215 } 1216 } 1217 1218 if (mOrganizationList != null) { 1219 boolean first = true; 1220 for (OrganizationData organizationData : mOrganizationList) { 1221 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 1222 builder.withValueBackReference(Organization.RAW_CONTACT_ID, 0); 1223 builder.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE); 1224 1225 // Currently, we do not use TYPE_CUSTOM. 1226 builder.withValue(Organization.TYPE, organizationData.type); 1227 builder.withValue(Organization.COMPANY, organizationData.companyName); 1228 builder.withValue(Organization.TITLE, organizationData.positionName); 1229 if (first) { 1230 builder.withValue(Data.IS_PRIMARY, 1); 1231 } 1232 operationList.add(builder.build()); 1233 } 1234 } 1235 1236 if (mEmailList != null) { 1237 for (EmailData emailData : mEmailList) { 1238 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 1239 builder.withValueBackReference(Email.RAW_CONTACT_ID, 0); 1240 builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); 1241 1242 builder.withValue(Email.TYPE, emailData.type); 1243 if (emailData.type == Email.TYPE_CUSTOM) { 1244 builder.withValue(Email.LABEL, emailData.label); 1245 } 1246 builder.withValue(Email.DATA, emailData.data); 1247 if (emailData.isPrimary) { 1248 builder.withValue(Data.IS_PRIMARY, 1); 1249 } 1250 operationList.add(builder.build()); 1251 } 1252 } 1253 1254 if (mPostalList != null) { 1255 for (PostalData postalData : mPostalList) { 1256 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 1257 VCardUtils.insertStructuredPostalDataUsingContactsStruct( 1258 mVCardType, builder, postalData); 1259 operationList.add(builder.build()); 1260 } 1261 } 1262 1263 if (mImList != null) { 1264 for (ImData imData : mImList) { 1265 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 1266 builder.withValueBackReference(Im.RAW_CONTACT_ID, 0); 1267 builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE); 1268 1269 builder.withValue(Im.TYPE, imData.type); 1270 if (imData.type == Im.TYPE_CUSTOM) { 1271 builder.withValue(Im.LABEL, imData.label); 1272 } 1273 builder.withValue(Im.DATA, imData.data); 1274 if (imData.isPrimary) { 1275 builder.withValue(Data.IS_PRIMARY, 1); 1276 } 1277 } 1278 } 1279 1280 if (mNoteList != null) { 1281 for (String note : mNoteList) { 1282 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 1283 builder.withValueBackReference(Note.RAW_CONTACT_ID, 0); 1284 builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE); 1285 1286 builder.withValue(Note.NOTE, note); 1287 operationList.add(builder.build()); 1288 } 1289 } 1290 1291 if (mPhotoList != null) { 1292 boolean first = true; 1293 for (PhotoData photoData : mPhotoList) { 1294 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 1295 builder.withValueBackReference(Photo.RAW_CONTACT_ID, 0); 1296 builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); 1297 builder.withValue(Photo.PHOTO, photoData.photoBytes); 1298 if (first) { 1299 builder.withValue(Data.IS_PRIMARY, 1); 1300 first = false; 1301 } 1302 operationList.add(builder.build()); 1303 } 1304 } 1305 1306 if (mWebsiteList != null) { 1307 for (String website : mWebsiteList) { 1308 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 1309 builder.withValueBackReference(Website.RAW_CONTACT_ID, 0); 1310 builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE); 1311 builder.withValue(Website.URL, website); 1312 // There's no information about the type of URL in vCard. 1313 // We use TYPE_HOME for safety. 1314 builder.withValue(Website.TYPE, Website.TYPE_HOME); 1315 operationList.add(builder.build()); 1316 } 1317 } 1318 1319 if (!TextUtils.isEmpty(mBirthday)) { 1320 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 1321 builder.withValueBackReference(Event.RAW_CONTACT_ID, 0); 1322 builder.withValue(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE); 1323 builder.withValue(Event.START_DATE, mBirthday); 1324 builder.withValue(Event.TYPE, Event.TYPE_BIRTHDAY); 1325 operationList.add(builder.build()); 1326 } 1327 1328 if (myGroupsId != null) { 1329 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 1330 builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0); 1331 builder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 1332 builder.withValue(GroupMembership.GROUP_SOURCE_ID, myGroupsId); 1333 operationList.add(builder.build()); 1334 } 1335 1336 try { 1337 resolver.applyBatch(ContactsContract.AUTHORITY, operationList); 1338 } catch (RemoteException e) { 1339 Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage())); 1340 } catch (OperationApplicationException e) { 1341 Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage())); 1342 } 1343 } 1344 isIgnorable()1345 public boolean isIgnorable() { 1346 return getDisplayName().length() == 0; 1347 } 1348 listToString(List<String> list)1349 private String listToString(List<String> list){ 1350 final int size = list.size(); 1351 if (size > 1) { 1352 StringBuilder builder = new StringBuilder(); 1353 int i = 0; 1354 for (String type : list) { 1355 builder.append(type); 1356 if (i < size - 1) { 1357 builder.append(";"); 1358 } 1359 } 1360 return builder.toString(); 1361 } else if (size == 1) { 1362 return list.get(0); 1363 } else { 1364 return ""; 1365 } 1366 } 1367 } 1368