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