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