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