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