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