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