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