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.test.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     private interface Weight {
    103         static final int NONE = -1;
    104         static final int ORGANIZATION = 5;
    105         static final int PHONE = 10;
    106         static final int EMAIL = 15;
    107         static final int IM = 20;
    108         static final int STRUCTURED_POSTAL = 25;
    109         static final int NOTE = 110;
    110         static final int NICKNAME = 115;
    111         static final int WEBSITE = 120;
    112         static final int SIP_ADDRESS = 130;
    113         static final int EVENT = 150;
    114         static final int RELATIONSHIP = 160;
    115         static final int GROUP_MEMBERSHIP = 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_launcher_contacts;
    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, -1, 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, -1, 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, -1, 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, 115, 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                 10, 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                 15, 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, 25, 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, 20,
    340                 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, 5, true));
    372         kind.actionHeader = new SimpleInflater(Organization.COMPANY);
    373         kind.actionBody = new SimpleInflater(Organization.TITLE);
    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, -1, 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, 110,
    395                 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, 120, 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, 130, 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, 999, 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     @Override
    635     public boolean isGroupMembershipEditable() {
    636         return false;
    637     }
    638 
    639     /**
    640      * Parses the content of the EditSchema tag in contacts.xml.
    641      */
    642     protected final void parseEditSchema(Context context, XmlPullParser parser, AttributeSet attrs)
    643             throws XmlPullParserException, IOException, DefinitionException {
    644 
    645         final int outerDepth = parser.getDepth();
    646         int type;
    647         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    648                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    649             final int depth = parser.getDepth();
    650             if (type != XmlPullParser.START_TAG || depth != outerDepth + 1) {
    651                 continue; // Not direct child tag
    652             }
    653 
    654             final String tag = parser.getName();
    655 
    656             if (Tag.DATA_KIND.equals(tag)) {
    657                 for (DataKind kind : KindParser.INSTANCE.parseDataKindTag(context, parser, attrs)) {
    658                     addKind(kind);
    659                 }
    660             } else {
    661                 Log.w(TAG, "Skipping unknown tag " + tag);
    662             }
    663         }
    664     }
    665 
    666     // Utility methods to keep code shorter.
    667     private static boolean getAttr(AttributeSet attrs, String attribute, boolean defaultValue) {
    668         return attrs.getAttributeBooleanValue(null, attribute, defaultValue);
    669     }
    670 
    671     private static int getAttr(AttributeSet attrs, String attribute, int defaultValue) {
    672         return attrs.getAttributeIntValue(null, attribute, defaultValue);
    673     }
    674 
    675     private static String getAttr(AttributeSet attrs, String attribute) {
    676         return attrs.getAttributeValue(null, attribute);
    677     }
    678 
    679     // TODO Extract it to its own class, and move all KindBuilders to it as well.
    680     private static class KindParser {
    681         public static final KindParser INSTANCE = new KindParser();
    682 
    683         private final Map<String, KindBuilder> mBuilders = Maps.newHashMap();
    684 
    685         private KindParser() {
    686             addBuilder(new NameKindBuilder());
    687             addBuilder(new NicknameKindBuilder());
    688             addBuilder(new PhoneKindBuilder());
    689             addBuilder(new EmailKindBuilder());
    690             addBuilder(new StructuredPostalKindBuilder());
    691             addBuilder(new ImKindBuilder());
    692             addBuilder(new OrganizationKindBuilder());
    693             addBuilder(new PhotoKindBuilder());
    694             addBuilder(new NoteKindBuilder());
    695             addBuilder(new WebsiteKindBuilder());
    696             addBuilder(new SipAddressKindBuilder());
    697             addBuilder(new GroupMembershipKindBuilder());
    698             addBuilder(new EventKindBuilder());
    699             addBuilder(new RelationshipKindBuilder());
    700         }
    701 
    702         private void addBuilder(KindBuilder builder) {
    703             mBuilders.put(builder.getTagName(), builder);
    704         }
    705 
    706         /**
    707          * Takes a {@link XmlPullParser} at the start of a DataKind tag, parses it and returns
    708          * {@link DataKind}s.  (Usually just one, but there are three for the "name" kind.)
    709          *
    710          * This method returns a list, because we need to add 3 kinds for the name data kind.
    711          * (structured, display and phonetic)
    712          */
    713         public List<DataKind> parseDataKindTag(Context context, XmlPullParser parser,
    714                 AttributeSet attrs)
    715                 throws DefinitionException, XmlPullParserException, IOException {
    716             final String kind = getAttr(attrs, Attr.KIND);
    717             final KindBuilder builder = mBuilders.get(kind);
    718             if (builder != null) {
    719                 return builder.parseDataKind(context, parser, attrs);
    720             } else {
    721                 throw new DefinitionException("Undefined data kind '" + kind + "'");
    722             }
    723         }
    724     }
    725 
    726     private static abstract class KindBuilder {
    727 
    728         public abstract String getTagName();
    729 
    730         /**
    731          * DataKind tag parser specific to each kind.  Subclasses must implement it.
    732          */
    733         public abstract List<DataKind> parseDataKind(Context context, XmlPullParser parser,
    734                 AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException;
    735 
    736         /**
    737          * Creates a new {@link DataKind}, and also parses the child Type tags in the DataKind
    738          * tag.
    739          */
    740         protected final DataKind newDataKind(Context context, XmlPullParser parser,
    741                 AttributeSet attrs, boolean isPseudo, String mimeType, String typeColumn,
    742                 int titleRes, int weight, StringInflater actionHeader, StringInflater actionBody)
    743                 throws DefinitionException, XmlPullParserException, IOException {
    744 
    745             if (Log.isLoggable(TAG, Log.DEBUG)) {
    746                 Log.d(TAG, "Adding DataKind: " + mimeType);
    747             }
    748 
    749             final DataKind kind = new DataKind(mimeType, titleRes, weight, true);
    750             kind.typeColumn = typeColumn;
    751             kind.actionHeader = actionHeader;
    752             kind.actionBody = actionBody;
    753             kind.fieldList = Lists.newArrayList();
    754 
    755             // Get more information from the tag...
    756             // A pseudo data kind doesn't have corresponding tag the XML, so we skip this.
    757             if (!isPseudo) {
    758                 kind.typeOverallMax = getAttr(attrs, Attr.MAX_OCCURRENCE, -1);
    759 
    760                 // Process "Type" tags.
    761                 // If a kind has the type column, contacts.xml must have at least one type
    762                 // definition.  Otherwise, it mustn't have a type definition.
    763                 if (kind.typeColumn != null) {
    764                     // Parse and add types.
    765                     kind.typeList = Lists.newArrayList();
    766                     parseTypes(context, parser, attrs, kind, true);
    767                     if (kind.typeList.size() == 0) {
    768                         throw new DefinitionException(
    769                                 "Kind " + kind.mimeType + " must have at least one type");
    770                     }
    771                 } else {
    772                     // Make sure it has no types.
    773                     parseTypes(context, parser, attrs, kind, false /* can't have types */);
    774                 }
    775             }
    776 
    777             return kind;
    778         }
    779 
    780         /**
    781          * Parses Type elements in a DataKind element, and if {@code canHaveTypes} is true adds
    782          * them to the given {@link DataKind}. Otherwise the {@link DataKind} can't have a type,
    783          * so throws {@link DefinitionException}.
    784          */
    785         private void parseTypes(Context context, XmlPullParser parser, AttributeSet attrs,
    786                 DataKind kind, boolean canHaveTypes)
    787                 throws DefinitionException, XmlPullParserException, IOException {
    788             final int outerDepth = parser.getDepth();
    789             int type;
    790             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    791                     && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    792                 final int depth = parser.getDepth();
    793                 if (type != XmlPullParser.START_TAG || depth != outerDepth + 1) {
    794                     continue; // Not direct child tag
    795                 }
    796 
    797                 final String tag = parser.getName();
    798                 if (Tag.TYPE.equals(tag)) {
    799                     if (canHaveTypes) {
    800                         kind.typeList.add(parseTypeTag(parser, attrs, kind));
    801                     } else {
    802                         throw new DefinitionException(
    803                                 "Kind " + kind.mimeType + " can't have types");
    804                     }
    805                 } else {
    806                     throw new DefinitionException("Unknown tag: " + tag);
    807                 }
    808             }
    809         }
    810 
    811         /**
    812          * Parses a single Type element and returns an {@link EditType} built from it.  Uses
    813          * {@link #buildEditTypeForTypeTag} defined in subclasses to actually build an
    814          * {@link EditType}.
    815          */
    816         private EditType parseTypeTag(XmlPullParser parser, AttributeSet attrs, DataKind kind)
    817                 throws DefinitionException {
    818 
    819             final String typeName = getAttr(attrs, Attr.TYPE);
    820 
    821             final EditType et = buildEditTypeForTypeTag(attrs, typeName);
    822             if (et == null) {
    823                 throw new DefinitionException(
    824                         "Undefined type '" + typeName + "' for data kind '" + kind.mimeType + "'");
    825             }
    826             et.specificMax = getAttr(attrs, Attr.MAX_OCCURRENCE, -1);
    827 
    828             return et;
    829         }
    830 
    831         /**
    832          * Returns an {@link EditType} for the given "type".  Subclasses may optionally use
    833          * the attributes in the tag to set optional values.
    834          * (e.g. "yearOptional" for the event kind)
    835          */
    836         protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
    837             return null;
    838         }
    839 
    840         protected final void throwIfList(DataKind kind) throws DefinitionException {
    841             if (kind.typeOverallMax != 1) {
    842                 throw new DefinitionException(
    843                         "Kind " + kind.mimeType + " must have 'overallMax=\"1\"'");
    844             }
    845         }
    846     }
    847 
    848     /**
    849      * DataKind parser for Name. (structured, display, phonetic)
    850      */
    851     private static class NameKindBuilder extends KindBuilder {
    852         @Override
    853         public String getTagName() {
    854             return "name";
    855         }
    856 
    857         private static void checkAttributeTrue(boolean value, String attrName)
    858                 throws DefinitionException {
    859             if (!value) {
    860                 throw new DefinitionException(attrName + " must be true");
    861             }
    862         }
    863 
    864         @Override
    865         public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
    866                 AttributeSet attrs) throws DefinitionException, XmlPullParserException,
    867                 IOException {
    868 
    869             // Build 3 data kinds:
    870             // - StructuredName.CONTENT_ITEM_TYPE
    871             // - DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME
    872             // - DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME
    873 
    874             final boolean displayOrderPrimary =
    875                     context.getResources().getBoolean(R.bool.config_editor_field_order_primary);
    876 
    877             final boolean supportsDisplayName = getAttr(attrs, "supportsDisplayName", false);
    878             final boolean supportsPrefix = getAttr(attrs, "supportsPrefix", false);
    879             final boolean supportsMiddleName = getAttr(attrs, "supportsMiddleName", false);
    880             final boolean supportsSuffix = getAttr(attrs, "supportsSuffix", false);
    881             final boolean supportsPhoneticFamilyName =
    882                     getAttr(attrs, "supportsPhoneticFamilyName", false);
    883             final boolean supportsPhoneticMiddleName =
    884                     getAttr(attrs, "supportsPhoneticMiddleName", false);
    885             final boolean supportsPhoneticGivenName =
    886                     getAttr(attrs, "supportsPhoneticGivenName", false);
    887 
    888             // For now, every things must be supported.
    889             checkAttributeTrue(supportsDisplayName, "supportsDisplayName");
    890             checkAttributeTrue(supportsPrefix, "supportsPrefix");
    891             checkAttributeTrue(supportsMiddleName, "supportsMiddleName");
    892             checkAttributeTrue(supportsSuffix, "supportsSuffix");
    893             checkAttributeTrue(supportsPhoneticFamilyName, "supportsPhoneticFamilyName");
    894             checkAttributeTrue(supportsPhoneticMiddleName, "supportsPhoneticMiddleName");
    895             checkAttributeTrue(supportsPhoneticGivenName, "supportsPhoneticGivenName");
    896 
    897             final List<DataKind> kinds = Lists.newArrayList();
    898 
    899             // Structured name
    900             final DataKind ks = newDataKind(context, parser, attrs, false,
    901                     StructuredName.CONTENT_ITEM_TYPE, null, R.string.nameLabelsGroup, Weight.NONE,
    902                     new SimpleInflater(R.string.nameLabelsGroup),
    903                     new SimpleInflater(Nickname.NAME));
    904 
    905             throwIfList(ks);
    906             kinds.add(ks);
    907 
    908             // Note about setLongForm/setShortForm below.
    909             // We need to set this only when the type supports display name. (=supportsDisplayName)
    910             // Otherwise (i.e. Exchange) we don't set these flags, but instead make some fields
    911             // "optional".
    912 
    913             ks.fieldList.add(new EditField(StructuredName.DISPLAY_NAME, R.string.full_name,
    914                     FLAGS_PERSON_NAME));
    915             ks.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
    916                     FLAGS_PERSON_NAME).setLongForm(true));
    917             ks.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
    918                     FLAGS_PERSON_NAME).setLongForm(true));
    919             ks.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
    920                     FLAGS_PERSON_NAME).setLongForm(true));
    921             ks.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
    922                     FLAGS_PERSON_NAME).setLongForm(true));
    923             ks.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
    924                     FLAGS_PERSON_NAME).setLongForm(true));
    925             ks.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
    926                     R.string.name_phonetic_family, FLAGS_PHONETIC));
    927             ks.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
    928                     R.string.name_phonetic_middle, FLAGS_PHONETIC));
    929             ks.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
    930                     R.string.name_phonetic_given, FLAGS_PHONETIC));
    931 
    932             // Display name
    933             final DataKind kd = newDataKind(context, parser, attrs, true,
    934                     DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME, null,
    935                     R.string.nameLabelsGroup, Weight.NONE,
    936                     new SimpleInflater(R.string.nameLabelsGroup),
    937                     new SimpleInflater(Nickname.NAME));
    938             kd.typeOverallMax = 1;
    939             kinds.add(kd);
    940 
    941             kd.fieldList.add(new EditField(StructuredName.DISPLAY_NAME,
    942                     R.string.full_name, FLAGS_PERSON_NAME).setShortForm(true));
    943 
    944             if (!displayOrderPrimary) {
    945                 kd.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
    946                         FLAGS_PERSON_NAME).setLongForm(true));
    947                 kd.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
    948                         FLAGS_PERSON_NAME).setLongForm(true));
    949                 kd.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
    950                         FLAGS_PERSON_NAME).setLongForm(true));
    951                 kd.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
    952                         FLAGS_PERSON_NAME).setLongForm(true));
    953                 kd.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
    954                         FLAGS_PERSON_NAME).setLongForm(true));
    955             } else {
    956                 kd.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
    957                         FLAGS_PERSON_NAME).setLongForm(true));
    958                 kd.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
    959                         FLAGS_PERSON_NAME).setLongForm(true));
    960                 kd.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
    961                         FLAGS_PERSON_NAME).setLongForm(true));
    962                 kd.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
    963                         FLAGS_PERSON_NAME).setLongForm(true));
    964                 kd.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
    965                         FLAGS_PERSON_NAME).setLongForm(true));
    966             }
    967 
    968             // Phonetic name
    969             final DataKind kp = newDataKind(context, parser, attrs, true,
    970                     DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME, null,
    971                     R.string.name_phonetic, Weight.NONE,
    972                     new SimpleInflater(R.string.nameLabelsGroup),
    973                     new SimpleInflater(Nickname.NAME));
    974             kp.typeOverallMax = 1;
    975             kinds.add(kp);
    976 
    977             // We may want to change the order depending on displayOrderPrimary too.
    978             kp.fieldList.add(new EditField(DataKind.PSEUDO_COLUMN_PHONETIC_NAME,
    979                     R.string.name_phonetic, FLAGS_PHONETIC).setShortForm(true));
    980             kp.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
    981                     R.string.name_phonetic_family, FLAGS_PHONETIC).setLongForm(true));
    982             kp.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
    983                     R.string.name_phonetic_middle, FLAGS_PHONETIC).setLongForm(true));
    984             kp.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
    985                     R.string.name_phonetic_given, FLAGS_PHONETIC).setLongForm(true));
    986             return kinds;
    987         }
    988     }
    989 
    990     private static class NicknameKindBuilder extends KindBuilder {
    991         @Override
    992         public String getTagName() {
    993             return "nickname";
    994         }
    995 
    996         @Override
    997         public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
    998                 AttributeSet attrs) throws DefinitionException, XmlPullParserException,
    999                 IOException {
   1000             final DataKind kind = newDataKind(context, parser, attrs, false,
   1001                     Nickname.CONTENT_ITEM_TYPE, null, R.string.nicknameLabelsGroup, Weight.NICKNAME,
   1002                     new SimpleInflater(R.string.nicknameLabelsGroup),
   1003                     new SimpleInflater(Nickname.NAME));
   1004 
   1005             kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
   1006                     FLAGS_PERSON_NAME));
   1007 
   1008             kind.defaultValues = new ContentValues();
   1009             kind.defaultValues.put(Nickname.TYPE, Nickname.TYPE_DEFAULT);
   1010 
   1011             throwIfList(kind);
   1012             return Lists.newArrayList(kind);
   1013         }
   1014     }
   1015 
   1016     private static class PhoneKindBuilder extends KindBuilder {
   1017         @Override
   1018         public String getTagName() {
   1019             return "phone";
   1020         }
   1021 
   1022         @Override
   1023         public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
   1024                 AttributeSet attrs) throws DefinitionException, XmlPullParserException,
   1025                 IOException {
   1026             final DataKind kind = newDataKind(context, parser, attrs, false,
   1027                     Phone.CONTENT_ITEM_TYPE, Phone.TYPE, R.string.phoneLabelsGroup, Weight.PHONE,
   1028                     new PhoneActionInflater(), new SimpleInflater(Phone.NUMBER));
   1029 
   1030             kind.iconAltRes = R.drawable.ic_text_holo_light;
   1031             kind.iconAltDescriptionRes = R.string.sms;
   1032             kind.actionAltHeader = new PhoneActionAltInflater();
   1033 
   1034             kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
   1035 
   1036             return Lists.newArrayList(kind);
   1037         }
   1038 
   1039         /** Just to avoid line-wrapping... */
   1040         protected static EditType build(int type, boolean secondary) {
   1041             return new EditType(type, Phone.getTypeLabelResource(type)).setSecondary(secondary);
   1042         }
   1043 
   1044         @Override
   1045         protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
   1046             if ("home".equals(type)) return build(Phone.TYPE_HOME, false);
   1047             if ("mobile".equals(type)) return build(Phone.TYPE_MOBILE, false);
   1048             if ("work".equals(type)) return build(Phone.TYPE_WORK, false);
   1049             if ("fax_work".equals(type)) return build(Phone.TYPE_FAX_WORK, true);
   1050             if ("fax_home".equals(type)) return build(Phone.TYPE_FAX_HOME, true);
   1051             if ("pager".equals(type)) return build(Phone.TYPE_PAGER, true);
   1052             if ("other".equals(type)) return build(Phone.TYPE_OTHER, false);
   1053             if ("callback".equals(type)) return build(Phone.TYPE_CALLBACK, true);
   1054             if ("car".equals(type)) return build(Phone.TYPE_CAR, true);
   1055             if ("company_main".equals(type)) return build(Phone.TYPE_COMPANY_MAIN, true);
   1056             if ("isdn".equals(type)) return build(Phone.TYPE_ISDN, true);
   1057             if ("main".equals(type)) return build(Phone.TYPE_MAIN, true);
   1058             if ("other_fax".equals(type)) return build(Phone.TYPE_OTHER_FAX, true);
   1059             if ("radio".equals(type)) return build(Phone.TYPE_RADIO, true);
   1060             if ("telex".equals(type)) return build(Phone.TYPE_TELEX, true);
   1061             if ("tty_tdd".equals(type)) return build(Phone.TYPE_TTY_TDD, true);
   1062             if ("work_mobile".equals(type)) return build(Phone.TYPE_WORK_MOBILE, true);
   1063             if ("work_pager".equals(type)) return build(Phone.TYPE_WORK_PAGER, true);
   1064 
   1065             // Note "assistant" used to be a custom column for the fallback type, but not anymore.
   1066             if ("assistant".equals(type)) return build(Phone.TYPE_ASSISTANT, true);
   1067             if ("mms".equals(type)) return build(Phone.TYPE_MMS, true);
   1068             if ("custom".equals(type)) {
   1069                 return build(Phone.TYPE_CUSTOM, true).setCustomColumn(Phone.LABEL);
   1070             }
   1071             return null;
   1072         }
   1073     }
   1074 
   1075     private static class EmailKindBuilder extends KindBuilder {
   1076         @Override
   1077         public String getTagName() {
   1078             return "email";
   1079         }
   1080 
   1081         @Override
   1082         public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
   1083                 AttributeSet attrs) throws DefinitionException, XmlPullParserException,
   1084                 IOException {
   1085             final DataKind kind = newDataKind(context, parser, attrs, false,
   1086                     Email.CONTENT_ITEM_TYPE, Email.TYPE, R.string.emailLabelsGroup, Weight.EMAIL,
   1087                     new EmailActionInflater(), new SimpleInflater(Email.DATA));
   1088             kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
   1089 
   1090             return Lists.newArrayList(kind);
   1091         }
   1092 
   1093         @Override
   1094         protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
   1095             // EditType is mutable, so we need to create a new instance every time.
   1096             if ("home".equals(type)) return buildEmailType(Email.TYPE_HOME);
   1097             if ("work".equals(type)) return buildEmailType(Email.TYPE_WORK);
   1098             if ("other".equals(type)) return buildEmailType(Email.TYPE_OTHER);
   1099             if ("mobile".equals(type)) return buildEmailType(Email.TYPE_MOBILE);
   1100             if ("custom".equals(type)) {
   1101                 return buildEmailType(Email.TYPE_CUSTOM)
   1102                         .setSecondary(true).setCustomColumn(Email.LABEL);
   1103             }
   1104             return null;
   1105         }
   1106     }
   1107 
   1108     private static class StructuredPostalKindBuilder extends KindBuilder {
   1109         @Override
   1110         public String getTagName() {
   1111             return "postal";
   1112         }
   1113 
   1114         @Override
   1115         public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
   1116                 AttributeSet attrs) throws DefinitionException, XmlPullParserException,
   1117                 IOException {
   1118             final DataKind kind = newDataKind(context, parser, attrs, false,
   1119                     StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.TYPE,
   1120                     R.string.postalLabelsGroup, Weight.STRUCTURED_POSTAL,
   1121                     new PostalActionInflater(),
   1122                     new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS));
   1123 
   1124             if (getAttr(attrs, "needsStructured", false)) {
   1125                 if (Locale.JAPANESE.getLanguage().equals(Locale.getDefault().getLanguage())) {
   1126                     // Japanese order
   1127                     kind.fieldList.add(new EditField(StructuredPostal.COUNTRY,
   1128                             R.string.postal_country, FLAGS_POSTAL).setOptional(true));
   1129                     kind.fieldList.add(new EditField(StructuredPostal.POSTCODE,
   1130                             R.string.postal_postcode, FLAGS_POSTAL));
   1131                     kind.fieldList.add(new EditField(StructuredPostal.REGION,
   1132                             R.string.postal_region, FLAGS_POSTAL));
   1133                     kind.fieldList.add(new EditField(StructuredPostal.CITY,
   1134                             R.string.postal_city,FLAGS_POSTAL));
   1135                     kind.fieldList.add(new EditField(StructuredPostal.STREET,
   1136                             R.string.postal_street, FLAGS_POSTAL));
   1137                 } else {
   1138                     // Generic order
   1139                     kind.fieldList.add(new EditField(StructuredPostal.STREET,
   1140                             R.string.postal_street, FLAGS_POSTAL));
   1141                     kind.fieldList.add(new EditField(StructuredPostal.CITY,
   1142                             R.string.postal_city,FLAGS_POSTAL));
   1143                     kind.fieldList.add(new EditField(StructuredPostal.REGION,
   1144                             R.string.postal_region, FLAGS_POSTAL));
   1145                     kind.fieldList.add(new EditField(StructuredPostal.POSTCODE,
   1146                             R.string.postal_postcode, FLAGS_POSTAL));
   1147                     kind.fieldList.add(new EditField(StructuredPostal.COUNTRY,
   1148                             R.string.postal_country, FLAGS_POSTAL).setOptional(true));
   1149                 }
   1150             } else {
   1151                 kind.maxLinesForDisplay= MAX_LINES_FOR_POSTAL_ADDRESS;
   1152                 kind.fieldList.add(
   1153                         new EditField(StructuredPostal.FORMATTED_ADDRESS, R.string.postal_address,
   1154                                 FLAGS_POSTAL));
   1155             }
   1156 
   1157             return Lists.newArrayList(kind);
   1158         }
   1159 
   1160         @Override
   1161         protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
   1162             // EditType is mutable, so we need to create a new instance every time.
   1163             if ("home".equals(type)) return buildPostalType(StructuredPostal.TYPE_HOME);
   1164             if ("work".equals(type)) return buildPostalType(StructuredPostal.TYPE_WORK);
   1165             if ("other".equals(type)) return buildPostalType(StructuredPostal.TYPE_OTHER);
   1166             if ("custom".equals(type)) {
   1167                 return buildPostalType(StructuredPostal.TYPE_CUSTOM)
   1168                         .setSecondary(true).setCustomColumn(Email.LABEL);
   1169             }
   1170             return null;
   1171         }
   1172     }
   1173 
   1174     private static class ImKindBuilder extends KindBuilder {
   1175         @Override
   1176         public String getTagName() {
   1177             return "im";
   1178         }
   1179 
   1180         @Override
   1181         public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
   1182                 AttributeSet attrs) throws DefinitionException, XmlPullParserException,
   1183                 IOException {
   1184 
   1185             // IM is special:
   1186             // - It uses "protocol" as the custom label field
   1187             // - Its TYPE is fixed to TYPE_OTHER
   1188 
   1189             final DataKind kind = newDataKind(context, parser, attrs, false,
   1190                     Im.CONTENT_ITEM_TYPE, Im.PROTOCOL, R.string.imLabelsGroup, Weight.IM,
   1191                     new ImActionInflater(), new SimpleInflater(Im.DATA) // header / action
   1192                     );
   1193             kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
   1194 
   1195             kind.defaultValues = new ContentValues();
   1196             kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);
   1197 
   1198             return Lists.newArrayList(kind);
   1199         }
   1200 
   1201         @Override
   1202         protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
   1203             if ("aim".equals(type)) return buildImType(Im.PROTOCOL_AIM);
   1204             if ("msn".equals(type)) return buildImType(Im.PROTOCOL_MSN);
   1205             if ("yahoo".equals(type)) return buildImType(Im.PROTOCOL_YAHOO);
   1206             if ("skype".equals(type)) return buildImType(Im.PROTOCOL_SKYPE);
   1207             if ("qq".equals(type)) return buildImType(Im.PROTOCOL_QQ);
   1208             if ("google_talk".equals(type)) return buildImType(Im.PROTOCOL_GOOGLE_TALK);
   1209             if ("icq".equals(type)) return buildImType(Im.PROTOCOL_ICQ);
   1210             if ("jabber".equals(type)) return buildImType(Im.PROTOCOL_JABBER);
   1211             if ("custom".equals(type)) {
   1212                 return buildImType(Im.PROTOCOL_CUSTOM).setSecondary(true)
   1213                         .setCustomColumn(Im.CUSTOM_PROTOCOL);
   1214             }
   1215             return null;
   1216         }
   1217     }
   1218 
   1219     private static class OrganizationKindBuilder extends KindBuilder {
   1220         @Override
   1221         public String getTagName() {
   1222             return "organization";
   1223         }
   1224 
   1225         @Override
   1226         public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
   1227                 AttributeSet attrs) throws DefinitionException, XmlPullParserException,
   1228                 IOException {
   1229             final DataKind kind = newDataKind(context, parser, attrs, false,
   1230                     Organization.CONTENT_ITEM_TYPE, null, R.string.organizationLabelsGroup,
   1231                     Weight.ORGANIZATION,
   1232                     new SimpleInflater(Organization.COMPANY),
   1233                     new SimpleInflater(Organization.TITLE));
   1234 
   1235             kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
   1236                     FLAGS_GENERIC_NAME));
   1237             kind.fieldList.add(new EditField(Organization.TITLE, R.string.ghostData_title,
   1238                     FLAGS_GENERIC_NAME));
   1239 
   1240             throwIfList(kind);
   1241 
   1242             return Lists.newArrayList(kind);
   1243         }
   1244     }
   1245 
   1246     private static class PhotoKindBuilder extends KindBuilder {
   1247         @Override
   1248         public String getTagName() {
   1249             return "photo";
   1250         }
   1251 
   1252         @Override
   1253         public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
   1254                 AttributeSet attrs) throws DefinitionException, XmlPullParserException,
   1255                 IOException {
   1256             final DataKind kind = newDataKind(context, parser, attrs, false,
   1257                     Photo.CONTENT_ITEM_TYPE, null /* no type */, Weight.NONE, -1,
   1258                     null, null // no header, no body
   1259                     );
   1260 
   1261             kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
   1262 
   1263             throwIfList(kind);
   1264 
   1265             return Lists.newArrayList(kind);
   1266         }
   1267     }
   1268 
   1269     private static class NoteKindBuilder extends KindBuilder {
   1270         @Override
   1271         public String getTagName() {
   1272             return "note";
   1273         }
   1274 
   1275         @Override
   1276         public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
   1277                 AttributeSet attrs) throws DefinitionException, XmlPullParserException,
   1278                 IOException {
   1279             final DataKind kind = newDataKind(context, parser, attrs, false,
   1280                     Note.CONTENT_ITEM_TYPE, null, R.string.label_notes, Weight.NOTE,
   1281                     new SimpleInflater(R.string.label_notes), new SimpleInflater(Note.NOTE));
   1282 
   1283             kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
   1284             kind.maxLinesForDisplay = MAX_LINES_FOR_NOTE;
   1285 
   1286             throwIfList(kind);
   1287 
   1288             return Lists.newArrayList(kind);
   1289         }
   1290     }
   1291 
   1292     private static class WebsiteKindBuilder extends KindBuilder {
   1293         @Override
   1294         public String getTagName() {
   1295             return "website";
   1296         }
   1297 
   1298         @Override
   1299         public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
   1300                 AttributeSet attrs) throws DefinitionException, XmlPullParserException,
   1301                 IOException {
   1302             final DataKind kind = newDataKind(context, parser, attrs, false,
   1303                     Website.CONTENT_ITEM_TYPE, null, R.string.websiteLabelsGroup, Weight.WEBSITE,
   1304                     new SimpleInflater(R.string.websiteLabelsGroup),
   1305                     new SimpleInflater(Website.URL));
   1306 
   1307             kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup,
   1308                     FLAGS_WEBSITE));
   1309 
   1310             kind.defaultValues = new ContentValues();
   1311             kind.defaultValues.put(Website.TYPE, Website.TYPE_OTHER);
   1312 
   1313             return Lists.newArrayList(kind);
   1314         }
   1315     }
   1316 
   1317     private static class SipAddressKindBuilder extends KindBuilder {
   1318         @Override
   1319         public String getTagName() {
   1320             return "sip_address";
   1321         }
   1322 
   1323         @Override
   1324         public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
   1325                 AttributeSet attrs) throws DefinitionException, XmlPullParserException,
   1326                 IOException {
   1327             final DataKind kind = newDataKind(context, parser, attrs, false,
   1328                     SipAddress.CONTENT_ITEM_TYPE, null, R.string.label_sip_address,
   1329                     Weight.SIP_ADDRESS,
   1330                     new SimpleInflater(R.string.label_sip_address),
   1331                     new SimpleInflater(SipAddress.SIP_ADDRESS));
   1332 
   1333             kind.fieldList.add(new EditField(SipAddress.SIP_ADDRESS,
   1334                     R.string.label_sip_address, FLAGS_SIP_ADDRESS));
   1335 
   1336             throwIfList(kind);
   1337 
   1338             return Lists.newArrayList(kind);
   1339         }
   1340     }
   1341 
   1342     private static class GroupMembershipKindBuilder extends KindBuilder {
   1343         @Override
   1344         public String getTagName() {
   1345             return "group_membership";
   1346         }
   1347 
   1348         @Override
   1349         public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
   1350                 AttributeSet attrs) throws DefinitionException, XmlPullParserException,
   1351                 IOException {
   1352             final DataKind kind = newDataKind(context, parser, attrs, false,
   1353                     GroupMembership.CONTENT_ITEM_TYPE, null,
   1354                     R.string.groupsLabel, Weight.GROUP_MEMBERSHIP, null, null);
   1355 
   1356             kind.fieldList.add(new EditField(GroupMembership.GROUP_ROW_ID, -1, -1));
   1357             kind.maxLinesForDisplay = MAX_LINES_FOR_GROUP;
   1358 
   1359             throwIfList(kind);
   1360 
   1361             return Lists.newArrayList(kind);
   1362         }
   1363     }
   1364 
   1365     /**
   1366      * Event DataKind parser.
   1367      *
   1368      * Event DataKind is used only for Google/Exchange types, so this parser is not used for now.
   1369      */
   1370     private static class EventKindBuilder extends KindBuilder {
   1371         @Override
   1372         public String getTagName() {
   1373             return "event";
   1374         }
   1375 
   1376         @Override
   1377         public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
   1378                 AttributeSet attrs) throws DefinitionException, XmlPullParserException,
   1379                 IOException {
   1380             final DataKind kind = newDataKind(context, parser, attrs, false,
   1381                     Event.CONTENT_ITEM_TYPE, Event.TYPE, R.string.eventLabelsGroup, Weight.EVENT,
   1382                     new EventActionInflater(), new SimpleInflater(Event.START_DATE));
   1383 
   1384             kind.fieldList.add(new EditField(Event.DATA, R.string.eventLabelsGroup, FLAGS_EVENT));
   1385 
   1386             if (getAttr(attrs, Attr.DATE_WITH_TIME, false)) {
   1387                 kind.dateFormatWithoutYear = CommonDateUtils.NO_YEAR_DATE_AND_TIME_FORMAT;
   1388                 kind.dateFormatWithYear = CommonDateUtils.DATE_AND_TIME_FORMAT;
   1389             } else {
   1390                 kind.dateFormatWithoutYear = CommonDateUtils.NO_YEAR_DATE_FORMAT;
   1391                 kind.dateFormatWithYear = CommonDateUtils.FULL_DATE_FORMAT;
   1392             }
   1393 
   1394             return Lists.newArrayList(kind);
   1395         }
   1396 
   1397         @Override
   1398         protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
   1399             final boolean yo = getAttr(attrs, Attr.YEAR_OPTIONAL, false);
   1400 
   1401             if ("birthday".equals(type)) {
   1402                 return buildEventType(Event.TYPE_BIRTHDAY, yo).setSpecificMax(1);
   1403             }
   1404             if ("anniversary".equals(type)) return buildEventType(Event.TYPE_ANNIVERSARY, yo);
   1405             if ("other".equals(type)) return buildEventType(Event.TYPE_OTHER, yo);
   1406             if ("custom".equals(type)) {
   1407                 return buildEventType(Event.TYPE_CUSTOM, yo)
   1408                         .setSecondary(true).setCustomColumn(Event.LABEL);
   1409             }
   1410             return null;
   1411         }
   1412     }
   1413 
   1414     /**
   1415      * Relationship DataKind parser.
   1416      *
   1417      * Relationship DataKind is used only for Google/Exchange types, so this parser is not used for
   1418      * now.
   1419      */
   1420     private static class RelationshipKindBuilder extends KindBuilder {
   1421         @Override
   1422         public String getTagName() {
   1423             return "relationship";
   1424         }
   1425 
   1426         @Override
   1427         public List<DataKind> parseDataKind(Context context, XmlPullParser parser,
   1428                 AttributeSet attrs) throws DefinitionException, XmlPullParserException,
   1429                 IOException {
   1430             final DataKind kind = newDataKind(context, parser, attrs, false,
   1431                     Relation.CONTENT_ITEM_TYPE, Relation.TYPE,
   1432                     R.string.relationLabelsGroup, Weight.RELATIONSHIP,
   1433                     new RelationActionInflater(), new SimpleInflater(Relation.NAME));
   1434 
   1435             kind.fieldList.add(new EditField(Relation.DATA, R.string.relationLabelsGroup,
   1436                     FLAGS_RELATION));
   1437 
   1438             kind.defaultValues = new ContentValues();
   1439             kind.defaultValues.put(Relation.TYPE, Relation.TYPE_SPOUSE);
   1440 
   1441             return Lists.newArrayList(kind);
   1442         }
   1443 
   1444         @Override
   1445         protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) {
   1446             // EditType is mutable, so we need to create a new instance every time.
   1447             if ("assistant".equals(type)) return buildRelationType(Relation.TYPE_ASSISTANT);
   1448             if ("brother".equals(type)) return buildRelationType(Relation.TYPE_BROTHER);
   1449             if ("child".equals(type)) return buildRelationType(Relation.TYPE_CHILD);
   1450             if ("domestic_partner".equals(type)) {
   1451                     return buildRelationType(Relation.TYPE_DOMESTIC_PARTNER);
   1452             }
   1453             if ("father".equals(type)) return buildRelationType(Relation.TYPE_FATHER);
   1454             if ("friend".equals(type)) return buildRelationType(Relation.TYPE_FRIEND);
   1455             if ("manager".equals(type)) return buildRelationType(Relation.TYPE_MANAGER);
   1456             if ("mother".equals(type)) return buildRelationType(Relation.TYPE_MOTHER);
   1457             if ("parent".equals(type)) return buildRelationType(Relation.TYPE_PARENT);
   1458             if ("partner".equals(type)) return buildRelationType(Relation.TYPE_PARTNER);
   1459             if ("referred_by".equals(type)) return buildRelationType(Relation.TYPE_REFERRED_BY);
   1460             if ("relative".equals(type)) return buildRelationType(Relation.TYPE_RELATIVE);
   1461             if ("sister".equals(type)) return buildRelationType(Relation.TYPE_SISTER);
   1462             if ("spouse".equals(type)) return buildRelationType(Relation.TYPE_SPOUSE);
   1463             if ("custom".equals(type)) {
   1464                 return buildRelationType(Relation.TYPE_CUSTOM).setSecondary(true)
   1465                         .setCustomColumn(Relation.LABEL);
   1466             }
   1467             return null;
   1468         }
   1469     }
   1470 }
   1471