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