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