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