VCardEntry.java revision d3162582852917b4d19d49a36d82d430349d5272
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) {
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 = (VCardConfig.isJapaneseDevice(mVCardType) ?
504                    PhoneNumberUtils.FORMAT_JAPAN : PhoneNumberUtils.FORMAT_NANP);
505            formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType);
506        }
507        PhoneData phoneData = new PhoneData(type, formattedNumber, label, isPrimary);
508        mPhoneList.add(phoneData);
509    }
510
511    private void addNickName(final String nickName) {
512        if (mNickNameList == null) {
513            mNickNameList = new ArrayList<String>();
514        }
515        mNickNameList.add(nickName);
516    }
517
518    private void addEmail(int type, String data, String label, boolean isPrimary){
519        if (mEmailList == null) {
520            mEmailList = new ArrayList<EmailData>();
521        }
522        mEmailList.add(new EmailData(type, data, label, isPrimary));
523    }
524
525    private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){
526        if (mPostalList == null) {
527            mPostalList = new ArrayList<PostalData>(0);
528        }
529        mPostalList.add(new PostalData(type, propValueList, label, isPrimary));
530    }
531
532    /**
533     * Should be called via {@link #handleOrgValue(int, List, boolean)} or
534     * {@link #handleTitleValue(String)}.
535     */
536    private void addNewOrganization(int type, final String companyName,
537            final String departmentName,
538            final String titleName, boolean isPrimary) {
539        if (mOrganizationList == null) {
540            mOrganizationList = new ArrayList<OrganizationData>();
541        }
542        mOrganizationList.add(new OrganizationData(type, companyName,
543                departmentName, titleName, isPrimary));
544    }
545
546    private static final List<String> sEmptyList =
547            Collections.unmodifiableList(new ArrayList<String>(0));
548
549    /**
550     * Set "ORG" related values to the appropriate data. If there's more than one
551     * {@link OrganizationData} objects, this input data are attached to the last one which
552     * does not have valid values (not including empty but only null). If there's no
553     * {@link OrganizationData} object, a new {@link OrganizationData} is created,
554     * whose title is set to null.
555     */
556    private void handleOrgValue(final int type, List<String> orgList, boolean isPrimary) {
557        if (orgList == null) {
558            orgList = sEmptyList;
559        }
560        final String companyName;
561        final String departmentName;
562        final int size = orgList.size();
563        switch (size) {
564            case 0: {
565                companyName = "";
566                departmentName = null;
567                break;
568            }
569            case 1: {
570                companyName = orgList.get(0);
571                departmentName = null;
572                break;
573            }
574            default: {  // More than 1.
575                companyName = orgList.get(0);
576                // We're not sure which is the correct string for department.
577                // In order to keep all the data, concatinate the rest of elements.
578                StringBuilder builder = new StringBuilder();
579                for (int i = 1; i < size; i++) {
580                    if (i > 1) {
581                        builder.append(' ');
582                    }
583                    builder.append(orgList.get(i));
584                }
585                departmentName = builder.toString();
586            }
587        }
588        if (mOrganizationList == null) {
589            // Create new first organization entry, with "null" title which may be
590            // added via handleTitleValue().
591            addNewOrganization(type, companyName, departmentName, null, isPrimary);
592            return;
593        }
594        for (OrganizationData organizationData : mOrganizationList) {
595            // Not use TextUtils.isEmpty() since ORG was set but the elements might be empty.
596            // e.g. "ORG;PREF:;" -> Both companyName and departmentName become empty but not null.
597            if (organizationData.companyName == null &&
598                    organizationData.departmentName == null) {
599                // Probably the "TITLE" property comes before the "ORG" property via
600                // handleTitleLine().
601                organizationData.companyName = companyName;
602                organizationData.departmentName = departmentName;
603                organizationData.isPrimary = isPrimary;
604                return;
605            }
606        }
607        // No OrganizatioData is available. Create another one, with "null" title, which may be
608        // added via handleTitleValue().
609        addNewOrganization(type, companyName, departmentName, null, isPrimary);
610    }
611
612    /**
613     * Set "title" value to the appropriate data. If there's more than one
614     * OrganizationData objects, this input is attached to the last one which does not
615     * have valid title value (not including empty but only null). If there's no
616     * OrganizationData object, a new OrganizationData is created, whose company name is
617     * set to null.
618     */
619    private void handleTitleValue(final String title) {
620        if (mOrganizationList == null) {
621            // Create new first organization entry, with "null" other info, which may be
622            // added via handleOrgValue().
623            addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
624            return;
625        }
626        for (OrganizationData organizationData : mOrganizationList) {
627            if (organizationData.titleName == null) {
628                organizationData.titleName = title;
629                return;
630            }
631        }
632        // No Organization is available. Create another one, with "null" other info, which may be
633        // added via handleOrgValue().
634        addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
635    }
636
637    private void addIm(int protocol, String customProtocol, int type,
638            String propValue, boolean isPrimary) {
639        if (mImList == null) {
640            mImList = new ArrayList<ImData>();
641        }
642        mImList.add(new ImData(protocol, customProtocol, type, propValue, isPrimary));
643    }
644
645    private void addNote(final String note) {
646        if (mNoteList == null) {
647            mNoteList = new ArrayList<String>(1);
648        }
649        mNoteList.add(note);
650    }
651
652    private void addPhotoBytes(String formatName, byte[] photoBytes, boolean isPrimary) {
653        if (mPhotoList == null) {
654            mPhotoList = new ArrayList<PhotoData>(1);
655        }
656        final PhotoData photoData = new PhotoData(0, null, photoBytes, isPrimary);
657        mPhotoList.add(photoData);
658    }
659
660    @SuppressWarnings("fallthrough")
661    private void handleNProperty(List<String> elems) {
662        // Family, Given, Middle, Prefix, Suffix. (1 - 5)
663        int size;
664        if (elems == null || (size = elems.size()) < 1) {
665            return;
666        }
667        if (size > 5) {
668            size = 5;
669        }
670
671        switch (size) {
672            // fallthrough
673            case 5: mSuffix = elems.get(4);
674            case 4: mPrefix = elems.get(3);
675            case 3: mMiddleName = elems.get(2);
676            case 2: mGivenName = elems.get(1);
677            default: mFamilyName = elems.get(0);
678        }
679    }
680
681    /**
682     * Note: Some Japanese mobile phones use this field for phonetic name,
683     *       since vCard 2.1 does not have "SORT-STRING" type.
684     *       Also, in some cases, the field has some ';'s in it.
685     *       Assume the ';' means the same meaning in N property
686     */
687    @SuppressWarnings("fallthrough")
688    private void handlePhoneticNameFromSound(List<String> elems) {
689        if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
690                TextUtils.isEmpty(mPhoneticMiddleName) &&
691                TextUtils.isEmpty(mPhoneticGivenName))) {
692            // This means the other properties like "X-PHONETIC-FIRST-NAME" was already found.
693            // Ignore "SOUND;X-IRMC-N".
694            return;
695        }
696
697        int size;
698        if (elems == null || (size = elems.size()) < 1) {
699            return;
700        }
701
702        // Assume that the order is "Family, Given, Middle".
703        // This is not from specification but mere assumption. Some Japanese phones use this order.
704        if (size > 3) {
705            size = 3;
706        }
707
708        if (elems.get(0).length() > 0) {
709            boolean onlyFirstElemIsNonEmpty = true;
710            for (int i = 1; i < size; i++) {
711                if (elems.get(i).length() > 0) {
712                    onlyFirstElemIsNonEmpty = false;
713                    break;
714                }
715            }
716            if (onlyFirstElemIsNonEmpty) {
717                final String[] namesArray = elems.get(0).split(" ");
718                final int nameArrayLength = namesArray.length;
719                if (nameArrayLength == 3) {
720                    // Assume the string is "Family Middle Given".
721                    mPhoneticFamilyName = namesArray[0];
722                    mPhoneticMiddleName = namesArray[1];
723                    mPhoneticGivenName = namesArray[2];
724                } else if (nameArrayLength == 2) {
725                    // Assume the string is "Family Given" based on the Japanese mobile
726                    // phones' preference.
727                    mPhoneticFamilyName = namesArray[0];
728                    mPhoneticGivenName = namesArray[1];
729                } else {
730                    mPhoneticFullName = elems.get(0);
731                }
732                return;
733            }
734        }
735
736        switch (size) {
737            // fallthrough
738            case 3: mPhoneticMiddleName = elems.get(2);
739            case 2: mPhoneticGivenName = elems.get(1);
740            default: mPhoneticFamilyName = elems.get(0);
741        }
742    }
743
744    public void addProperty(final Property property) {
745        final String propName = property.mPropertyName;
746        final Map<String, Collection<String>> paramMap = property.mParameterMap;
747        final List<String> propValueList = property.mPropertyValueList;
748        byte[] propBytes = property.mPropertyBytes;
749
750        if (propValueList.size() == 0) {
751            return;
752        }
753        final String propValue = listToString(propValueList).trim();
754
755        if (propName.equals(VCardConstants.PROPERTY_VERSION)) {
756            // vCard version. Ignore this.
757        } else if (propName.equals(VCardConstants.PROPERTY_FN)) {
758            mFullName = propValue;
759        } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFullName == null) {
760            // Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not
761            // actually exist in the real vCard data, does not exist.
762            mFullName = propValue;
763        } else if (propName.equals(VCardConstants.PROPERTY_N)) {
764            handleNProperty(propValueList);
765        } else if (propName.equals(VCardConstants.PROPERTY_SORT_STRING)) {
766            mPhoneticFullName = propValue;
767        } else if (propName.equals(VCardConstants.PROPERTY_NICKNAME) ||
768                propName.equals(VCardConstants.ImportOnly.PROPERTY_X_NICKNAME)) {
769            addNickName(propValue);
770        } else if (propName.equals(VCardConstants.PROPERTY_SOUND)) {
771            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
772            if (typeCollection != null
773                    && typeCollection.contains(VCardConstants.PARAM_TYPE_X_IRMC_N)) {
774                // As of 2009-10-08, Parser side does not split a property value into separated
775                // values using ';' (in other words, propValueList.size() == 1),
776                // which is correct behavior from the view of vCard 2.1.
777                // But we want it to be separated, so do the separation here.
778                final List<String> phoneticNameList =
779                        VCardUtils.constructListFromValue(propValue,
780                                VCardConfig.isV30(mVCardType));
781                handlePhoneticNameFromSound(phoneticNameList);
782            } else {
783                // Ignore this field since Android cannot understand what it is.
784            }
785        } else if (propName.equals(VCardConstants.PROPERTY_ADR)) {
786            boolean valuesAreAllEmpty = true;
787            for (String value : propValueList) {
788                if (value.length() > 0) {
789                    valuesAreAllEmpty = false;
790                    break;
791                }
792            }
793            if (valuesAreAllEmpty) {
794                return;
795            }
796
797            int type = -1;
798            String label = "";
799            boolean isPrimary = false;
800            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
801            if (typeCollection != null) {
802                for (String typeString : typeCollection) {
803                    typeString = typeString.toUpperCase();
804                    if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
805                        isPrimary = true;
806                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_HOME)) {
807                        type = StructuredPostal.TYPE_HOME;
808                        label = "";
809                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_WORK) ||
810                            typeString.equalsIgnoreCase(VCardConstants.PARAM_EXTRA_TYPE_COMPANY)) {
811                        // "COMPANY" seems emitted by Windows Mobile, which is not
812                        // specifically supported by vCard 2.1. We assume this is same
813                        // as "WORK".
814                        type = StructuredPostal.TYPE_WORK;
815                        label = "";
816                    } else if (typeString.equals(VCardConstants.PARAM_ADR_TYPE_PARCEL) ||
817                            typeString.equals(VCardConstants.PARAM_ADR_TYPE_DOM) ||
818                            typeString.equals(VCardConstants.PARAM_ADR_TYPE_INTL)) {
819                        // We do not have any appropriate way to store this information.
820                    } else {
821                        if (typeString.startsWith("X-") && type < 0) {
822                            typeString = typeString.substring(2);
823                        }
824                        // vCard 3.0 allows iana-token. Also some vCard 2.1 exporters
825                        // emit non-standard types. We do not handle their values now.
826                        type = StructuredPostal.TYPE_CUSTOM;
827                        label = typeString;
828                    }
829                }
830            }
831            // We use "HOME" as default
832            if (type < 0) {
833                type = StructuredPostal.TYPE_HOME;
834            }
835
836            addPostal(type, propValueList, label, isPrimary);
837        } else if (propName.equals(VCardConstants.PROPERTY_EMAIL)) {
838            int type = -1;
839            String label = null;
840            boolean isPrimary = false;
841            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
842            if (typeCollection != null) {
843                for (String typeString : typeCollection) {
844                    typeString = typeString.toUpperCase();
845                    if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
846                        isPrimary = true;
847                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_HOME)) {
848                        type = Email.TYPE_HOME;
849                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_WORK)) {
850                        type = Email.TYPE_WORK;
851                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_CELL)) {
852                        type = Email.TYPE_MOBILE;
853                    } else {
854                        if (typeString.startsWith("X-") && type < 0) {
855                            typeString = typeString.substring(2);
856                        }
857                        // vCard 3.0 allows iana-token.
858                        // We may have INTERNET (specified in vCard spec),
859                        // SCHOOL, etc.
860                        type = Email.TYPE_CUSTOM;
861                        label = typeString;
862                    }
863                }
864            }
865            if (type < 0) {
866                type = Email.TYPE_OTHER;
867            }
868            addEmail(type, propValue, label, isPrimary);
869        } else if (propName.equals(VCardConstants.PROPERTY_ORG)) {
870            // vCard specification does not specify other types.
871            final int type = Organization.TYPE_WORK;
872            boolean isPrimary = false;
873            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
874            if (typeCollection != null) {
875                for (String typeString : typeCollection) {
876                    if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
877                        isPrimary = true;
878                    }
879                }
880            }
881            handleOrgValue(type, propValueList, isPrimary);
882        } else if (propName.equals(VCardConstants.PROPERTY_TITLE)) {
883            handleTitleValue(propValue);
884        } else if (propName.equals(VCardConstants.PROPERTY_ROLE)) {
885            // This conflicts with TITLE. Ignore for now...
886            // handleTitleValue(propValue);
887        } else if (propName.equals(VCardConstants.PROPERTY_PHOTO) ||
888                propName.equals(VCardConstants.PROPERTY_LOGO)) {
889            Collection<String> paramMapValue = paramMap.get("VALUE");
890            if (paramMapValue != null && paramMapValue.contains("URL")) {
891                // Currently we do not have appropriate example for testing this case.
892            } else {
893                final Collection<String> typeCollection = paramMap.get("TYPE");
894                String formatName = null;
895                boolean isPrimary = false;
896                if (typeCollection != null) {
897                    for (String typeValue : typeCollection) {
898                        if (VCardConstants.PARAM_TYPE_PREF.equals(typeValue)) {
899                            isPrimary = true;
900                        } else if (formatName == null){
901                            formatName = typeValue;
902                        }
903                    }
904                }
905                addPhotoBytes(formatName, propBytes, isPrimary);
906            }
907        } else if (propName.equals(VCardConstants.PROPERTY_TEL)) {
908            final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
909            final Object typeObject =
910                VCardUtils.getPhoneTypeFromStrings(typeCollection, propValue);
911            final int type;
912            final String label;
913            if (typeObject instanceof Integer) {
914                type = (Integer)typeObject;
915                label = null;
916            } else {
917                type = Phone.TYPE_CUSTOM;
918                label = typeObject.toString();
919            }
920
921            final boolean isPrimary;
922            if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
923                isPrimary = true;
924            } else {
925                isPrimary = false;
926            }
927            addPhone(type, propValue, label, isPrimary);
928        } else if (propName.equals(VCardConstants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
929            // The phone number available via Skype.
930            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
931            final int type = Phone.TYPE_OTHER;
932            final boolean isPrimary;
933            if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
934                isPrimary = true;
935            } else {
936                isPrimary = false;
937            }
938            addPhone(type, propValue, null, isPrimary);
939        } else if (sImMap.containsKey(propName)) {
940            final int protocol = sImMap.get(propName);
941            boolean isPrimary = false;
942            int type = -1;
943            final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
944            if (typeCollection != null) {
945                for (String typeString : typeCollection) {
946                    if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
947                        isPrimary = true;
948                    } else if (type < 0) {
949                        if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_HOME)) {
950                            type = Im.TYPE_HOME;
951                        } else if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_WORK)) {
952                            type = Im.TYPE_WORK;
953                        }
954                    }
955                }
956            }
957            if (type < 0) {
958                type = Phone.TYPE_HOME;
959            }
960            addIm(protocol, null, type, propValue, isPrimary);
961        } else if (propName.equals(VCardConstants.PROPERTY_NOTE)) {
962            addNote(propValue);
963        } else if (propName.equals(VCardConstants.PROPERTY_URL)) {
964            if (mWebsiteList == null) {
965                mWebsiteList = new ArrayList<String>(1);
966            }
967            mWebsiteList.add(propValue);
968        } else if (propName.equals(VCardConstants.PROPERTY_BDAY)) {
969            mBirthday = propValue;
970        } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
971            mPhoneticGivenName = propValue;
972        } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
973            mPhoneticMiddleName = propValue;
974        } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME)) {
975            mPhoneticFamilyName = propValue;
976        } else if (propName.equals(VCardConstants.PROPERTY_X_ANDROID_CUSTOM)) {
977            final List<String> customPropertyList =
978                VCardUtils.constructListFromValue(propValue,
979                        VCardConfig.isV30(mVCardType));
980            handleAndroidCustomProperty(customPropertyList);
981        /*} else if (propName.equals("REV")) {
982            // Revision of this VCard entry. I think we can ignore this.
983        } else if (propName.equals("UID")) {
984        } else if (propName.equals("KEY")) {
985            // Type is X509 or PGP? I don't know how to handle this...
986        } else if (propName.equals("MAILER")) {
987        } else if (propName.equals("TZ")) {
988        } else if (propName.equals("GEO")) {
989        } else if (propName.equals("CLASS")) {
990            // vCard 3.0 only.
991            // e.g. CLASS:CONFIDENTIAL
992        } else if (propName.equals("PROFILE")) {
993            // VCard 3.0 only. Must be "VCARD". I think we can ignore this.
994        } else if (propName.equals("CATEGORIES")) {
995            // VCard 3.0 only.
996            // e.g. CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY
997        } else if (propName.equals("SOURCE")) {
998            // VCard 3.0 only.
999        } else if (propName.equals("PRODID")) {
1000            // VCard 3.0 only.
1001            // To specify the identifier for the product that created
1002            // the vCard object.*/
1003        } else {
1004            // Unknown X- words and IANA token.
1005        }
1006    }
1007
1008    private void handleAndroidCustomProperty(final List<String> customPropertyList) {
1009        if (mAndroidCustomPropertyList == null) {
1010            mAndroidCustomPropertyList = new ArrayList<List<String>>();
1011        }
1012        mAndroidCustomPropertyList.add(customPropertyList);
1013    }
1014
1015    /**
1016     * Construct the display name. The constructed data must not be null.
1017     */
1018    private void constructDisplayName() {
1019        // FullName (created via "FN" or "NAME" field) is prefered.
1020        if (!TextUtils.isEmpty(mFullName)) {
1021            mDisplayName = mFullName;
1022        } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
1023            mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
1024                    mFamilyName, mMiddleName, mGivenName, mPrefix, mSuffix);
1025        } else if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
1026                TextUtils.isEmpty(mPhoneticGivenName))) {
1027            mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
1028                    mPhoneticFamilyName, mPhoneticMiddleName, mPhoneticGivenName);
1029        } else if (mEmailList != null && mEmailList.size() > 0) {
1030            mDisplayName = mEmailList.get(0).data;
1031        } else if (mPhoneList != null && mPhoneList.size() > 0) {
1032            mDisplayName = mPhoneList.get(0).data;
1033        } else if (mPostalList != null && mPostalList.size() > 0) {
1034            mDisplayName = mPostalList.get(0).getFormattedAddress(mVCardType);
1035        } else if (mOrganizationList != null && mOrganizationList.size() > 0) {
1036            mDisplayName = mOrganizationList.get(0).getFormattedString();
1037        }
1038
1039        if (mDisplayName == null) {
1040            mDisplayName = "";
1041        }
1042    }
1043
1044    /**
1045     * Consolidate several fielsds (like mName) using name candidates,
1046     */
1047    public void consolidateFields() {
1048        constructDisplayName();
1049
1050        if (mPhoneticFullName != null) {
1051            mPhoneticFullName = mPhoneticFullName.trim();
1052        }
1053    }
1054
1055    public Uri pushIntoContentResolver(ContentResolver resolver) {
1056        ArrayList<ContentProviderOperation> operationList =
1057            new ArrayList<ContentProviderOperation>();
1058        // After applying the batch the first result's Uri is returned so it is important that
1059        // the RawContact is the first operation that gets inserted into the list
1060        ContentProviderOperation.Builder builder =
1061            ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
1062        String myGroupsId = null;
1063        if (mAccount != null) {
1064            builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name);
1065            builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type);
1066
1067            // Assume that caller side creates this group if it does not exist.
1068            if (ACCOUNT_TYPE_GOOGLE.equals(mAccount.type)) {
1069                final Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] {
1070                        Groups.SOURCE_ID },
1071                        Groups.TITLE + "=?", new String[] {
1072                        GOOGLE_MY_CONTACTS_GROUP }, null);
1073                try {
1074                    if (cursor != null && cursor.moveToFirst()) {
1075                        myGroupsId = cursor.getString(0);
1076                    }
1077                } finally {
1078                    if (cursor != null) {
1079                        cursor.close();
1080                    }
1081                }
1082            }
1083        } else {
1084            builder.withValue(RawContacts.ACCOUNT_NAME, null);
1085            builder.withValue(RawContacts.ACCOUNT_TYPE, null);
1086        }
1087        operationList.add(builder.build());
1088
1089        if (!nameFieldsAreEmpty()) {
1090            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1091            builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
1092            builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
1093
1094            builder.withValue(StructuredName.GIVEN_NAME, mGivenName);
1095            builder.withValue(StructuredName.FAMILY_NAME, mFamilyName);
1096            builder.withValue(StructuredName.MIDDLE_NAME, mMiddleName);
1097            builder.withValue(StructuredName.PREFIX, mPrefix);
1098            builder.withValue(StructuredName.SUFFIX, mSuffix);
1099
1100            if (!(TextUtils.isEmpty(mPhoneticGivenName)
1101                    && TextUtils.isEmpty(mPhoneticFamilyName)
1102                    && TextUtils.isEmpty(mPhoneticMiddleName))) {
1103                builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGivenName);
1104                builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamilyName);
1105                builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddleName);
1106            } else if (!TextUtils.isEmpty(mPhoneticFullName)) {
1107                builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticFullName);
1108            }
1109
1110            builder.withValue(StructuredName.DISPLAY_NAME, getDisplayName());
1111            operationList.add(builder.build());
1112        }
1113
1114        if (mNickNameList != null && mNickNameList.size() > 0) {
1115            for (String nickName : mNickNameList) {
1116                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1117                builder.withValueBackReference(Nickname.RAW_CONTACT_ID, 0);
1118                builder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
1119                builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT);
1120                builder.withValue(Nickname.NAME, nickName);
1121                operationList.add(builder.build());
1122            }
1123        }
1124
1125        if (mPhoneList != null) {
1126            for (PhoneData phoneData : mPhoneList) {
1127                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1128                builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
1129                builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
1130
1131                builder.withValue(Phone.TYPE, phoneData.type);
1132                if (phoneData.type == Phone.TYPE_CUSTOM) {
1133                    builder.withValue(Phone.LABEL, phoneData.label);
1134                }
1135                builder.withValue(Phone.NUMBER, phoneData.data);
1136                if (phoneData.isPrimary) {
1137                    builder.withValue(Phone.IS_PRIMARY, 1);
1138                }
1139                operationList.add(builder.build());
1140            }
1141        }
1142
1143        if (mOrganizationList != null) {
1144            for (OrganizationData organizationData : mOrganizationList) {
1145                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1146                builder.withValueBackReference(Organization.RAW_CONTACT_ID, 0);
1147                builder.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
1148                builder.withValue(Organization.TYPE, organizationData.type);
1149                if (organizationData.companyName != null) {
1150                    builder.withValue(Organization.COMPANY, organizationData.companyName);
1151                }
1152                if (organizationData.departmentName != null) {
1153                    builder.withValue(Organization.DEPARTMENT, organizationData.departmentName);
1154                }
1155                if (organizationData.titleName != null) {
1156                    builder.withValue(Organization.TITLE, organizationData.titleName);
1157                }
1158                if (organizationData.isPrimary) {
1159                    builder.withValue(Organization.IS_PRIMARY, 1);
1160                }
1161                operationList.add(builder.build());
1162            }
1163        }
1164
1165        if (mEmailList != null) {
1166            for (EmailData emailData : mEmailList) {
1167                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1168                builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
1169                builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
1170
1171                builder.withValue(Email.TYPE, emailData.type);
1172                if (emailData.type == Email.TYPE_CUSTOM) {
1173                    builder.withValue(Email.LABEL, emailData.label);
1174                }
1175                builder.withValue(Email.DATA, emailData.data);
1176                if (emailData.isPrimary) {
1177                    builder.withValue(Data.IS_PRIMARY, 1);
1178                }
1179                operationList.add(builder.build());
1180            }
1181        }
1182
1183        if (mPostalList != null) {
1184            for (PostalData postalData : mPostalList) {
1185                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1186                VCardUtils.insertStructuredPostalDataUsingContactsStruct(
1187                        mVCardType, builder, postalData);
1188                operationList.add(builder.build());
1189            }
1190        }
1191
1192        if (mImList != null) {
1193            for (ImData imData : mImList) {
1194                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1195                builder.withValueBackReference(Im.RAW_CONTACT_ID, 0);
1196                builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
1197                builder.withValue(Im.TYPE, imData.type);
1198                builder.withValue(Im.PROTOCOL, imData.protocol);
1199                if (imData.protocol == Im.PROTOCOL_CUSTOM) {
1200                    builder.withValue(Im.CUSTOM_PROTOCOL, imData.customProtocol);
1201                }
1202                if (imData.isPrimary) {
1203                    builder.withValue(Data.IS_PRIMARY, 1);
1204                }
1205            }
1206        }
1207
1208        if (mNoteList != null) {
1209            for (String note : mNoteList) {
1210                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1211                builder.withValueBackReference(Note.RAW_CONTACT_ID, 0);
1212                builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
1213                builder.withValue(Note.NOTE, note);
1214                operationList.add(builder.build());
1215            }
1216        }
1217
1218        if (mPhotoList != null) {
1219            for (PhotoData photoData : mPhotoList) {
1220                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1221                builder.withValueBackReference(Photo.RAW_CONTACT_ID, 0);
1222                builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
1223                builder.withValue(Photo.PHOTO, photoData.photoBytes);
1224                if (photoData.isPrimary) {
1225                    builder.withValue(Photo.IS_PRIMARY, 1);
1226                }
1227                operationList.add(builder.build());
1228            }
1229        }
1230
1231        if (mWebsiteList != null) {
1232            for (String website : mWebsiteList) {
1233                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1234                builder.withValueBackReference(Website.RAW_CONTACT_ID, 0);
1235                builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE);
1236                builder.withValue(Website.URL, website);
1237                // There's no information about the type of URL in vCard.
1238                // We use TYPE_HOMEPAGE for safety.
1239                builder.withValue(Website.TYPE, Website.TYPE_HOMEPAGE);
1240                operationList.add(builder.build());
1241            }
1242        }
1243
1244        if (!TextUtils.isEmpty(mBirthday)) {
1245            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1246            builder.withValueBackReference(Event.RAW_CONTACT_ID, 0);
1247            builder.withValue(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
1248            builder.withValue(Event.START_DATE, mBirthday);
1249            builder.withValue(Event.TYPE, Event.TYPE_BIRTHDAY);
1250            operationList.add(builder.build());
1251        }
1252
1253        if (mAndroidCustomPropertyList != null) {
1254            for (List<String> customPropertyList : mAndroidCustomPropertyList) {
1255                int size = customPropertyList.size();
1256                if (size < 2 || TextUtils.isEmpty(customPropertyList.get(0))) {
1257                    continue;
1258                } else if (size > VCardConstants.MAX_DATA_COLUMN + 1) {
1259                    size = VCardConstants.MAX_DATA_COLUMN + 1;
1260                    customPropertyList =
1261                        customPropertyList.subList(0, VCardConstants.MAX_DATA_COLUMN + 2);
1262                }
1263
1264                int i = 0;
1265                for (final String customPropertyValue : customPropertyList) {
1266                    if (i == 0) {
1267                        final String mimeType = customPropertyValue;
1268                        builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1269                        builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
1270                        builder.withValue(Data.MIMETYPE, mimeType);
1271                    } else {  // 1 <= i && i <= MAX_DATA_COLUMNS
1272                        if (!TextUtils.isEmpty(customPropertyValue)) {
1273                            builder.withValue("data" + i, customPropertyValue);
1274                        }
1275                    }
1276
1277                    i++;
1278                }
1279                operationList.add(builder.build());
1280            }
1281        }
1282
1283        if (myGroupsId != null) {
1284            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1285            builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
1286            builder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
1287            builder.withValue(GroupMembership.GROUP_SOURCE_ID, myGroupsId);
1288            operationList.add(builder.build());
1289        }
1290
1291        try {
1292            ContentProviderResult[] results = resolver.applyBatch(
1293                        ContactsContract.AUTHORITY, operationList);
1294            // the first result is always the raw_contact. return it's uri so
1295            // that it can be found later. do null checking for badly behaving
1296            // ContentResolvers
1297            return (results == null || results.length == 0 || results[0] == null)
1298                ? null
1299                : results[0].uri;
1300        } catch (RemoteException e) {
1301            Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
1302            return null;
1303        } catch (OperationApplicationException e) {
1304            Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
1305            return null;
1306        }
1307    }
1308
1309    public static VCardEntry buildFromResolver(ContentResolver resolver) {
1310        return buildFromResolver(resolver, Contacts.CONTENT_URI);
1311    }
1312
1313    public static VCardEntry buildFromResolver(ContentResolver resolver, Uri uri) {
1314
1315        return null;
1316    }
1317
1318    private boolean nameFieldsAreEmpty() {
1319        return (TextUtils.isEmpty(mFamilyName)
1320                && TextUtils.isEmpty(mMiddleName)
1321                && TextUtils.isEmpty(mGivenName)
1322                && TextUtils.isEmpty(mPrefix)
1323                && TextUtils.isEmpty(mSuffix)
1324                && TextUtils.isEmpty(mFullName)
1325                && TextUtils.isEmpty(mPhoneticFamilyName)
1326                && TextUtils.isEmpty(mPhoneticMiddleName)
1327                && TextUtils.isEmpty(mPhoneticGivenName)
1328                && TextUtils.isEmpty(mPhoneticFullName));
1329    }
1330
1331    public boolean isIgnorable() {
1332        return getDisplayName().length() == 0;
1333    }
1334
1335    private String listToString(List<String> list){
1336        final int size = list.size();
1337        if (size > 1) {
1338            StringBuilder builder = new StringBuilder();
1339            int i = 0;
1340            for (String type : list) {
1341                builder.append(type);
1342                if (i < size - 1) {
1343                    builder.append(";");
1344                }
1345            }
1346            return builder.toString();
1347        } else if (size == 1) {
1348            return list.get(0);
1349        } else {
1350            return "";
1351        }
1352    }
1353
1354    // All getter methods should be used carefully, since they may change
1355    // in the future as of 2009-10-05, on which I cannot be sure this structure
1356    // is completely consolidated.
1357    //
1358    // Also note that these getter methods should be used only after
1359    // all properties being pushed into this object. If not, incorrect
1360    // value will "be stored in the local cache and" be returned to you.
1361
1362    public String getFamilyName() {
1363        return mFamilyName;
1364    }
1365
1366    public String getGivenName() {
1367        return mGivenName;
1368    }
1369
1370    public String getMiddleName() {
1371        return mMiddleName;
1372    }
1373
1374    public String getPrefix() {
1375        return mPrefix;
1376    }
1377
1378    public String getSuffix() {
1379        return mSuffix;
1380    }
1381
1382    public String getFullName() {
1383        return mFullName;
1384    }
1385
1386    public String getPhoneticFamilyName() {
1387        return mPhoneticFamilyName;
1388    }
1389
1390    public String getPhoneticGivenName() {
1391        return mPhoneticGivenName;
1392    }
1393
1394    public String getPhoneticMiddleName() {
1395        return mPhoneticMiddleName;
1396    }
1397
1398    public String getPhoneticFullName() {
1399        return mPhoneticFullName;
1400    }
1401
1402    public final List<String> getNickNameList() {
1403        return mNickNameList;
1404    }
1405
1406    public String getBirthday() {
1407        return mBirthday;
1408    }
1409
1410    public final List<String> getNotes() {
1411        return mNoteList;
1412    }
1413
1414    public final List<PhoneData> getPhoneList() {
1415        return mPhoneList;
1416    }
1417
1418    public final List<EmailData> getEmailList() {
1419        return mEmailList;
1420    }
1421
1422    public final List<PostalData> getPostalList() {
1423        return mPostalList;
1424    }
1425
1426    public final List<OrganizationData> getOrganizationList() {
1427        return mOrganizationList;
1428    }
1429
1430    public final List<ImData> getImList() {
1431        return mImList;
1432    }
1433
1434    public final List<PhotoData> getPhotoList() {
1435        return mPhotoList;
1436    }
1437
1438    public final List<String> getWebsiteList() {
1439        return mWebsiteList;
1440    }
1441
1442    public String getDisplayName() {
1443        if (mDisplayName == null) {
1444            constructDisplayName();
1445        }
1446        return mDisplayName;
1447    }
1448}
1449