VCardEntry.java revision 7e25f5cbfbe433c6ffe06a17ef42c471baf07c93
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 = "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        ContentProviderOperation.Builder builder =
1059            ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
1060        String myGroupsId = null;
1061        if (mAccount != null) {
1062            builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name);
1063            builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type);
1064
1065            // Assume that caller side creates this group if it does not exist.
1066            if (ACCOUNT_TYPE_GOOGLE.equals(mAccount.type)) {
1067                final Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] {
1068                        Groups.SOURCE_ID },
1069                        Groups.TITLE + "=?", new String[] {
1070                        GOOGLE_MY_CONTACTS_GROUP }, null);
1071                try {
1072                    if (cursor != null && cursor.moveToFirst()) {
1073                        myGroupsId = cursor.getString(0);
1074                    }
1075                } finally {
1076                    if (cursor != null) {
1077                        cursor.close();
1078                    }
1079                }
1080            }
1081        } else {
1082            builder.withValue(RawContacts.ACCOUNT_NAME, null);
1083            builder.withValue(RawContacts.ACCOUNT_TYPE, null);
1084        }
1085        operationList.add(builder.build());
1086
1087        if (!nameFieldsAreEmpty()) {
1088            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1089            builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
1090            builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
1091
1092            builder.withValue(StructuredName.GIVEN_NAME, mGivenName);
1093            builder.withValue(StructuredName.FAMILY_NAME, mFamilyName);
1094            builder.withValue(StructuredName.MIDDLE_NAME, mMiddleName);
1095            builder.withValue(StructuredName.PREFIX, mPrefix);
1096            builder.withValue(StructuredName.SUFFIX, mSuffix);
1097
1098            if (!(TextUtils.isEmpty(mPhoneticGivenName)
1099                    && TextUtils.isEmpty(mPhoneticFamilyName)
1100                    && TextUtils.isEmpty(mPhoneticMiddleName))) {
1101                builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGivenName);
1102                builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamilyName);
1103                builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddleName);
1104            } else if (!TextUtils.isEmpty(mPhoneticFullName)) {
1105                builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticFullName);
1106            }
1107
1108            builder.withValue(StructuredName.DISPLAY_NAME, getDisplayName());
1109            operationList.add(builder.build());
1110        }
1111
1112        if (mNickNameList != null && mNickNameList.size() > 0) {
1113            for (String nickName : mNickNameList) {
1114                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1115                builder.withValueBackReference(Nickname.RAW_CONTACT_ID, 0);
1116                builder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
1117                builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT);
1118                builder.withValue(Nickname.NAME, nickName);
1119                operationList.add(builder.build());
1120            }
1121        }
1122
1123        if (mPhoneList != null) {
1124            for (PhoneData phoneData : mPhoneList) {
1125                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1126                builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
1127                builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
1128
1129                builder.withValue(Phone.TYPE, phoneData.type);
1130                if (phoneData.type == Phone.TYPE_CUSTOM) {
1131                    builder.withValue(Phone.LABEL, phoneData.label);
1132                }
1133                builder.withValue(Phone.NUMBER, phoneData.data);
1134                if (phoneData.isPrimary) {
1135                    builder.withValue(Phone.IS_PRIMARY, 1);
1136                }
1137                operationList.add(builder.build());
1138            }
1139        }
1140
1141        if (mOrganizationList != null) {
1142            for (OrganizationData organizationData : mOrganizationList) {
1143                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1144                builder.withValueBackReference(Organization.RAW_CONTACT_ID, 0);
1145                builder.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
1146                builder.withValue(Organization.TYPE, organizationData.type);
1147                if (organizationData.companyName != null) {
1148                    builder.withValue(Organization.COMPANY, organizationData.companyName);
1149                }
1150                if (organizationData.departmentName != null) {
1151                    builder.withValue(Organization.DEPARTMENT, organizationData.departmentName);
1152                }
1153                if (organizationData.titleName != null) {
1154                    builder.withValue(Organization.TITLE, organizationData.titleName);
1155                }
1156                if (organizationData.isPrimary) {
1157                    builder.withValue(Organization.IS_PRIMARY, 1);
1158                }
1159                operationList.add(builder.build());
1160            }
1161        }
1162
1163        if (mEmailList != null) {
1164            for (EmailData emailData : mEmailList) {
1165                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1166                builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
1167                builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
1168
1169                builder.withValue(Email.TYPE, emailData.type);
1170                if (emailData.type == Email.TYPE_CUSTOM) {
1171                    builder.withValue(Email.LABEL, emailData.label);
1172                }
1173                builder.withValue(Email.DATA, emailData.data);
1174                if (emailData.isPrimary) {
1175                    builder.withValue(Data.IS_PRIMARY, 1);
1176                }
1177                operationList.add(builder.build());
1178            }
1179        }
1180
1181        if (mPostalList != null) {
1182            for (PostalData postalData : mPostalList) {
1183                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1184                VCardUtils.insertStructuredPostalDataUsingContactsStruct(
1185                        mVCardType, builder, postalData);
1186                operationList.add(builder.build());
1187            }
1188        }
1189
1190        if (mImList != null) {
1191            for (ImData imData : mImList) {
1192                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1193                builder.withValueBackReference(Im.RAW_CONTACT_ID, 0);
1194                builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
1195                builder.withValue(Im.TYPE, imData.type);
1196                builder.withValue(Im.PROTOCOL, imData.protocol);
1197                if (imData.protocol == Im.PROTOCOL_CUSTOM) {
1198                    builder.withValue(Im.CUSTOM_PROTOCOL, imData.customProtocol);
1199                }
1200                if (imData.isPrimary) {
1201                    builder.withValue(Data.IS_PRIMARY, 1);
1202                }
1203            }
1204        }
1205
1206        if (mNoteList != null) {
1207            for (String note : mNoteList) {
1208                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1209                builder.withValueBackReference(Note.RAW_CONTACT_ID, 0);
1210                builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
1211                builder.withValue(Note.NOTE, note);
1212                operationList.add(builder.build());
1213            }
1214        }
1215
1216        if (mPhotoList != null) {
1217            for (PhotoData photoData : mPhotoList) {
1218                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1219                builder.withValueBackReference(Photo.RAW_CONTACT_ID, 0);
1220                builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
1221                builder.withValue(Photo.PHOTO, photoData.photoBytes);
1222                if (photoData.isPrimary) {
1223                    builder.withValue(Photo.IS_PRIMARY, 1);
1224                }
1225                operationList.add(builder.build());
1226            }
1227        }
1228
1229        if (mWebsiteList != null) {
1230            for (String website : mWebsiteList) {
1231                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1232                builder.withValueBackReference(Website.RAW_CONTACT_ID, 0);
1233                builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE);
1234                builder.withValue(Website.URL, website);
1235                // There's no information about the type of URL in vCard.
1236                // We use TYPE_HOMEPAGE for safety.
1237                builder.withValue(Website.TYPE, Website.TYPE_HOMEPAGE);
1238                operationList.add(builder.build());
1239            }
1240        }
1241
1242        if (!TextUtils.isEmpty(mBirthday)) {
1243            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1244            builder.withValueBackReference(Event.RAW_CONTACT_ID, 0);
1245            builder.withValue(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
1246            builder.withValue(Event.START_DATE, mBirthday);
1247            builder.withValue(Event.TYPE, Event.TYPE_BIRTHDAY);
1248            operationList.add(builder.build());
1249        }
1250
1251        if (mAndroidCustomPropertyList != null) {
1252            for (List<String> customPropertyList : mAndroidCustomPropertyList) {
1253                int size = customPropertyList.size();
1254                if (size < 2 || TextUtils.isEmpty(customPropertyList.get(0))) {
1255                    continue;
1256                } else if (size > VCardConstants.MAX_DATA_COLUMN + 1) {
1257                    size = VCardConstants.MAX_DATA_COLUMN + 1;
1258                    customPropertyList =
1259                        customPropertyList.subList(0, VCardConstants.MAX_DATA_COLUMN + 2);
1260                }
1261
1262                int i = 0;
1263                for (final String customPropertyValue : customPropertyList) {
1264                    if (i == 0) {
1265                        final String mimeType = customPropertyValue;
1266                        builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1267                        builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
1268                        builder.withValue(Data.MIMETYPE, mimeType);
1269                    } else {  // 1 <= i && i <= MAX_DATA_COLUMNS
1270                        if (!TextUtils.isEmpty(customPropertyValue)) {
1271                            builder.withValue("data" + i, customPropertyValue);
1272                        }
1273                    }
1274
1275                    i++;
1276                }
1277                operationList.add(builder.build());
1278            }
1279        }
1280
1281        if (myGroupsId != null) {
1282            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
1283            builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
1284            builder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
1285            builder.withValue(GroupMembership.GROUP_SOURCE_ID, myGroupsId);
1286            operationList.add(builder.build());
1287        }
1288
1289        try {
1290            ContentProviderResult[] results = resolver.applyBatch(
1291                        ContactsContract.AUTHORITY, operationList);
1292            // the first result is always the raw_contact. return it's uri so
1293            // that it can be found later. do null checking for badly behaving
1294            // ContentResolvers
1295            return (results == null || results.length == 0 || results[0] == null)
1296                ? null
1297                : results[0].uri;
1298        } catch (RemoteException e) {
1299            Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
1300            return null;
1301        } catch (OperationApplicationException e) {
1302            Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
1303            return null;
1304        }
1305    }
1306
1307    public static VCardEntry buildFromResolver(ContentResolver resolver) {
1308        return buildFromResolver(resolver, Contacts.CONTENT_URI);
1309    }
1310
1311    public static VCardEntry buildFromResolver(ContentResolver resolver, Uri uri) {
1312
1313        return null;
1314    }
1315
1316    private boolean nameFieldsAreEmpty() {
1317        return (TextUtils.isEmpty(mFamilyName)
1318                && TextUtils.isEmpty(mMiddleName)
1319                && TextUtils.isEmpty(mGivenName)
1320                && TextUtils.isEmpty(mPrefix)
1321                && TextUtils.isEmpty(mSuffix)
1322                && TextUtils.isEmpty(mFullName)
1323                && TextUtils.isEmpty(mPhoneticFamilyName)
1324                && TextUtils.isEmpty(mPhoneticMiddleName)
1325                && TextUtils.isEmpty(mPhoneticGivenName)
1326                && TextUtils.isEmpty(mPhoneticFullName));
1327    }
1328
1329    public boolean isIgnorable() {
1330        return getDisplayName().length() == 0;
1331    }
1332
1333    private String listToString(List<String> list){
1334        final int size = list.size();
1335        if (size > 1) {
1336            StringBuilder builder = new StringBuilder();
1337            int i = 0;
1338            for (String type : list) {
1339                builder.append(type);
1340                if (i < size - 1) {
1341                    builder.append(";");
1342                }
1343            }
1344            return builder.toString();
1345        } else if (size == 1) {
1346            return list.get(0);
1347        } else {
1348            return "";
1349        }
1350    }
1351
1352    // All getter methods should be used carefully, since they may change
1353    // in the future as of 2009-10-05, on which I cannot be sure this structure
1354    // is completely consolidated.
1355    //
1356    // Also note that these getter methods should be used only after
1357    // all properties being pushed into this object. If not, incorrect
1358    // value will "be stored in the local cache and" be returned to you.
1359
1360    public String getFamilyName() {
1361        return mFamilyName;
1362    }
1363
1364    public String getGivenName() {
1365        return mGivenName;
1366    }
1367
1368    public String getMiddleName() {
1369        return mMiddleName;
1370    }
1371
1372    public String getPrefix() {
1373        return mPrefix;
1374    }
1375
1376    public String getSuffix() {
1377        return mSuffix;
1378    }
1379
1380    public String getFullName() {
1381        return mFullName;
1382    }
1383
1384    public String getPhoneticFamilyName() {
1385        return mPhoneticFamilyName;
1386    }
1387
1388    public String getPhoneticGivenName() {
1389        return mPhoneticGivenName;
1390    }
1391
1392    public String getPhoneticMiddleName() {
1393        return mPhoneticMiddleName;
1394    }
1395
1396    public String getPhoneticFullName() {
1397        return mPhoneticFullName;
1398    }
1399
1400    public final List<String> getNickNameList() {
1401        return mNickNameList;
1402    }
1403
1404    public String getBirthday() {
1405        return mBirthday;
1406    }
1407
1408    public final List<String> getNotes() {
1409        return mNoteList;
1410    }
1411
1412    public final List<PhoneData> getPhoneList() {
1413        return mPhoneList;
1414    }
1415
1416    public final List<EmailData> getEmailList() {
1417        return mEmailList;
1418    }
1419
1420    public final List<PostalData> getPostalList() {
1421        return mPostalList;
1422    }
1423
1424    public final List<OrganizationData> getOrganizationList() {
1425        return mOrganizationList;
1426    }
1427
1428    public final List<ImData> getImList() {
1429        return mImList;
1430    }
1431
1432    public final List<PhotoData> getPhotoList() {
1433        return mPhotoList;
1434    }
1435
1436    public final List<String> getWebsiteList() {
1437        return mWebsiteList;
1438    }
1439
1440    public String getDisplayName() {
1441        if (mDisplayName == null) {
1442            constructDisplayName();
1443        }
1444        return mDisplayName;
1445    }
1446}
1447