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