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