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