BaseAccountType.java revision 851222a96b5d68602fb361ea3527101e893f67e3
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.contacts.model.account;
18
19import android.content.ContentValues;
20import android.content.Context;
21import android.content.res.Resources;
22import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
23import android.provider.ContactsContract.CommonDataKinds.Email;
24import android.provider.ContactsContract.CommonDataKinds.Event;
25import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
26import android.provider.ContactsContract.CommonDataKinds.Im;
27import android.provider.ContactsContract.CommonDataKinds.Nickname;
28import android.provider.ContactsContract.CommonDataKinds.Note;
29import android.provider.ContactsContract.CommonDataKinds.Organization;
30import android.provider.ContactsContract.CommonDataKinds.Phone;
31import android.provider.ContactsContract.CommonDataKinds.Photo;
32import android.provider.ContactsContract.CommonDataKinds.Relation;
33import android.provider.ContactsContract.CommonDataKinds.SipAddress;
34import android.provider.ContactsContract.CommonDataKinds.StructuredName;
35import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
36import android.provider.ContactsContract.CommonDataKinds.Website;
37import android.util.AttributeSet;
38import android.util.Log;
39import android.view.inputmethod.EditorInfo;
40
41import com.android.contacts.R;
42import com.android.contacts.model.dataitem.DataKind;
43import com.android.contacts.test.NeededForTesting;
44import com.android.contacts.util.DateUtils;
45import com.google.common.collect.Lists;
46import com.google.common.collect.Maps;
47
48import org.xmlpull.v1.XmlPullParser;
49import org.xmlpull.v1.XmlPullParserException;
50
51import java.io.IOException;
52import java.util.List;
53import java.util.Locale;
54import java.util.Map;
55
56public abstract class BaseAccountType extends AccountType {
57    private static final String TAG = "BaseAccountType";
58
59    protected static final int FLAGS_PHONE = EditorInfo.TYPE_CLASS_PHONE;
60    protected static final int FLAGS_EMAIL = EditorInfo.TYPE_CLASS_TEXT
61            | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
62    protected static final int FLAGS_PERSON_NAME = EditorInfo.TYPE_CLASS_TEXT
63            | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS | EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME;
64    protected static final int FLAGS_PHONETIC = EditorInfo.TYPE_CLASS_TEXT
65            | EditorInfo.TYPE_TEXT_VARIATION_PHONETIC;
66    protected static final int FLAGS_GENERIC_NAME = EditorInfo.TYPE_CLASS_TEXT
67            | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
68    protected static final int FLAGS_NOTE = EditorInfo.TYPE_CLASS_TEXT
69            | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
70    protected static final int FLAGS_EVENT = EditorInfo.TYPE_CLASS_TEXT;
71    protected static final int FLAGS_WEBSITE = EditorInfo.TYPE_CLASS_TEXT
72            | EditorInfo.TYPE_TEXT_VARIATION_URI;
73    protected static final int FLAGS_POSTAL = EditorInfo.TYPE_CLASS_TEXT
74            | EditorInfo.TYPE_TEXT_VARIATION_POSTAL_ADDRESS | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS
75            | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
76    protected static final int FLAGS_SIP_ADDRESS = EditorInfo.TYPE_CLASS_TEXT
77            | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;  // since SIP addresses have the same
78                                                             // basic format as email addresses
79    protected static final int FLAGS_RELATION = EditorInfo.TYPE_CLASS_TEXT
80            | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS | EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME;
81
82    // Specify the maximum number of lines that can be used to display various field types.  If no
83    // value is specified for a particular type, we use the default value from {@link DataKind}.
84    protected static final int MAX_LINES_FOR_POSTAL_ADDRESS = 10;
85    protected static final int MAX_LINES_FOR_GROUP = 10;
86    protected static final int MAX_LINES_FOR_NOTE = 100;
87
88    private interface Tag {
89        static final String DATA_KIND = "DataKind";
90        static final String TYPE = "Type";
91    }
92
93    private interface Attr {
94        static final String MAX_OCCURRENCE = "maxOccurs";
95        static final String DATE_WITH_TIME = "dateWithTime";
96        static final String YEAR_OPTIONAL = "yearOptional";
97        static final String KIND = "kind";
98        static final String TYPE = "type";
99    }
100
101    private interface Weight {
102        static final int NONE = -1;
103        static final int ORGANIZATION = 5;
104        static final int PHONE = 10;
105        static final int EMAIL = 15;
106        static final int IM = 20;
107        static final int STRUCTURED_POSTAL = 25;
108        static final int NOTE = 110;
109        static final int NICKNAME = 115;
110        static final int WEBSITE = 120;
111        static final int SIP_ADDRESS = 130;
112        static final int EVENT = 150;
113        static final int RELATIONSHIP = 160;
114        static final int GROUP_MEMBERSHIP = 999;
115    }
116
117    public BaseAccountType() {
118        this.accountType = null;
119        this.dataSet = null;
120        this.titleRes = R.string.account_phone;
121        this.iconRes = R.mipmap.ic_launcher_contacts;
122    }
123
124    protected static EditType buildPhoneType(int type) {
125        return new EditType(type, Phone.getTypeLabelResource(type));
126    }
127
128    protected static EditType buildEmailType(int type) {
129        return new EditType(type, Email.getTypeLabelResource(type));
130    }
131
132    protected static EditType buildPostalType(int type) {
133        return new EditType(type, StructuredPostal.getTypeLabelResource(type));
134    }
135
136    protected static EditType buildImType(int type) {
137        return new EditType(type, Im.getProtocolLabelResource(type));
138    }
139
140    protected static EditType buildEventType(int type, boolean yearOptional) {
141        return new EventEditType(type, Event.getTypeResource(type)).setYearOptional(yearOptional);
142    }
143
144    protected static EditType buildRelationType(int type) {
145        return new EditType(type, Relation.getTypeLabelResource(type));
146    }
147
148    protected DataKind addDataKindStructuredName(Context context) throws DefinitionException {
149        DataKind kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
150                R.string.nameLabelsGroup, -1, true, R.layout.structured_name_editor_view));
151        kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
152        kind.actionBody = new SimpleInflater(Nickname.NAME);
153        kind.typeOverallMax = 1;
154
155        kind.fieldList = Lists.newArrayList();
156        kind.fieldList.add(new EditField(StructuredName.DISPLAY_NAME,
157                R.string.full_name, FLAGS_PERSON_NAME));
158        kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
159                FLAGS_PERSON_NAME).setLongForm(true));
160        kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
161                FLAGS_PERSON_NAME).setLongForm(true));
162        kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
163                FLAGS_PERSON_NAME).setLongForm(true));
164        kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
165                FLAGS_PERSON_NAME).setLongForm(true));
166        kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
167                FLAGS_PERSON_NAME).setLongForm(true));
168        kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
169                R.string.name_phonetic_family, FLAGS_PHONETIC));
170        kind.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
171                R.string.name_phonetic_middle, FLAGS_PHONETIC));
172        kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
173                R.string.name_phonetic_given, FLAGS_PHONETIC));
174
175        return kind;
176    }
177
178    protected DataKind addDataKindDisplayName(Context context) throws DefinitionException {
179        DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
180                R.string.nameLabelsGroup, -1, true, R.layout.text_fields_editor_view));
181        kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
182        kind.actionBody = new SimpleInflater(Nickname.NAME);
183        kind.typeOverallMax = 1;
184
185        kind.fieldList = Lists.newArrayList();
186        kind.fieldList.add(new EditField(StructuredName.DISPLAY_NAME,
187                R.string.full_name, FLAGS_PERSON_NAME).setShortForm(true));
188
189        boolean displayOrderPrimary =
190                context.getResources().getBoolean(R.bool.config_editor_field_order_primary);
191
192        if (!displayOrderPrimary) {
193            kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
194                    FLAGS_PERSON_NAME).setLongForm(true));
195            kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
196                    FLAGS_PERSON_NAME).setLongForm(true));
197            kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
198                    FLAGS_PERSON_NAME).setLongForm(true));
199            kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
200                    FLAGS_PERSON_NAME).setLongForm(true));
201            kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
202                    FLAGS_PERSON_NAME).setLongForm(true));
203        } else {
204            kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
205                    FLAGS_PERSON_NAME).setLongForm(true));
206            kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
207                    FLAGS_PERSON_NAME).setLongForm(true));
208            kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
209                    FLAGS_PERSON_NAME).setLongForm(true));
210            kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
211                    FLAGS_PERSON_NAME).setLongForm(true));
212            kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
213                    FLAGS_PERSON_NAME).setLongForm(true));
214        }
215
216        return kind;
217    }
218
219    protected DataKind addDataKindPhoneticName(Context context) throws DefinitionException {
220        DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
221                R.string.name_phonetic, -1, true, R.layout.phonetic_name_editor_view));
222        kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
223        kind.actionBody = new SimpleInflater(Nickname.NAME);
224        kind.typeOverallMax = 1;
225
226        kind.fieldList = Lists.newArrayList();
227        kind.fieldList.add(new EditField(DataKind.PSEUDO_COLUMN_PHONETIC_NAME,
228                R.string.name_phonetic, FLAGS_PHONETIC).setShortForm(true));
229        kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
230                R.string.name_phonetic_family, FLAGS_PHONETIC).setLongForm(true));
231        kind.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
232                R.string.name_phonetic_middle, FLAGS_PHONETIC).setLongForm(true));
233        kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
234                R.string.name_phonetic_given, FLAGS_PHONETIC).setLongForm(true));
235
236        return kind;
237    }
238
239    protected DataKind addDataKindNickname(Context context) throws DefinitionException {
240        DataKind kind = addKind(new DataKind(Nickname.CONTENT_ITEM_TYPE,
241                    R.string.nicknameLabelsGroup, 115, true, R.layout.text_fields_editor_view));
242        kind.typeOverallMax = 1;
243        kind.actionHeader = new SimpleInflater(R.string.nicknameLabelsGroup);
244        kind.actionBody = new SimpleInflater(Nickname.NAME);
245        kind.defaultValues = new ContentValues();
246        kind.defaultValues.put(Nickname.TYPE, Nickname.TYPE_DEFAULT);
247
248        kind.fieldList = Lists.newArrayList();
249        kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
250                FLAGS_PERSON_NAME));
251
252        return kind;
253    }
254
255    protected DataKind addDataKindPhone(Context context) throws DefinitionException {
256        DataKind kind = addKind(new DataKind(Phone.CONTENT_ITEM_TYPE, R.string.phoneLabelsGroup,
257                10, true, R.layout.text_fields_editor_view));
258        kind.iconAltRes = R.drawable.ic_text_holo_light;
259        kind.iconAltDescriptionRes = R.string.sms;
260        kind.actionHeader = new PhoneActionInflater();
261        kind.actionAltHeader = new PhoneActionAltInflater();
262        kind.actionBody = new SimpleInflater(Phone.NUMBER);
263        kind.typeColumn = Phone.TYPE;
264        kind.typeList = Lists.newArrayList();
265        kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE));
266        kind.typeList.add(buildPhoneType(Phone.TYPE_HOME));
267        kind.typeList.add(buildPhoneType(Phone.TYPE_WORK));
268        kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_WORK).setSecondary(true));
269        kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_HOME).setSecondary(true));
270        kind.typeList.add(buildPhoneType(Phone.TYPE_PAGER).setSecondary(true));
271        kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER));
272        kind.typeList.add(
273                buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true).setCustomColumn(Phone.LABEL));
274        kind.typeList.add(buildPhoneType(Phone.TYPE_CALLBACK).setSecondary(true));
275        kind.typeList.add(buildPhoneType(Phone.TYPE_CAR).setSecondary(true));
276        kind.typeList.add(buildPhoneType(Phone.TYPE_COMPANY_MAIN).setSecondary(true));
277        kind.typeList.add(buildPhoneType(Phone.TYPE_ISDN).setSecondary(true));
278        kind.typeList.add(buildPhoneType(Phone.TYPE_MAIN).setSecondary(true));
279        kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER_FAX).setSecondary(true));
280        kind.typeList.add(buildPhoneType(Phone.TYPE_RADIO).setSecondary(true));
281        kind.typeList.add(buildPhoneType(Phone.TYPE_TELEX).setSecondary(true));
282        kind.typeList.add(buildPhoneType(Phone.TYPE_TTY_TDD).setSecondary(true));
283        kind.typeList.add(buildPhoneType(Phone.TYPE_WORK_MOBILE).setSecondary(true));
284        kind.typeList.add(buildPhoneType(Phone.TYPE_WORK_PAGER).setSecondary(true));
285        kind.typeList.add(buildPhoneType(Phone.TYPE_ASSISTANT).setSecondary(true));
286        kind.typeList.add(buildPhoneType(Phone.TYPE_MMS).setSecondary(true));
287
288        kind.fieldList = Lists.newArrayList();
289        kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
290
291        return kind;
292    }
293
294    protected DataKind addDataKindEmail(Context context) throws DefinitionException {
295        DataKind kind = addKind(new DataKind(Email.CONTENT_ITEM_TYPE, R.string.emailLabelsGroup,
296                15, true, R.layout.text_fields_editor_view));
297        kind.actionHeader = new EmailActionInflater();
298        kind.actionBody = new SimpleInflater(Email.DATA);
299        kind.typeColumn = Email.TYPE;
300        kind.typeList = Lists.newArrayList();
301        kind.typeList.add(buildEmailType(Email.TYPE_HOME));
302        kind.typeList.add(buildEmailType(Email.TYPE_WORK));
303        kind.typeList.add(buildEmailType(Email.TYPE_OTHER));
304        kind.typeList.add(buildEmailType(Email.TYPE_MOBILE));
305        kind.typeList.add(
306                buildEmailType(Email.TYPE_CUSTOM).setSecondary(true).setCustomColumn(Email.LABEL));
307
308        kind.fieldList = Lists.newArrayList();
309        kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
310
311        return kind;
312    }
313
314    protected DataKind addDataKindStructuredPostal(Context context) throws DefinitionException {
315        DataKind kind = addKind(new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
316                R.string.postalLabelsGroup, 25, true, R.layout.text_fields_editor_view));
317        kind.actionHeader = new PostalActionInflater();
318        kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
319        kind.typeColumn = StructuredPostal.TYPE;
320        kind.typeList = Lists.newArrayList();
321        kind.typeList.add(buildPostalType(StructuredPostal.TYPE_HOME));
322        kind.typeList.add(buildPostalType(StructuredPostal.TYPE_WORK));
323        kind.typeList.add(buildPostalType(StructuredPostal.TYPE_OTHER));
324        kind.typeList.add(buildPostalType(StructuredPostal.TYPE_CUSTOM).setSecondary(true)
325                .setCustomColumn(StructuredPostal.LABEL));
326
327        kind.fieldList = Lists.newArrayList();
328        kind.fieldList.add(
329                new EditField(StructuredPostal.FORMATTED_ADDRESS, R.string.postal_address,
330                        FLAGS_POSTAL));
331
332        kind.maxLinesForDisplay = MAX_LINES_FOR_POSTAL_ADDRESS;
333
334        return kind;
335    }
336
337    protected DataKind addDataKindIm(Context context) throws DefinitionException {
338        DataKind kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup, 20, true,
339                    R.layout.text_fields_editor_view));
340        kind.actionHeader = new ImActionInflater();
341        kind.actionBody = new SimpleInflater(Im.DATA);
342
343        // NOTE: even though a traditional "type" exists, for editing
344        // purposes we're using the protocol to pick labels
345
346        kind.defaultValues = new ContentValues();
347        kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);
348
349        kind.typeColumn = Im.PROTOCOL;
350        kind.typeList = Lists.newArrayList();
351        kind.typeList.add(buildImType(Im.PROTOCOL_AIM));
352        kind.typeList.add(buildImType(Im.PROTOCOL_MSN));
353        kind.typeList.add(buildImType(Im.PROTOCOL_YAHOO));
354        kind.typeList.add(buildImType(Im.PROTOCOL_SKYPE));
355        kind.typeList.add(buildImType(Im.PROTOCOL_QQ));
356        kind.typeList.add(buildImType(Im.PROTOCOL_GOOGLE_TALK));
357        kind.typeList.add(buildImType(Im.PROTOCOL_ICQ));
358        kind.typeList.add(buildImType(Im.PROTOCOL_JABBER));
359        kind.typeList.add(buildImType(Im.PROTOCOL_CUSTOM).setSecondary(true).setCustomColumn(
360                Im.CUSTOM_PROTOCOL));
361
362        kind.fieldList = Lists.newArrayList();
363        kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
364
365        return kind;
366    }
367
368    protected DataKind addDataKindOrganization(Context context) throws DefinitionException {
369        DataKind kind = addKind(new DataKind(Organization.CONTENT_ITEM_TYPE,
370                    R.string.organizationLabelsGroup, 5, true,
371                    R.layout.text_fields_editor_view));
372        kind.actionHeader = new SimpleInflater(Organization.COMPANY);
373        kind.actionBody = new SimpleInflater(Organization.TITLE);
374        kind.typeOverallMax = 1;
375
376        kind.fieldList = Lists.newArrayList();
377        kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
378                FLAGS_GENERIC_NAME));
379        kind.fieldList.add(new EditField(Organization.TITLE, R.string.ghostData_title,
380                FLAGS_GENERIC_NAME));
381
382        return kind;
383    }
384
385    protected DataKind addDataKindPhoto(Context context) throws DefinitionException {
386        DataKind kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, true, -1));
387        kind.typeOverallMax = 1;
388        kind.fieldList = Lists.newArrayList();
389        kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
390        return kind;
391    }
392
393    protected DataKind addDataKindNote(Context context) throws DefinitionException {
394        DataKind kind = addKind(new DataKind(Note.CONTENT_ITEM_TYPE,
395                    R.string.label_notes, 110, true, R.layout.text_fields_editor_view));
396        kind.typeOverallMax = 1;
397        kind.actionHeader = new SimpleInflater(R.string.label_notes);
398        kind.actionBody = new SimpleInflater(Note.NOTE);
399        kind.fieldList = Lists.newArrayList();
400        kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
401
402        kind.maxLinesForDisplay = MAX_LINES_FOR_NOTE;
403
404        return kind;
405    }
406
407    protected DataKind addDataKindWebsite(Context context) throws DefinitionException {
408        DataKind kind = addKind(new DataKind(Website.CONTENT_ITEM_TYPE,
409                R.string.websiteLabelsGroup, 120, true, R.layout.text_fields_editor_view));
410        kind.actionHeader = new SimpleInflater(R.string.websiteLabelsGroup);
411        kind.actionBody = new SimpleInflater(Website.URL);
412        kind.defaultValues = new ContentValues();
413        kind.defaultValues.put(Website.TYPE, Website.TYPE_OTHER);
414
415        kind.fieldList = Lists.newArrayList();
416        kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
417
418        return kind;
419    }
420
421    protected DataKind addDataKindSipAddress(Context context) throws DefinitionException {
422        DataKind kind = addKind(new DataKind(SipAddress.CONTENT_ITEM_TYPE,
423                    R.string.label_sip_address, 130, true, R.layout.text_fields_editor_view));
424
425        kind.typeOverallMax = 1;
426        kind.actionHeader = new SimpleInflater(R.string.label_sip_address);
427        kind.actionBody = new SimpleInflater(SipAddress.SIP_ADDRESS);
428        kind.fieldList = Lists.newArrayList();
429        kind.fieldList.add(new EditField(SipAddress.SIP_ADDRESS,
430                                         R.string.label_sip_address, FLAGS_SIP_ADDRESS));
431
432        return kind;
433    }
434
435    protected DataKind addDataKindGroupMembership(Context context) throws DefinitionException {
436        DataKind kind = addKind(new DataKind(GroupMembership.CONTENT_ITEM_TYPE,
437                R.string.groupsLabel, 999, true, -1));
438
439        kind.typeOverallMax = 1;
440        kind.fieldList = Lists.newArrayList();
441        kind.fieldList.add(new EditField(GroupMembership.GROUP_ROW_ID, -1, -1));
442
443        kind.maxLinesForDisplay = MAX_LINES_FOR_GROUP;
444
445        return kind;
446    }
447
448    /**
449     * Simple inflater that assumes a string resource has a "%s" that will be
450     * filled from the given column.
451     */
452    public static class SimpleInflater implements StringInflater {
453        private final int mStringRes;
454        private final String mColumnName;
455
456        public SimpleInflater(int stringRes) {
457            this(stringRes, null);
458        }
459
460        public SimpleInflater(String columnName) {
461            this(-1, columnName);
462        }
463
464        public SimpleInflater(int stringRes, String columnName) {
465            mStringRes = stringRes;
466            mColumnName = columnName;
467        }
468
469        @Override
470        public CharSequence inflateUsing(Context context, ContentValues values) {
471            final boolean validColumn = values.containsKey(mColumnName);
472            final boolean validString = mStringRes > 0;
473
474            final CharSequence stringValue = validString ? context.getText(mStringRes) : null;
475            final CharSequence columnValue = validColumn ? values.getAsString(mColumnName) : null;
476
477            if (validString && validColumn) {
478                return String.format(stringValue.toString(), columnValue);
479            } else if (validString) {
480                return stringValue;
481            } else if (validColumn) {
482                return columnValue;
483            } else {
484                return null;
485            }
486        }
487
488        @Override
489        public String toString() {
490            return this.getClass().getSimpleName()
491                    + " mStringRes=" + mStringRes
492                    + " mColumnName" + mColumnName;
493        }
494
495        @NeededForTesting
496        public String getColumnNameForTest() {
497            return mColumnName;
498        }
499    }
500
501    public static abstract class CommonInflater implements StringInflater {
502        protected abstract int getTypeLabelResource(Integer type);
503
504        protected boolean isCustom(Integer type) {
505            return type == BaseTypes.TYPE_CUSTOM;
506        }
507
508        protected String getTypeColumn() {
509            return Phone.TYPE;
510        }
511
512        protected String getLabelColumn() {
513            return Phone.LABEL;
514        }
515
516        protected CharSequence getTypeLabel(Resources res, Integer type, CharSequence label) {
517            final int labelRes = getTypeLabelResource(type);
518            if (type == null) {
519                return res.getText(labelRes);
520            } else if (isCustom(type)) {
521                return res.getString(labelRes, label == null ? "" : label);
522            } else {
523                return res.getText(labelRes);
524            }
525        }
526
527        @Override
528        public CharSequence inflateUsing(Context context, ContentValues values) {
529            final Integer type = values.getAsInteger(getTypeColumn());
530            final String label = values.getAsString(getLabelColumn());
531            return getTypeLabel(context.getResources(), type, label);
532        }
533
534        @Override
535        public String toString() {
536            return this.getClass().getSimpleName();
537        }
538    }
539
540    public static class PhoneActionInflater extends CommonInflater {
541        @Override
542        protected boolean isCustom(Integer type) {
543            return type == Phone.TYPE_CUSTOM || type == Phone.TYPE_ASSISTANT;
544        }
545
546        @Override
547        protected int getTypeLabelResource(Integer type) {
548            if (type == null) return R.string.call_other;
549            switch (type) {
550                case Phone.TYPE_HOME: return R.string.call_home;
551                case Phone.TYPE_MOBILE: return R.string.call_mobile;
552                case Phone.TYPE_WORK: return R.string.call_work;
553                case Phone.TYPE_FAX_WORK: return R.string.call_fax_work;
554                case Phone.TYPE_FAX_HOME: return R.string.call_fax_home;
555                case Phone.TYPE_PAGER: return R.string.call_pager;
556                case Phone.TYPE_OTHER: return R.string.call_other;
557                case Phone.TYPE_CALLBACK: return R.string.call_callback;
558                case Phone.TYPE_CAR: return R.string.call_car;
559                case Phone.TYPE_COMPANY_MAIN: return R.string.call_company_main;
560                case Phone.TYPE_ISDN: return R.string.call_isdn;
561                case Phone.TYPE_MAIN: return R.string.call_main;
562                case Phone.TYPE_OTHER_FAX: return R.string.call_other_fax;
563                case Phone.TYPE_RADIO: return R.string.call_radio;
564                case Phone.TYPE_TELEX: return R.string.call_telex;
565                case Phone.TYPE_TTY_TDD: return R.string.call_tty_tdd;
566                case Phone.TYPE_WORK_MOBILE: return R.string.call_work_mobile;
567                case Phone.TYPE_WORK_PAGER: return R.string.call_work_pager;
568                case Phone.TYPE_ASSISTANT: return R.string.call_assistant;
569                case Phone.TYPE_MMS: return R.string.call_mms;
570                default: return R.string.call_custom;
571            }
572        }
573    }
574
575    public static class PhoneActionAltInflater extends CommonInflater {
576        @Override
577        protected boolean isCustom(Integer type) {
578            return (type == Phone.TYPE_CUSTOM || type == Phone.TYPE_ASSISTANT);
579        }
580
581        @Override
582        protected int getTypeLabelResource(Integer type) {
583            if (type == null) return R.string.sms_other;
584            switch (type) {
585                case Phone.TYPE_HOME: return R.string.sms_home;
586                case Phone.TYPE_MOBILE: return R.string.sms_mobile;
587                case Phone.TYPE_WORK: return R.string.sms_work;
588                case Phone.TYPE_FAX_WORK: return R.string.sms_fax_work;
589                case Phone.TYPE_FAX_HOME: return R.string.sms_fax_home;
590                case Phone.TYPE_PAGER: return R.string.sms_pager;
591                case Phone.TYPE_OTHER: return R.string.sms_other;
592                case Phone.TYPE_CALLBACK: return R.string.sms_callback;
593                case Phone.TYPE_CAR: return R.string.sms_car;
594                case Phone.TYPE_COMPANY_MAIN: return R.string.sms_company_main;
595                case Phone.TYPE_ISDN: return R.string.sms_isdn;
596                case Phone.TYPE_MAIN: return R.string.sms_main;
597                case Phone.TYPE_OTHER_FAX: return R.string.sms_other_fax;
598                case Phone.TYPE_RADIO: return R.string.sms_radio;
599                case Phone.TYPE_TELEX: return R.string.sms_telex;
600                case Phone.TYPE_TTY_TDD: return R.string.sms_tty_tdd;
601                case Phone.TYPE_WORK_MOBILE: return R.string.sms_work_mobile;
602                case Phone.TYPE_WORK_PAGER: return R.string.sms_work_pager;
603                case Phone.TYPE_ASSISTANT: return R.string.sms_assistant;
604                case Phone.TYPE_MMS: return R.string.sms_mms;
605                default: return R.string.sms_custom;
606            }
607        }
608    }
609
610    public static class EmailActionInflater extends CommonInflater {
611        @Override
612        protected int getTypeLabelResource(Integer type) {
613            if (type == null) return R.string.email;
614            switch (type) {
615                case Email.TYPE_HOME: return R.string.email_home;
616                case Email.TYPE_WORK: return R.string.email_work;
617                case Email.TYPE_OTHER: return R.string.email_other;
618                case Email.TYPE_MOBILE: return R.string.email_mobile;
619                default: return R.string.email_custom;
620            }
621        }
622    }
623
624    public static class EventActionInflater extends CommonInflater {
625        @Override
626        protected int getTypeLabelResource(Integer type) {
627            return Event.getTypeResource(type);
628        }
629    }
630
631    public static class RelationActionInflater extends CommonInflater {
632        @Override
633        protected int getTypeLabelResource(Integer type) {
634            return Relation.getTypeLabelResource(type == null ? Relation.TYPE_CUSTOM : type);
635        }
636    }
637
638    public static class PostalActionInflater extends CommonInflater {
639        @Override
640        protected int getTypeLabelResource(Integer type) {
641            if (type == null) return R.string.map_other;
642            switch (type) {
643                case StructuredPostal.TYPE_HOME: return R.string.map_home;
644                case StructuredPostal.TYPE_WORK: return R.string.map_work;
645                case StructuredPostal.TYPE_OTHER: return R.string.map_other;
646                default: return R.string.map_custom;
647            }
648        }
649    }
650
651    public static class ImActionInflater extends CommonInflater {
652        @Override
653        protected String getTypeColumn() {
654            return Im.PROTOCOL;
655        }
656
657        @Override
658        protected String getLabelColumn() {
659            return Im.CUSTOM_PROTOCOL;
660        }
661
662        @Override
663        protected int getTypeLabelResource(Integer type) {
664            if (type == null) return R.string.chat;
665            switch (type) {
666                case Im.PROTOCOL_AIM: return R.string.chat_aim;
667                case Im.PROTOCOL_MSN: return R.string.chat_msn;
668                case Im.PROTOCOL_YAHOO: return R.string.chat_yahoo;
669                case Im.PROTOCOL_SKYPE: return R.string.chat_skype;
670                case Im.PROTOCOL_QQ: return R.string.chat_qq;
671                case Im.PROTOCOL_GOOGLE_TALK: return R.string.chat_gtalk;
672                case Im.PROTOCOL_ICQ: return R.string.chat_icq;
673                case Im.PROTOCOL_JABBER: return R.string.chat_jabber;
674                case Im.PROTOCOL_NETMEETING: return R.string.chat;
675                default: return R.string.chat;
676            }
677        }
678    }
679
680    @Override
681    public boolean isGroupMembershipEditable() {
682        return false;
683    }
684
685    /**
686     * Parses the content of the EditSchema tag in contacts.xml.
687     */
688    protected final void parseEditSchema(Context context, XmlPullParser parser, AttributeSet attrs)
689            throws XmlPullParserException, IOException, DefinitionException {
690
691        final int outerDepth = parser.getDepth();
692        int type;
693        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
694                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
695            final int depth = parser.getDepth();
696            if (type != XmlPullParser.START_TAG || depth != outerDepth + 1) {
697                continue; // Not direct child tag
698            }
699
700            final String tag = parser.getName();
701
702            if (Tag.DATA_KIND.equals(tag)) {
703                for (DataKind kind : KindParser.INSTANCE.parseDataKindTag(context, parser, attrs)) {
704                    addKind(kind);
705                }
706            } else {
707                Log.w(TAG, "Skipping unknown tag " + tag);
708            }
709        }
710    }
711
712    // Utility methods to keep code shorter.
713    private static boolean getAttr(AttributeSet attrs, String attribute, boolean defaultValue) {
714        return attrs.getAttributeBooleanValue(null, attribute, defaultValue);
715    }
716
717    private static int getAttr(AttributeSet attrs, String attribute, int defaultValue) {
718        return attrs.getAttributeIntValue(null, attribute, defaultValue);
719    }
720
721    private static String getAttr(AttributeSet attrs, String attribute) {
722        return attrs.getAttributeValue(null, attribute);
723    }
724
725    // TODO Extract it to its own class, and move all KindBuilders to it as well.
726    private static class KindParser {
727        public static final KindParser INSTANCE = new KindParser();
728
729        private final Map<String, KindBuilder> mBuilders = Maps.newHashMap();
730
731        private KindParser() {
732            addBuilder(new NameKindBuilder());
733            addBuilder(new NicknameKindBuilder());
734            addBuilder(new PhoneKindBuilder());
735            addBuilder(new EmailKindBuilder());
736            addBuilder(new StructuredPostalKindBuilder());
737            addBuilder(new ImKindBuilder());
738            addBuilder(new OrganizationKindBuilder());
739            addBuilder(new PhotoKindBuilder());
740            addBuilder(new NoteKindBuilder());
741            addBuilder(new WebsiteKindBuilder());
742            addBuilder(new SipAddressKindBuilder());
743            addBuilder(new GroupMembershipKindBuilder());
744            addBuilder(new EventKindBuilder());
745            addBuilder(new RelationshipKindBuilder());
746        }
747
748        private void addBuilder(KindBuilder builder) {
749            mBuilders.put(builder.getTagName(), builder);
750        }
751
752        /**
753         * Takes a {@link XmlPullParser} at the start of a DataKind tag, parses it and returns
754         * {@link DataKind}s.  (Usually just one, but there are three for the "name" kind.)
755         *
756         * This method returns a list, because we need to add 3 kinds for the name data kind.
757         * (structured, display and phonetic)
758         */
759        public List<DataKind> parseDataKindTag(Context context, XmlPullParser parser,
760                AttributeSet attrs)
761                throws DefinitionException, XmlPullParserException, IOException {
762            final String kind = getAttr(attrs, Attr.KIND);
763            final KindBuilder builder = mBuilders.get(kind);
764            if (builder != null) {
765                return builder.parseDataKind(context, parser, attrs);
766            } else {
767                throw new DefinitionException("Undefined data kind '" + kind + "'");
768            }
769        }
770    }
771
772    private static abstract class KindBuilder {
773
774        public abstract String getTagName();
775
776        /**
777         * DataKind tag parser specific to each kind.  Subclasses must implement it.
778         */
779        public abstract List<DataKind> parseDataKind(Context context, XmlPullParser parser,
780                AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException;
781
782        /**
783         * Creates a new {@link DataKind}, and also parses the child Type tags in the DataKind
784         * tag.
785         */
786        protected final DataKind newDataKind(Context context, XmlPullParser parser,
787                AttributeSet attrs, boolean isPseudo, String mimeType, String typeColumn,
788                int titleRes, int weight, int editorLayoutResourceId,
789                StringInflater actionHeader, StringInflater actionBody)
790                throws DefinitionException, XmlPullParserException, IOException {
791
792            if (Log.isLoggable(TAG, Log.DEBUG)) {
793                Log.d(TAG, "Adding DataKind: " + mimeType);
794            }
795
796            final DataKind kind = new DataKind(mimeType, titleRes, weight, true,
797                    editorLayoutResourceId);
798            kind.typeColumn = typeColumn;
799            kind.actionHeader = actionHeader;
800            kind.actionBody = actionBody;
801            kind.fieldList = Lists.newArrayList();
802
803            // Get more information from the tag...
804            // A pseudo data kind doesn't have corresponding tag the XML, so we skip this.
805            if (!isPseudo) {
806                kind.typeOverallMax = getAttr(attrs, Attr.MAX_OCCURRENCE, -1);
807
808                // Process "Type" tags.
809                // If a kind has the type column, contacts.xml must have at least one type
810                // definition.  Otherwise, it mustn't have a type definition.
811                if (kind.typeColumn != null) {
812                    // Parse and add types.
813                    kind.typeList = Lists.newArrayList();
814                    parseTypes(context, parser, attrs, kind, true);
815                    if (kind.typeList.size() == 0) {
816                        throw new DefinitionException(
817                                "Kind " + kind.mimeType + " must have at least one type");
818                    }
819                } else {
820                    // Make sure it has no types.
821                    parseTypes(context, parser, attrs, kind, false /* can't have types */);
822                }
823            }
824
825            return kind;
826        }
827
828        /**
829         * Parses Type elements in a DataKind element, and if {@code canHaveTypes} is true adds
830         * them to the given {@link DataKind}. Otherwise the {@link DataKind} can't have a type,
831         * so throws {@link DefinitionException}.
832         */
833        private void parseTypes(Context context, XmlPullParser parser, AttributeSet attrs,
834                DataKind kind, boolean canHaveTypes)
835                throws DefinitionException, XmlPullParserException, IOException {
836            final int outerDepth = parser.getDepth();
837            int type;
838            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
839                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
840                final int depth = parser.getDepth();
841                if (type != XmlPullParser.START_TAG || depth != outerDepth + 1) {
842                    continue; // Not direct child tag
843                }
844
845                final String tag = parser.getName();
846                if (Tag.TYPE.equals(tag)) {
847                    if (canHaveTypes) {
848                        kind.typeList.add(parseTypeTag(parser, attrs, kind));
849                    } else {
850                        throw new DefinitionException(
851                                "Kind " + kind.mimeType + " can't have types");
852                    }
853                } else {
854                    throw new DefinitionException("Unknown tag: " + tag);
855                }
856            }
857        }
858
859        /**
860         * Parses a single Type element and returns an {@link EditType} built from it.  Uses
861         * {@link #buildEditTypeForTypeTag} defined in subclasses to actually build an
862         * {@link EditType}.
863         */
864        private EditType parseTypeTag(XmlPullParser parser, AttributeSet attrs, DataKind kind)
865                throws DefinitionException {
866
867            final String typeName = getAttr(attrs, Attr.TYPE);
868
869            final EditType et = buildEditTypeForTypeTag(attrs, typeName);
870            if (et == null) {
871                throw new DefinitionException(
872                        "Undefined type '" + typeName + "' for data kind '" + kind.mimeType + "'");
873            }
874            et.specificMax = getAttr(attrs, Attr.MAX_OCCURRENCE, -1);
875
876            return et;
877        }
878
879        /**
880         * Returns an {@link EditType} for the given "type".  Subclasses may optionally use
881         * the attributes in the tag to set optional values.
882         * (e.g. "yearOptional" for the event kind)
883         */
884        protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
885            return null;
886        }
887
888        protected final void throwIfList(DataKind kind) throws DefinitionException {
889            if (kind.typeOverallMax != 1) {
890                throw new DefinitionException(
891                        "Kind " + kind.mimeType + " must have 'overallMax=\"1\"'");
892            }
893        }
894    }
895
896    /**
897     * DataKind parser for Name. (structured, display, phonetic)
898     */
899    private static class NameKindBuilder extends KindBuilder {
900        @Override
901        public String getTagName() {
902            return "name";
903        }
904
905        private static void checkAttributeTrue(boolean value, String attrName)
906                throws DefinitionException {
907            if (!value) {
908                throw new DefinitionException(attrName + " must be true");
909            }
910        }
911
912        @Override
913        public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
914                AttributeSet attrs) throws DefinitionException, XmlPullParserException,
915                IOException {
916
917            // Build 3 data kinds:
918            // - StructuredName.CONTENT_ITEM_TYPE
919            // - DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME
920            // - DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME
921
922            final boolean displayOrderPrimary =
923                    context.getResources().getBoolean(R.bool.config_editor_field_order_primary);
924
925            final boolean supportsDisplayName = getAttr(attrs, "supportsDisplayName", false);
926            final boolean supportsPrefix = getAttr(attrs, "supportsPrefix", false);
927            final boolean supportsMiddleName = getAttr(attrs, "supportsMiddleName", false);
928            final boolean supportsSuffix = getAttr(attrs, "supportsSuffix", false);
929            final boolean supportsPhoneticFamilyName =
930                    getAttr(attrs, "supportsPhoneticFamilyName", false);
931            final boolean supportsPhoneticMiddleName =
932                    getAttr(attrs, "supportsPhoneticMiddleName", false);
933            final boolean supportsPhoneticGivenName =
934                    getAttr(attrs, "supportsPhoneticGivenName", false);
935
936            // For now, every things must be supported.
937            checkAttributeTrue(supportsDisplayName, "supportsDisplayName");
938            checkAttributeTrue(supportsPrefix, "supportsPrefix");
939            checkAttributeTrue(supportsMiddleName, "supportsMiddleName");
940            checkAttributeTrue(supportsSuffix, "supportsSuffix");
941            checkAttributeTrue(supportsPhoneticFamilyName, "supportsPhoneticFamilyName");
942            checkAttributeTrue(supportsPhoneticMiddleName, "supportsPhoneticMiddleName");
943            checkAttributeTrue(supportsPhoneticGivenName, "supportsPhoneticGivenName");
944
945            final List<DataKind> kinds = Lists.newArrayList();
946
947            // Structured name
948            final DataKind ks = newDataKind(context, parser, attrs, false,
949                    StructuredName.CONTENT_ITEM_TYPE, null, R.string.nameLabelsGroup, Weight.NONE,
950                    R.layout.structured_name_editor_view,
951                    new SimpleInflater(R.string.nameLabelsGroup),
952                    new SimpleInflater(Nickname.NAME));
953
954            throwIfList(ks);
955            kinds.add(ks);
956
957            // Note about setLongForm/setShortForm below.
958            // We need to set this only when the type supports display name. (=supportsDisplayName)
959            // Otherwise (i.e. Exchange) we don't set these flags, but instead make some fields
960            // "optional".
961
962            ks.fieldList.add(new EditField(StructuredName.DISPLAY_NAME, R.string.full_name,
963                    FLAGS_PERSON_NAME));
964            ks.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
965                    FLAGS_PERSON_NAME).setLongForm(true));
966            ks.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
967                    FLAGS_PERSON_NAME).setLongForm(true));
968            ks.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
969                    FLAGS_PERSON_NAME).setLongForm(true));
970            ks.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
971                    FLAGS_PERSON_NAME).setLongForm(true));
972            ks.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
973                    FLAGS_PERSON_NAME).setLongForm(true));
974            ks.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
975                    R.string.name_phonetic_family, FLAGS_PHONETIC));
976            ks.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
977                    R.string.name_phonetic_middle, FLAGS_PHONETIC));
978            ks.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
979                    R.string.name_phonetic_given, FLAGS_PHONETIC));
980
981            // Display name
982            final DataKind kd = newDataKind(context, parser, attrs, true,
983                    DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME, null,
984                    R.string.nameLabelsGroup, Weight.NONE, R.layout.text_fields_editor_view,
985                    new SimpleInflater(R.string.nameLabelsGroup),
986                    new SimpleInflater(Nickname.NAME));
987            kd.typeOverallMax = 1;
988            kinds.add(kd);
989
990            kd.fieldList.add(new EditField(StructuredName.DISPLAY_NAME,
991                    R.string.full_name, FLAGS_PERSON_NAME).setShortForm(true));
992
993            if (!displayOrderPrimary) {
994                kd.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
995                        FLAGS_PERSON_NAME).setLongForm(true));
996                kd.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
997                        FLAGS_PERSON_NAME).setLongForm(true));
998                kd.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
999                        FLAGS_PERSON_NAME).setLongForm(true));
1000                kd.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
1001                        FLAGS_PERSON_NAME).setLongForm(true));
1002                kd.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
1003                        FLAGS_PERSON_NAME).setLongForm(true));
1004            } else {
1005                kd.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
1006                        FLAGS_PERSON_NAME).setLongForm(true));
1007                kd.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
1008                        FLAGS_PERSON_NAME).setLongForm(true));
1009                kd.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
1010                        FLAGS_PERSON_NAME).setLongForm(true));
1011                kd.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
1012                        FLAGS_PERSON_NAME).setLongForm(true));
1013                kd.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
1014                        FLAGS_PERSON_NAME).setLongForm(true));
1015            }
1016
1017            // Phonetic name
1018            final DataKind kp = newDataKind(context, parser, attrs, true,
1019                    DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME, null,
1020                    R.string.name_phonetic, Weight.NONE, R.layout.phonetic_name_editor_view,
1021                    new SimpleInflater(R.string.nameLabelsGroup),
1022                    new SimpleInflater(Nickname.NAME));
1023            kp.typeOverallMax = 1;
1024            kinds.add(kp);
1025
1026            // We may want to change the order depending on displayOrderPrimary too.
1027            kp.fieldList.add(new EditField(DataKind.PSEUDO_COLUMN_PHONETIC_NAME,
1028                    R.string.name_phonetic, FLAGS_PHONETIC).setShortForm(true));
1029            kp.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
1030                    R.string.name_phonetic_family, FLAGS_PHONETIC).setLongForm(true));
1031            kp.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
1032                    R.string.name_phonetic_middle, FLAGS_PHONETIC).setLongForm(true));
1033            kp.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
1034                    R.string.name_phonetic_given, FLAGS_PHONETIC).setLongForm(true));
1035            return kinds;
1036        }
1037    }
1038
1039    private static class NicknameKindBuilder extends KindBuilder {
1040        @Override
1041        public String getTagName() {
1042            return "nickname";
1043        }
1044
1045        @Override
1046        public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
1047                AttributeSet attrs) throws DefinitionException, XmlPullParserException,
1048                IOException {
1049            final DataKind kind = newDataKind(context, parser, attrs, false,
1050                    Nickname.CONTENT_ITEM_TYPE, null, R.string.nicknameLabelsGroup, Weight.NICKNAME,
1051                    R.layout.text_fields_editor_view,
1052                    new SimpleInflater(R.string.nicknameLabelsGroup),
1053                    new SimpleInflater(Nickname.NAME));
1054
1055            kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
1056                    FLAGS_PERSON_NAME));
1057
1058            kind.defaultValues = new ContentValues();
1059            kind.defaultValues.put(Nickname.TYPE, Nickname.TYPE_DEFAULT);
1060
1061            throwIfList(kind);
1062            return Lists.newArrayList(kind);
1063        }
1064    }
1065
1066    private static class PhoneKindBuilder extends KindBuilder {
1067        @Override
1068        public String getTagName() {
1069            return "phone";
1070        }
1071
1072        @Override
1073        public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
1074                AttributeSet attrs) throws DefinitionException, XmlPullParserException,
1075                IOException {
1076            final DataKind kind = newDataKind(context, parser, attrs, false,
1077                    Phone.CONTENT_ITEM_TYPE, Phone.TYPE, R.string.phoneLabelsGroup, Weight.PHONE,
1078                    R.layout.text_fields_editor_view,
1079                    new PhoneActionInflater(), new SimpleInflater(Phone.NUMBER));
1080
1081            kind.iconAltRes = R.drawable.ic_text_holo_light;
1082            kind.iconAltDescriptionRes = R.string.sms;
1083            kind.actionAltHeader = new PhoneActionAltInflater();
1084
1085            kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
1086
1087            return Lists.newArrayList(kind);
1088        }
1089
1090        /** Just to avoid line-wrapping... */
1091        protected static EditType build(int type, boolean secondary) {
1092            return new EditType(type, Phone.getTypeLabelResource(type)).setSecondary(secondary);
1093        }
1094
1095        @Override
1096        protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
1097            if ("home".equals(type)) return build(Phone.TYPE_HOME, false);
1098            if ("mobile".equals(type)) return build(Phone.TYPE_MOBILE, false);
1099            if ("work".equals(type)) return build(Phone.TYPE_WORK, false);
1100            if ("fax_work".equals(type)) return build(Phone.TYPE_FAX_WORK, true);
1101            if ("fax_home".equals(type)) return build(Phone.TYPE_FAX_HOME, true);
1102            if ("pager".equals(type)) return build(Phone.TYPE_PAGER, true);
1103            if ("other".equals(type)) return build(Phone.TYPE_OTHER, false);
1104            if ("callback".equals(type)) return build(Phone.TYPE_CALLBACK, true);
1105            if ("car".equals(type)) return build(Phone.TYPE_CAR, true);
1106            if ("company_main".equals(type)) return build(Phone.TYPE_COMPANY_MAIN, true);
1107            if ("isdn".equals(type)) return build(Phone.TYPE_ISDN, true);
1108            if ("main".equals(type)) return build(Phone.TYPE_MAIN, true);
1109            if ("other_fax".equals(type)) return build(Phone.TYPE_OTHER_FAX, true);
1110            if ("radio".equals(type)) return build(Phone.TYPE_RADIO, true);
1111            if ("telex".equals(type)) return build(Phone.TYPE_TELEX, true);
1112            if ("tty_tdd".equals(type)) return build(Phone.TYPE_TTY_TDD, true);
1113            if ("work_mobile".equals(type)) return build(Phone.TYPE_WORK_MOBILE, true);
1114            if ("work_pager".equals(type)) return build(Phone.TYPE_WORK_PAGER, true);
1115
1116            // Note "assistant" used to be a custom column for the fallback type, but not anymore.
1117            if ("assistant".equals(type)) return build(Phone.TYPE_ASSISTANT, true);
1118            if ("mms".equals(type)) return build(Phone.TYPE_MMS, true);
1119            if ("custom".equals(type)) {
1120                return build(Phone.TYPE_CUSTOM, true).setCustomColumn(Phone.LABEL);
1121            }
1122            return null;
1123        }
1124    }
1125
1126    private static class EmailKindBuilder extends KindBuilder {
1127        @Override
1128        public String getTagName() {
1129            return "email";
1130        }
1131
1132        @Override
1133        public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
1134                AttributeSet attrs) throws DefinitionException, XmlPullParserException,
1135                IOException {
1136            final DataKind kind = newDataKind(context, parser, attrs, false,
1137                    Email.CONTENT_ITEM_TYPE, Email.TYPE, R.string.emailLabelsGroup, Weight.EMAIL,
1138                    R.layout.text_fields_editor_view,
1139                    new EmailActionInflater(), new SimpleInflater(Email.DATA));
1140            kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
1141
1142            return Lists.newArrayList(kind);
1143        }
1144
1145        @Override
1146        protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
1147            // EditType is mutable, so we need to create a new instance every time.
1148            if ("home".equals(type)) return buildEmailType(Email.TYPE_HOME);
1149            if ("work".equals(type)) return buildEmailType(Email.TYPE_WORK);
1150            if ("other".equals(type)) return buildEmailType(Email.TYPE_OTHER);
1151            if ("mobile".equals(type)) return buildEmailType(Email.TYPE_MOBILE);
1152            if ("custom".equals(type)) {
1153                return buildEmailType(Email.TYPE_CUSTOM)
1154                        .setSecondary(true).setCustomColumn(Email.LABEL);
1155            }
1156            return null;
1157        }
1158    }
1159
1160    private static class StructuredPostalKindBuilder extends KindBuilder {
1161        @Override
1162        public String getTagName() {
1163            return "postal";
1164        }
1165
1166        @Override
1167        public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
1168                AttributeSet attrs) throws DefinitionException, XmlPullParserException,
1169                IOException {
1170            final DataKind kind = newDataKind(context, parser, attrs, false,
1171                    StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.TYPE,
1172                    R.string.postalLabelsGroup, Weight.STRUCTURED_POSTAL,
1173                    R.layout.text_fields_editor_view, new PostalActionInflater(),
1174                    new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS));
1175
1176            if (getAttr(attrs, "needsStructured", false)) {
1177                if (Locale.JAPANESE.getLanguage().equals(Locale.getDefault().getLanguage())) {
1178                    // Japanese order
1179                    kind.fieldList.add(new EditField(StructuredPostal.COUNTRY,
1180                            R.string.postal_country, FLAGS_POSTAL).setOptional(true));
1181                    kind.fieldList.add(new EditField(StructuredPostal.POSTCODE,
1182                            R.string.postal_postcode, FLAGS_POSTAL));
1183                    kind.fieldList.add(new EditField(StructuredPostal.REGION,
1184                            R.string.postal_region, FLAGS_POSTAL));
1185                    kind.fieldList.add(new EditField(StructuredPostal.CITY,
1186                            R.string.postal_city,FLAGS_POSTAL));
1187                    kind.fieldList.add(new EditField(StructuredPostal.STREET,
1188                            R.string.postal_street, FLAGS_POSTAL));
1189                } else {
1190                    // Generic order
1191                    kind.fieldList.add(new EditField(StructuredPostal.STREET,
1192                            R.string.postal_street, FLAGS_POSTAL));
1193                    kind.fieldList.add(new EditField(StructuredPostal.CITY,
1194                            R.string.postal_city,FLAGS_POSTAL));
1195                    kind.fieldList.add(new EditField(StructuredPostal.REGION,
1196                            R.string.postal_region, FLAGS_POSTAL));
1197                    kind.fieldList.add(new EditField(StructuredPostal.POSTCODE,
1198                            R.string.postal_postcode, FLAGS_POSTAL));
1199                    kind.fieldList.add(new EditField(StructuredPostal.COUNTRY,
1200                            R.string.postal_country, FLAGS_POSTAL).setOptional(true));
1201                }
1202            } else {
1203                kind.maxLinesForDisplay= MAX_LINES_FOR_POSTAL_ADDRESS;
1204                kind.fieldList.add(
1205                        new EditField(StructuredPostal.FORMATTED_ADDRESS, R.string.postal_address,
1206                                FLAGS_POSTAL));
1207            }
1208
1209            return Lists.newArrayList(kind);
1210        }
1211
1212        @Override
1213        protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
1214            // EditType is mutable, so we need to create a new instance every time.
1215            if ("home".equals(type)) return buildPostalType(StructuredPostal.TYPE_HOME);
1216            if ("work".equals(type)) return buildPostalType(StructuredPostal.TYPE_WORK);
1217            if ("other".equals(type)) return buildPostalType(StructuredPostal.TYPE_OTHER);
1218            if ("custom".equals(type)) {
1219                return buildPostalType(StructuredPostal.TYPE_CUSTOM)
1220                        .setSecondary(true).setCustomColumn(Email.LABEL);
1221            }
1222            return null;
1223        }
1224    }
1225
1226    private static class ImKindBuilder extends KindBuilder {
1227        @Override
1228        public String getTagName() {
1229            return "im";
1230        }
1231
1232        @Override
1233        public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
1234                AttributeSet attrs) throws DefinitionException, XmlPullParserException,
1235                IOException {
1236
1237            // IM is special:
1238            // - It uses "protocol" as the custom label field
1239            // - Its TYPE is fixed to TYPE_OTHER
1240
1241            final DataKind kind = newDataKind(context, parser, attrs, false,
1242                    Im.CONTENT_ITEM_TYPE, Im.PROTOCOL, R.string.imLabelsGroup, Weight.IM,
1243                    R.layout.text_fields_editor_view,
1244                    new ImActionInflater(), new SimpleInflater(Im.DATA) // header / action
1245                    );
1246            kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
1247
1248            kind.defaultValues = new ContentValues();
1249            kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);
1250
1251            return Lists.newArrayList(kind);
1252        }
1253
1254        @Override
1255        protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
1256            if ("aim".equals(type)) return buildImType(Im.PROTOCOL_AIM);
1257            if ("msn".equals(type)) return buildImType(Im.PROTOCOL_MSN);
1258            if ("yahoo".equals(type)) return buildImType(Im.PROTOCOL_YAHOO);
1259            if ("skype".equals(type)) return buildImType(Im.PROTOCOL_SKYPE);
1260            if ("qq".equals(type)) return buildImType(Im.PROTOCOL_QQ);
1261            if ("google_talk".equals(type)) return buildImType(Im.PROTOCOL_GOOGLE_TALK);
1262            if ("icq".equals(type)) return buildImType(Im.PROTOCOL_ICQ);
1263            if ("jabber".equals(type)) return buildImType(Im.PROTOCOL_JABBER);
1264            if ("custom".equals(type)) {
1265                return buildImType(Im.PROTOCOL_CUSTOM).setSecondary(true)
1266                        .setCustomColumn(Im.CUSTOM_PROTOCOL);
1267            }
1268            return null;
1269        }
1270    }
1271
1272    private static class OrganizationKindBuilder extends KindBuilder {
1273        @Override
1274        public String getTagName() {
1275            return "organization";
1276        }
1277
1278        @Override
1279        public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
1280                AttributeSet attrs) throws DefinitionException, XmlPullParserException,
1281                IOException {
1282            final DataKind kind = newDataKind(context, parser, attrs, false,
1283                    Organization.CONTENT_ITEM_TYPE, null, R.string.organizationLabelsGroup,
1284                    Weight.ORGANIZATION, R.layout.text_fields_editor_view ,
1285                    new SimpleInflater(Organization.COMPANY),
1286                    new SimpleInflater(Organization.TITLE));
1287
1288            kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
1289                    FLAGS_GENERIC_NAME));
1290            kind.fieldList.add(new EditField(Organization.TITLE, R.string.ghostData_title,
1291                    FLAGS_GENERIC_NAME));
1292
1293            throwIfList(kind);
1294
1295            return Lists.newArrayList(kind);
1296        }
1297    }
1298
1299    private static class PhotoKindBuilder extends KindBuilder {
1300        @Override
1301        public String getTagName() {
1302            return "photo";
1303        }
1304
1305        @Override
1306        public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
1307                AttributeSet attrs) throws DefinitionException, XmlPullParserException,
1308                IOException {
1309            final DataKind kind = newDataKind(context, parser, attrs, false,
1310                    Photo.CONTENT_ITEM_TYPE, null /* no type */, -1, Weight.NONE, -1,
1311                    null, null // no header, no body
1312                    );
1313
1314            kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
1315
1316            throwIfList(kind);
1317
1318            return Lists.newArrayList(kind);
1319        }
1320    }
1321
1322    private static class NoteKindBuilder extends KindBuilder {
1323        @Override
1324        public String getTagName() {
1325            return "note";
1326        }
1327
1328        @Override
1329        public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
1330                AttributeSet attrs) throws DefinitionException, XmlPullParserException,
1331                IOException {
1332            final DataKind kind = newDataKind(context, parser, attrs, false,
1333                    Note.CONTENT_ITEM_TYPE, null, R.string.label_notes, Weight.NOTE,
1334                    R.layout.text_fields_editor_view,
1335                    new SimpleInflater(R.string.label_notes), new SimpleInflater(Note.NOTE));
1336
1337            kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
1338            kind.maxLinesForDisplay = MAX_LINES_FOR_NOTE;
1339
1340            throwIfList(kind);
1341
1342            return Lists.newArrayList(kind);
1343        }
1344    }
1345
1346    private static class WebsiteKindBuilder extends KindBuilder {
1347        @Override
1348        public String getTagName() {
1349            return "website";
1350        }
1351
1352        @Override
1353        public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
1354                AttributeSet attrs) throws DefinitionException, XmlPullParserException,
1355                IOException {
1356            final DataKind kind = newDataKind(context, parser, attrs, false,
1357                    Website.CONTENT_ITEM_TYPE, null, R.string.websiteLabelsGroup, Weight.WEBSITE,
1358                    R.layout.text_fields_editor_view,
1359                    new SimpleInflater(R.string.websiteLabelsGroup),
1360                    new SimpleInflater(Website.URL));
1361
1362            kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup,
1363                    FLAGS_WEBSITE));
1364
1365            kind.defaultValues = new ContentValues();
1366            kind.defaultValues.put(Website.TYPE, Website.TYPE_OTHER);
1367
1368            return Lists.newArrayList(kind);
1369        }
1370    }
1371
1372    private static class SipAddressKindBuilder extends KindBuilder {
1373        @Override
1374        public String getTagName() {
1375            return "sip_address";
1376        }
1377
1378        @Override
1379        public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
1380                AttributeSet attrs) throws DefinitionException, XmlPullParserException,
1381                IOException {
1382            final DataKind kind = newDataKind(context, parser, attrs, false,
1383                    SipAddress.CONTENT_ITEM_TYPE, null, R.string.label_sip_address,
1384                    Weight.SIP_ADDRESS, R.layout.text_fields_editor_view,
1385                    new SimpleInflater(R.string.label_sip_address),
1386                    new SimpleInflater(SipAddress.SIP_ADDRESS));
1387
1388            kind.fieldList.add(new EditField(SipAddress.SIP_ADDRESS,
1389                    R.string.label_sip_address, FLAGS_SIP_ADDRESS));
1390
1391            throwIfList(kind);
1392
1393            return Lists.newArrayList(kind);
1394        }
1395    }
1396
1397    private static class GroupMembershipKindBuilder extends KindBuilder {
1398        @Override
1399        public String getTagName() {
1400            return "group_membership";
1401        }
1402
1403        @Override
1404        public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
1405                AttributeSet attrs) throws DefinitionException, XmlPullParserException,
1406                IOException {
1407            final DataKind kind = newDataKind(context, parser, attrs, false,
1408                    GroupMembership.CONTENT_ITEM_TYPE, null,
1409                    R.string.groupsLabel, Weight.GROUP_MEMBERSHIP, -1, null, null);
1410
1411            kind.fieldList.add(new EditField(GroupMembership.GROUP_ROW_ID, -1, -1));
1412            kind.maxLinesForDisplay = MAX_LINES_FOR_GROUP;
1413
1414            throwIfList(kind);
1415
1416            return Lists.newArrayList(kind);
1417        }
1418    }
1419
1420    /**
1421     * Event DataKind parser.
1422     *
1423     * Event DataKind is used only for Google/Exchange types, so this parser is not used for now.
1424     */
1425    private static class EventKindBuilder extends KindBuilder {
1426        @Override
1427        public String getTagName() {
1428            return "event";
1429        }
1430
1431        @Override
1432        public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
1433                AttributeSet attrs) throws DefinitionException, XmlPullParserException,
1434                IOException {
1435            final DataKind kind = newDataKind(context, parser, attrs, false,
1436                    Event.CONTENT_ITEM_TYPE, Event.TYPE, R.string.eventLabelsGroup, Weight.EVENT,
1437                    R.layout.event_field_editor_view,
1438                    new EventActionInflater(), new SimpleInflater(Event.START_DATE));
1439
1440            kind.fieldList.add(new EditField(Event.DATA, R.string.eventLabelsGroup, FLAGS_EVENT));
1441
1442            if (getAttr(attrs, Attr.DATE_WITH_TIME, false)) {
1443                kind.dateFormatWithoutYear = DateUtils.NO_YEAR_DATE_AND_TIME_FORMAT;
1444                kind.dateFormatWithYear = DateUtils.DATE_AND_TIME_FORMAT;
1445            } else {
1446                kind.dateFormatWithoutYear = DateUtils.NO_YEAR_DATE_FORMAT;
1447                kind.dateFormatWithYear = DateUtils.FULL_DATE_FORMAT;
1448            }
1449
1450            return Lists.newArrayList(kind);
1451        }
1452
1453        @Override
1454        protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
1455            final boolean yo = getAttr(attrs, Attr.YEAR_OPTIONAL, false);
1456
1457            if ("birthday".equals(type)) {
1458                return buildEventType(Event.TYPE_BIRTHDAY, yo).setSpecificMax(1);
1459            }
1460            if ("anniversary".equals(type)) return buildEventType(Event.TYPE_ANNIVERSARY, yo);
1461            if ("other".equals(type)) return buildEventType(Event.TYPE_OTHER, yo);
1462            if ("custom".equals(type)) {
1463                return buildEventType(Event.TYPE_CUSTOM, yo)
1464                        .setSecondary(true).setCustomColumn(Event.LABEL);
1465            }
1466            return null;
1467        }
1468    }
1469
1470    /**
1471     * Relationship DataKind parser.
1472     *
1473     * Relationship DataKind is used only for Google/Exchange types, so this parser is not used for
1474     * now.
1475     */
1476    private static class RelationshipKindBuilder extends KindBuilder {
1477        @Override
1478        public String getTagName() {
1479            return "relationship";
1480        }
1481
1482        @Override
1483        public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
1484                AttributeSet attrs) throws DefinitionException, XmlPullParserException,
1485                IOException {
1486            final DataKind kind = newDataKind(context, parser, attrs, false,
1487                    Relation.CONTENT_ITEM_TYPE, Relation.TYPE,
1488                    R.string.relationLabelsGroup, Weight.RELATIONSHIP,
1489                    R.layout.text_fields_editor_view,
1490                    new RelationActionInflater(), new SimpleInflater(Relation.NAME));
1491
1492            kind.fieldList.add(new EditField(Relation.DATA, R.string.relationLabelsGroup,
1493                    FLAGS_RELATION));
1494
1495            kind.defaultValues = new ContentValues();
1496            kind.defaultValues.put(Relation.TYPE, Relation.TYPE_SPOUSE);
1497
1498            return Lists.newArrayList(kind);
1499        }
1500
1501        @Override
1502        protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
1503            // EditType is mutable, so we need to create a new instance every time.
1504            if ("assistant".equals(type)) return buildRelationType(Relation.TYPE_ASSISTANT);
1505            if ("brother".equals(type)) return buildRelationType(Relation.TYPE_BROTHER);
1506            if ("child".equals(type)) return buildRelationType(Relation.TYPE_CHILD);
1507            if ("domestic_partner".equals(type)) {
1508                    return buildRelationType(Relation.TYPE_DOMESTIC_PARTNER);
1509            }
1510            if ("father".equals(type)) return buildRelationType(Relation.TYPE_FATHER);
1511            if ("friend".equals(type)) return buildRelationType(Relation.TYPE_FRIEND);
1512            if ("manager".equals(type)) return buildRelationType(Relation.TYPE_MANAGER);
1513            if ("mother".equals(type)) return buildRelationType(Relation.TYPE_MOTHER);
1514            if ("parent".equals(type)) return buildRelationType(Relation.TYPE_PARENT);
1515            if ("partner".equals(type)) return buildRelationType(Relation.TYPE_PARTNER);
1516            if ("referred_by".equals(type)) return buildRelationType(Relation.TYPE_REFERRED_BY);
1517            if ("relative".equals(type)) return buildRelationType(Relation.TYPE_RELATIVE);
1518            if ("sister".equals(type)) return buildRelationType(Relation.TYPE_SISTER);
1519            if ("spouse".equals(type)) return buildRelationType(Relation.TYPE_SPOUSE);
1520            if ("custom".equals(type)) {
1521                return buildRelationType(Relation.TYPE_CUSTOM).setSecondary(true)
1522                        .setCustomColumn(Relation.LABEL);
1523            }
1524            return null;
1525        }
1526    }
1527}
1528