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