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