• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.model;
18 
19 import com.android.contacts.R;
20 import com.android.contacts.test.NeededForTesting;
21 import com.android.contacts.util.DateUtils;
22 import com.google.android.collect.Lists;
23 import com.google.android.collect.Maps;
24 
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.res.Resources;
28 import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
29 import android.provider.ContactsContract.CommonDataKinds.Email;
30 import android.provider.ContactsContract.CommonDataKinds.Event;
31 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
32 import android.provider.ContactsContract.CommonDataKinds.Im;
33 import android.provider.ContactsContract.CommonDataKinds.Nickname;
34 import android.provider.ContactsContract.CommonDataKinds.Note;
35 import android.provider.ContactsContract.CommonDataKinds.Organization;
36 import android.provider.ContactsContract.CommonDataKinds.Phone;
37 import android.provider.ContactsContract.CommonDataKinds.Photo;
38 import android.provider.ContactsContract.CommonDataKinds.Relation;
39 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
40 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
41 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
42 import android.provider.ContactsContract.CommonDataKinds.Website;
43 import android.util.AttributeSet;
44 import android.util.Log;
45 import android.view.inputmethod.EditorInfo;
46 
47 import org.xmlpull.v1.XmlPullParser;
48 import org.xmlpull.v1.XmlPullParserException;
49 
50 import java.io.IOException;
51 import java.util.List;
52 import java.util.Locale;
53 import java.util.Map;
54 
55 public 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 
BaseAccountType()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 
buildPhoneType(int type)123     protected static EditType buildPhoneType(int type) {
124         return new EditType(type, Phone.getTypeLabelResource(type));
125     }
126 
buildEmailType(int type)127     protected static EditType buildEmailType(int type) {
128         return new EditType(type, Email.getTypeLabelResource(type));
129     }
130 
buildPostalType(int type)131     protected static EditType buildPostalType(int type) {
132         return new EditType(type, StructuredPostal.getTypeLabelResource(type));
133     }
134 
buildImType(int type)135     protected static EditType buildImType(int type) {
136         return new EditType(type, Im.getProtocolLabelResource(type));
137     }
138 
buildEventType(int type, boolean yearOptional)139     protected static EditType buildEventType(int type, boolean yearOptional) {
140         return new EventEditType(type, Event.getTypeResource(type)).setYearOptional(yearOptional);
141     }
142 
buildRelationType(int type)143     protected static EditType buildRelationType(int type) {
144         return new EditType(type, Relation.getTypeLabelResource(type));
145     }
146 
addDataKindStructuredName(Context context)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 
addDataKindDisplayName(Context context)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 
addDataKindPhoneticName(Context context)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 
addDataKindNickname(Context context)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 
addDataKindPhone(Context context)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 
addDataKindEmail(Context context)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 
addDataKindStructuredPostal(Context context)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 
addDataKindIm(Context context)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 
addDataKindOrganization(Context context)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 
addDataKindPhoto(Context context)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 
addDataKindNote(Context context)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 
addDataKindWebsite(Context context)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 
addDataKindSipAddress(Context context)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 
addDataKindGroupMembership(Context context)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 
SimpleInflater(int stringRes)455         public SimpleInflater(int stringRes) {
456             this(stringRes, null);
457         }
458 
SimpleInflater(String columnName)459         public SimpleInflater(String columnName) {
460             this(-1, columnName);
461         }
462 
SimpleInflater(int stringRes, String columnName)463         public SimpleInflater(int stringRes, String columnName) {
464             mStringRes = stringRes;
465             mColumnName = columnName;
466         }
467 
468         @Override
inflateUsing(Context context, ContentValues values)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
toString()488         public String toString() {
489             return this.getClass().getSimpleName()
490                     + " mStringRes=" + mStringRes
491                     + " mColumnName" + mColumnName;
492         }
493 
494         @NeededForTesting
getColumnNameForTest()495         public String getColumnNameForTest() {
496             return mColumnName;
497         }
498     }
499 
500     public static abstract class CommonInflater implements StringInflater {
getTypeLabelResource(Integer type)501         protected abstract int getTypeLabelResource(Integer type);
502 
isCustom(Integer type)503         protected boolean isCustom(Integer type) {
504             return type == BaseTypes.TYPE_CUSTOM;
505         }
506 
getTypeColumn()507         protected String getTypeColumn() {
508             return Phone.TYPE;
509         }
510 
getLabelColumn()511         protected String getLabelColumn() {
512             return Phone.LABEL;
513         }
514 
getTypeLabel(Resources res, Integer type, CharSequence label)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
inflateUsing(Context context, ContentValues values)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
toString()534         public String toString() {
535             return this.getClass().getSimpleName();
536         }
537     }
538 
539     public static class PhoneActionInflater extends CommonInflater {
540         @Override
isCustom(Integer type)541         protected boolean isCustom(Integer type) {
542             return type == Phone.TYPE_CUSTOM || type == Phone.TYPE_ASSISTANT;
543         }
544 
545         @Override
getTypeLabelResource(Integer type)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
isCustom(Integer type)576         protected boolean isCustom(Integer type) {
577             return (type == Phone.TYPE_CUSTOM || type == Phone.TYPE_ASSISTANT);
578         }
579 
580         @Override
getTypeLabelResource(Integer type)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
getTypeLabelResource(Integer type)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
getTypeLabelResource(Integer type)625         protected int getTypeLabelResource(Integer type) {
626             return Event.getTypeResource(type);
627         }
628     }
629 
630     public static class RelationActionInflater extends CommonInflater {
631         @Override
getTypeLabelResource(Integer type)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
getTypeLabelResource(Integer type)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
getTypeColumn()652         protected String getTypeColumn() {
653             return Im.PROTOCOL;
654         }
655 
656         @Override
getLabelColumn()657         protected String getLabelColumn() {
658             return Im.CUSTOM_PROTOCOL;
659         }
660 
661         @Override
getTypeLabelResource(Integer type)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
isGroupMembershipEditable()680     public boolean isGroupMembershipEditable() {
681         return false;
682     }
683 
684     /**
685      * Parses the content of the EditSchema tag in contacts.xml.
686      */
parseEditSchema(Context context, XmlPullParser parser, AttributeSet attrs)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.
getAttr(AttributeSet attrs, String attribute, boolean defaultValue)712     private static boolean getAttr(AttributeSet attrs, String attribute, boolean defaultValue) {
713         return attrs.getAttributeBooleanValue(null, attribute, defaultValue);
714     }
715 
getAttr(AttributeSet attrs, String attribute, int defaultValue)716     private static int getAttr(AttributeSet attrs, String attribute, int defaultValue) {
717         return attrs.getAttributeIntValue(null, attribute, defaultValue);
718     }
719 
getAttr(AttributeSet attrs, String attribute)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 
KindParser()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 
addBuilder(KindBuilder builder)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          */
parseDataKindTag(Context context, XmlPullParser parser, AttributeSet attrs)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 
getTagName()773         public abstract String getTagName();
774 
775         /**
776          * DataKind tag parser specific to each kind.  Subclasses must implement it.
777          */
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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          */
newDataKind(Context context, XmlPullParser parser, AttributeSet attrs, boolean isPseudo, String mimeType, String typeColumn, int titleRes, int weight, int editorLayoutResourceId, StringInflater actionHeader, StringInflater actionBody)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          */
parseTypes(Context context, XmlPullParser parser, AttributeSet attrs, DataKind kind, boolean canHaveTypes)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          */
parseTypeTag(XmlPullParser parser, AttributeSet attrs, DataKind kind)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          */
buildEditTypeForTypeTag(AttributeSet attrs, String type)883         protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
884             return null;
885         }
886 
throwIfList(DataKind kind)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
getTagName()900         public String getTagName() {
901             return "name";
902         }
903 
checkAttributeTrue(boolean value, String attrName)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
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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
getTagName()1040         public String getTagName() {
1041             return "nickname";
1042         }
1043 
1044         @Override
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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
getTagName()1067         public String getTagName() {
1068             return "phone";
1069         }
1070 
1071         @Override
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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... */
build(int type, boolean secondary)1090         protected static EditType build(int type, boolean secondary) {
1091             return new EditType(type, Phone.getTypeLabelResource(type)).setSecondary(secondary);
1092         }
1093 
1094         @Override
buildEditTypeForTypeTag(AttributeSet attrs, String type)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
getTagName()1127         public String getTagName() {
1128             return "email";
1129         }
1130 
1131         @Override
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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
buildEditTypeForTypeTag(AttributeSet attrs, String type)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
getTagName()1161         public String getTagName() {
1162             return "postal";
1163         }
1164 
1165         @Override
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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
buildEditTypeForTypeTag(AttributeSet attrs, String type)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
getTagName()1227         public String getTagName() {
1228             return "im";
1229         }
1230 
1231         @Override
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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
buildEditTypeForTypeTag(AttributeSet attrs, String type)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
getTagName()1273         public String getTagName() {
1274             return "organization";
1275         }
1276 
1277         @Override
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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
getTagName()1300         public String getTagName() {
1301             return "photo";
1302         }
1303 
1304         @Override
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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
getTagName()1323         public String getTagName() {
1324             return "note";
1325         }
1326 
1327         @Override
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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
getTagName()1347         public String getTagName() {
1348             return "website";
1349         }
1350 
1351         @Override
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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
getTagName()1373         public String getTagName() {
1374             return "sip_address";
1375         }
1376 
1377         @Override
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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
getTagName()1398         public String getTagName() {
1399             return "group_membership";
1400         }
1401 
1402         @Override
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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
getTagName()1426         public String getTagName() {
1427             return "event";
1428         }
1429 
1430         @Override
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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
buildEditTypeForTypeTag(AttributeSet attrs, String type)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
getTagName()1477         public String getTagName() {
1478             return "relationship";
1479         }
1480 
1481         @Override
parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs)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
buildEditTypeForTypeTag(AttributeSet attrs, String type)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