/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.contacts.common.model.account; import android.content.ContentValues; import android.content.Context; import android.content.res.Resources; import android.provider.ContactsContract.CommonDataKinds.BaseTypes; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Event; import android.provider.ContactsContract.CommonDataKinds.GroupMembership; import android.provider.ContactsContract.CommonDataKinds.Im; import android.provider.ContactsContract.CommonDataKinds.Nickname; import android.provider.ContactsContract.CommonDataKinds.Note; import android.provider.ContactsContract.CommonDataKinds.Organization; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.Photo; import android.provider.ContactsContract.CommonDataKinds.Relation; import android.provider.ContactsContract.CommonDataKinds.SipAddress; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.Website; import android.util.ArrayMap; import android.util.AttributeSet; import android.view.inputmethod.EditorInfo; import com.android.contacts.common.R; import com.android.contacts.common.model.dataitem.DataKind; import com.android.contacts.common.util.CommonDateUtils; import com.android.contacts.common.util.ContactDisplayUtils; import com.android.dialer.common.LogUtil; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; public abstract class BaseAccountType extends AccountType { public static final StringInflater ORGANIZATION_BODY_INFLATER = new StringInflater() { @Override public CharSequence inflateUsing(Context context, ContentValues values) { final CharSequence companyValue = values.containsKey(Organization.COMPANY) ? values.getAsString(Organization.COMPANY) : null; final CharSequence titleValue = values.containsKey(Organization.TITLE) ? values.getAsString(Organization.TITLE) : null; if (companyValue != null && titleValue != null) { return companyValue + ": " + titleValue; } else if (companyValue == null) { return titleValue; } else { return companyValue; } } }; protected static final int FLAGS_PHONE = EditorInfo.TYPE_CLASS_PHONE; protected static final int FLAGS_EMAIL = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS; protected static final int FLAGS_PERSON_NAME = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS | EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME; protected static final int FLAGS_PHONETIC = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PHONETIC; protected static final int FLAGS_GENERIC_NAME = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; protected static final int FLAGS_NOTE = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; protected static final int FLAGS_EVENT = EditorInfo.TYPE_CLASS_TEXT; protected static final int FLAGS_WEBSITE = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_URI; protected static final int FLAGS_POSTAL = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_POSTAL_ADDRESS | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; protected static final int FLAGS_SIP_ADDRESS = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS; // since SIP addresses have the same // basic format as email addresses protected static final int FLAGS_RELATION = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS | EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME; // Specify the maximum number of lines that can be used to display various field types. If no // value is specified for a particular type, we use the default value from {@link DataKind}. protected static final int MAX_LINES_FOR_POSTAL_ADDRESS = 10; protected static final int MAX_LINES_FOR_GROUP = 10; protected static final int MAX_LINES_FOR_NOTE = 100; public BaseAccountType() { this.accountType = null; this.dataSet = null; this.titleRes = R.string.account_phone; this.iconRes = R.mipmap.ic_contacts_launcher; } protected static EditType buildPhoneType(int type) { return new EditType(type, Phone.getTypeLabelResource(type)); } protected static EditType buildEmailType(int type) { return new EditType(type, Email.getTypeLabelResource(type)); } protected static EditType buildPostalType(int type) { return new EditType(type, StructuredPostal.getTypeLabelResource(type)); } protected static EditType buildImType(int type) { return new EditType(type, Im.getProtocolLabelResource(type)); } protected static EditType buildEventType(int type, boolean yearOptional) { return new EventEditType(type, Event.getTypeResource(type)).setYearOptional(yearOptional); } protected static EditType buildRelationType(int type) { return new EditType(type, Relation.getTypeLabelResource(type)); } // Utility methods to keep code shorter. private static boolean getAttr(AttributeSet attrs, String attribute, boolean defaultValue) { return attrs.getAttributeBooleanValue(null, attribute, defaultValue); } private static int getAttr(AttributeSet attrs, String attribute, int defaultValue) { return attrs.getAttributeIntValue(null, attribute, defaultValue); } private static String getAttr(AttributeSet attrs, String attribute) { return attrs.getAttributeValue(null, attribute); } protected DataKind addDataKindStructuredName(Context context) throws DefinitionException { DataKind kind = addKind( new DataKind( StructuredName.CONTENT_ITEM_TYPE, R.string.nameLabelsGroup, Weight.NONE, true)); kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup); kind.actionBody = new SimpleInflater(Nickname.NAME); kind.typeOverallMax = 1; kind.fieldList = new ArrayList<>(); kind.fieldList.add( new EditField(StructuredName.DISPLAY_NAME, R.string.full_name, FLAGS_PERSON_NAME)); kind.fieldList.add( new EditField(StructuredName.PREFIX, R.string.name_prefix, FLAGS_PERSON_NAME) .setLongForm(true)); kind.fieldList.add( new EditField(StructuredName.FAMILY_NAME, R.string.name_family, FLAGS_PERSON_NAME) .setLongForm(true)); kind.fieldList.add( new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle, FLAGS_PERSON_NAME) .setLongForm(true)); kind.fieldList.add( new EditField(StructuredName.GIVEN_NAME, R.string.name_given, FLAGS_PERSON_NAME) .setLongForm(true)); kind.fieldList.add( new EditField(StructuredName.SUFFIX, R.string.name_suffix, FLAGS_PERSON_NAME) .setLongForm(true)); kind.fieldList.add( new EditField( StructuredName.PHONETIC_FAMILY_NAME, R.string.name_phonetic_family, FLAGS_PHONETIC)); kind.fieldList.add( new EditField( StructuredName.PHONETIC_MIDDLE_NAME, R.string.name_phonetic_middle, FLAGS_PHONETIC)); kind.fieldList.add( new EditField( StructuredName.PHONETIC_GIVEN_NAME, R.string.name_phonetic_given, FLAGS_PHONETIC)); return kind; } protected DataKind addDataKindDisplayName(Context context) throws DefinitionException { DataKind kind = addKind( new DataKind( DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME, R.string.nameLabelsGroup, Weight.NONE, true)); kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup); kind.actionBody = new SimpleInflater(Nickname.NAME); kind.typeOverallMax = 1; kind.fieldList = new ArrayList<>(); kind.fieldList.add( new EditField(StructuredName.DISPLAY_NAME, R.string.full_name, FLAGS_PERSON_NAME) .setShortForm(true)); boolean displayOrderPrimary = context.getResources().getBoolean(R.bool.config_editor_field_order_primary); if (!displayOrderPrimary) { kind.fieldList.add( new EditField(StructuredName.PREFIX, R.string.name_prefix, FLAGS_PERSON_NAME) .setLongForm(true)); kind.fieldList.add( new EditField(StructuredName.FAMILY_NAME, R.string.name_family, FLAGS_PERSON_NAME) .setLongForm(true)); kind.fieldList.add( new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle, FLAGS_PERSON_NAME) .setLongForm(true)); kind.fieldList.add( new EditField(StructuredName.GIVEN_NAME, R.string.name_given, FLAGS_PERSON_NAME) .setLongForm(true)); kind.fieldList.add( new EditField(StructuredName.SUFFIX, R.string.name_suffix, FLAGS_PERSON_NAME) .setLongForm(true)); } else { kind.fieldList.add( new EditField(StructuredName.PREFIX, R.string.name_prefix, FLAGS_PERSON_NAME) .setLongForm(true)); kind.fieldList.add( new EditField(StructuredName.GIVEN_NAME, R.string.name_given, FLAGS_PERSON_NAME) .setLongForm(true)); kind.fieldList.add( new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle, FLAGS_PERSON_NAME) .setLongForm(true)); kind.fieldList.add( new EditField(StructuredName.FAMILY_NAME, R.string.name_family, FLAGS_PERSON_NAME) .setLongForm(true)); kind.fieldList.add( new EditField(StructuredName.SUFFIX, R.string.name_suffix, FLAGS_PERSON_NAME) .setLongForm(true)); } return kind; } protected DataKind addDataKindPhoneticName(Context context) throws DefinitionException { DataKind kind = addKind( new DataKind( DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME, R.string.name_phonetic, Weight.NONE, true)); kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup); kind.actionBody = new SimpleInflater(Nickname.NAME); kind.typeOverallMax = 1; kind.fieldList = new ArrayList<>(); kind.fieldList.add( new EditField(DataKind.PSEUDO_COLUMN_PHONETIC_NAME, R.string.name_phonetic, FLAGS_PHONETIC) .setShortForm(true)); kind.fieldList.add( new EditField( StructuredName.PHONETIC_FAMILY_NAME, R.string.name_phonetic_family, FLAGS_PHONETIC) .setLongForm(true)); kind.fieldList.add( new EditField( StructuredName.PHONETIC_MIDDLE_NAME, R.string.name_phonetic_middle, FLAGS_PHONETIC) .setLongForm(true)); kind.fieldList.add( new EditField( StructuredName.PHONETIC_GIVEN_NAME, R.string.name_phonetic_given, FLAGS_PHONETIC) .setLongForm(true)); return kind; } protected DataKind addDataKindNickname(Context context) throws DefinitionException { DataKind kind = addKind( new DataKind( Nickname.CONTENT_ITEM_TYPE, R.string.nicknameLabelsGroup, Weight.NICKNAME, true)); kind.typeOverallMax = 1; kind.actionHeader = new SimpleInflater(R.string.nicknameLabelsGroup); kind.actionBody = new SimpleInflater(Nickname.NAME); kind.defaultValues = new ContentValues(); kind.defaultValues.put(Nickname.TYPE, Nickname.TYPE_DEFAULT); kind.fieldList = new ArrayList<>(); kind.fieldList.add( new EditField(Nickname.NAME, R.string.nicknameLabelsGroup, FLAGS_PERSON_NAME)); return kind; } protected DataKind addDataKindPhone(Context context) throws DefinitionException { DataKind kind = addKind( new DataKind(Phone.CONTENT_ITEM_TYPE, R.string.phoneLabelsGroup, Weight.PHONE, true)); kind.iconAltRes = R.drawable.quantum_ic_message_vd_theme_24; kind.iconAltDescriptionRes = R.string.sms; kind.actionHeader = new PhoneActionInflater(); kind.actionAltHeader = new PhoneActionAltInflater(); kind.actionBody = new SimpleInflater(Phone.NUMBER); kind.typeColumn = Phone.TYPE; kind.typeList = new ArrayList<>(); kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE)); kind.typeList.add(buildPhoneType(Phone.TYPE_HOME)); kind.typeList.add(buildPhoneType(Phone.TYPE_WORK)); kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_WORK).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_HOME).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_PAGER).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER)); kind.typeList.add( buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true).setCustomColumn(Phone.LABEL)); kind.typeList.add(buildPhoneType(Phone.TYPE_CALLBACK).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_CAR).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_COMPANY_MAIN).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_ISDN).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_MAIN).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER_FAX).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_RADIO).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_TELEX).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_TTY_TDD).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_WORK_MOBILE).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_WORK_PAGER).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_ASSISTANT).setSecondary(true)); kind.typeList.add(buildPhoneType(Phone.TYPE_MMS).setSecondary(true)); kind.fieldList = new ArrayList<>(); kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE)); return kind; } protected DataKind addDataKindEmail(Context context) throws DefinitionException { DataKind kind = addKind( new DataKind(Email.CONTENT_ITEM_TYPE, R.string.emailLabelsGroup, Weight.EMAIL, true)); kind.actionHeader = new EmailActionInflater(); kind.actionBody = new SimpleInflater(Email.DATA); kind.typeColumn = Email.TYPE; kind.typeList = new ArrayList<>(); kind.typeList.add(buildEmailType(Email.TYPE_HOME)); kind.typeList.add(buildEmailType(Email.TYPE_WORK)); kind.typeList.add(buildEmailType(Email.TYPE_OTHER)); kind.typeList.add(buildEmailType(Email.TYPE_MOBILE)); kind.typeList.add( buildEmailType(Email.TYPE_CUSTOM).setSecondary(true).setCustomColumn(Email.LABEL)); kind.fieldList = new ArrayList<>(); kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL)); return kind; } protected DataKind addDataKindStructuredPostal(Context context) throws DefinitionException { DataKind kind = addKind( new DataKind( StructuredPostal.CONTENT_ITEM_TYPE, R.string.postalLabelsGroup, Weight.STRUCTURED_POSTAL, true)); kind.actionHeader = new PostalActionInflater(); kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS); kind.typeColumn = StructuredPostal.TYPE; kind.typeList = new ArrayList<>(); kind.typeList.add(buildPostalType(StructuredPostal.TYPE_HOME)); kind.typeList.add(buildPostalType(StructuredPostal.TYPE_WORK)); kind.typeList.add(buildPostalType(StructuredPostal.TYPE_OTHER)); kind.typeList.add( buildPostalType(StructuredPostal.TYPE_CUSTOM) .setSecondary(true) .setCustomColumn(StructuredPostal.LABEL)); kind.fieldList = new ArrayList<>(); kind.fieldList.add( new EditField(StructuredPostal.FORMATTED_ADDRESS, R.string.postal_address, FLAGS_POSTAL)); kind.maxLinesForDisplay = MAX_LINES_FOR_POSTAL_ADDRESS; return kind; } protected DataKind addDataKindIm(Context context) throws DefinitionException { DataKind kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup, Weight.IM, true)); kind.actionHeader = new ImActionInflater(); kind.actionBody = new SimpleInflater(Im.DATA); // NOTE: even though a traditional "type" exists, for editing // purposes we're using the protocol to pick labels kind.defaultValues = new ContentValues(); kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER); kind.typeColumn = Im.PROTOCOL; kind.typeList = new ArrayList<>(); kind.typeList.add(buildImType(Im.PROTOCOL_AIM)); kind.typeList.add(buildImType(Im.PROTOCOL_MSN)); kind.typeList.add(buildImType(Im.PROTOCOL_YAHOO)); kind.typeList.add(buildImType(Im.PROTOCOL_SKYPE)); kind.typeList.add(buildImType(Im.PROTOCOL_QQ)); kind.typeList.add(buildImType(Im.PROTOCOL_GOOGLE_TALK)); kind.typeList.add(buildImType(Im.PROTOCOL_ICQ)); kind.typeList.add(buildImType(Im.PROTOCOL_JABBER)); kind.typeList.add( buildImType(Im.PROTOCOL_CUSTOM).setSecondary(true).setCustomColumn(Im.CUSTOM_PROTOCOL)); kind.fieldList = new ArrayList<>(); kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL)); return kind; } protected DataKind addDataKindOrganization(Context context) throws DefinitionException { DataKind kind = addKind( new DataKind( Organization.CONTENT_ITEM_TYPE, R.string.organizationLabelsGroup, Weight.ORGANIZATION, true)); kind.actionHeader = new SimpleInflater(R.string.organizationLabelsGroup); kind.actionBody = ORGANIZATION_BODY_INFLATER; kind.typeOverallMax = 1; kind.fieldList = new ArrayList<>(); kind.fieldList.add( new EditField(Organization.COMPANY, R.string.ghostData_company, FLAGS_GENERIC_NAME)); kind.fieldList.add( new EditField(Organization.TITLE, R.string.ghostData_title, FLAGS_GENERIC_NAME)); return kind; } protected DataKind addDataKindPhoto(Context context) throws DefinitionException { DataKind kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, Weight.NONE, true)); kind.typeOverallMax = 1; kind.fieldList = new ArrayList<>(); kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1)); return kind; } protected DataKind addDataKindNote(Context context) throws DefinitionException { DataKind kind = addKind(new DataKind(Note.CONTENT_ITEM_TYPE, R.string.label_notes, Weight.NOTE, true)); kind.typeOverallMax = 1; kind.actionHeader = new SimpleInflater(R.string.label_notes); kind.actionBody = new SimpleInflater(Note.NOTE); kind.fieldList = new ArrayList<>(); kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE)); kind.maxLinesForDisplay = MAX_LINES_FOR_NOTE; return kind; } protected DataKind addDataKindWebsite(Context context) throws DefinitionException { DataKind kind = addKind( new DataKind( Website.CONTENT_ITEM_TYPE, R.string.websiteLabelsGroup, Weight.WEBSITE, true)); kind.actionHeader = new SimpleInflater(R.string.websiteLabelsGroup); kind.actionBody = new SimpleInflater(Website.URL); kind.defaultValues = new ContentValues(); kind.defaultValues.put(Website.TYPE, Website.TYPE_OTHER); kind.fieldList = new ArrayList<>(); kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE)); return kind; } protected DataKind addDataKindSipAddress(Context context) throws DefinitionException { DataKind kind = addKind( new DataKind( SipAddress.CONTENT_ITEM_TYPE, R.string.label_sip_address, Weight.SIP_ADDRESS, true)); kind.actionHeader = new SimpleInflater(R.string.label_sip_address); kind.actionBody = new SimpleInflater(SipAddress.SIP_ADDRESS); kind.fieldList = new ArrayList<>(); kind.fieldList.add( new EditField(SipAddress.SIP_ADDRESS, R.string.label_sip_address, FLAGS_SIP_ADDRESS)); kind.typeOverallMax = 1; return kind; } protected DataKind addDataKindGroupMembership(Context context) throws DefinitionException { DataKind kind = addKind( new DataKind( GroupMembership.CONTENT_ITEM_TYPE, R.string.groupsLabel, Weight.GROUP_MEMBERSHIP, true)); kind.typeOverallMax = 1; kind.fieldList = new ArrayList<>(); kind.fieldList.add(new EditField(GroupMembership.GROUP_ROW_ID, -1, -1)); kind.maxLinesForDisplay = MAX_LINES_FOR_GROUP; return kind; } @Override public boolean isGroupMembershipEditable() { return false; } /** Parses the content of the EditSchema tag in contacts.xml. */ protected final void parseEditSchema(Context context, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException, DefinitionException { final int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { final int depth = parser.getDepth(); if (type != XmlPullParser.START_TAG || depth != outerDepth + 1) { continue; // Not direct child tag } final String tag = parser.getName(); if (Tag.DATA_KIND.equals(tag)) { for (DataKind kind : KindParser.INSTANCE.parseDataKindTag(context, parser, attrs)) { addKind(kind); } } else { LogUtil.i("BaseAccountType.parseEditSchema", "Skipping unknown tag " + tag); } } } private interface Tag { String DATA_KIND = "DataKind"; String TYPE = "Type"; } private interface Attr { String MAX_OCCURRENCE = "maxOccurs"; String DATE_WITH_TIME = "dateWithTime"; String YEAR_OPTIONAL = "yearOptional"; String KIND = "kind"; String TYPE = "type"; } protected interface Weight { int NONE = -1; int PHONE = 10; int EMAIL = 15; int STRUCTURED_POSTAL = 25; int NICKNAME = 111; int EVENT = 120; int ORGANIZATION = 125; int NOTE = 130; int IM = 140; int SIP_ADDRESS = 145; int GROUP_MEMBERSHIP = 150; int WEBSITE = 160; int RELATIONSHIP = 999; } /** * Simple inflater that assumes a string resource has a "%s" that will be filled from the given * column. */ public static class SimpleInflater implements StringInflater { private final int mStringRes; private final String mColumnName; public SimpleInflater(int stringRes) { this(stringRes, null); } public SimpleInflater(String columnName) { this(-1, columnName); } public SimpleInflater(int stringRes, String columnName) { mStringRes = stringRes; mColumnName = columnName; } @Override public CharSequence inflateUsing(Context context, ContentValues values) { final boolean validColumn = values.containsKey(mColumnName); final boolean validString = mStringRes > 0; final CharSequence stringValue = validString ? context.getText(mStringRes) : null; final CharSequence columnValue = validColumn ? values.getAsString(mColumnName) : null; if (validString && validColumn) { return String.format(stringValue.toString(), columnValue); } else if (validString) { return stringValue; } else if (validColumn) { return columnValue; } else { return null; } } @Override public String toString() { return this.getClass().getSimpleName() + " mStringRes=" + mStringRes + " mColumnName" + mColumnName; } public String getColumnNameForTest() { return mColumnName; } } public abstract static class CommonInflater implements StringInflater { protected abstract int getTypeLabelResource(Integer type); protected boolean isCustom(Integer type) { return type == BaseTypes.TYPE_CUSTOM; } protected String getTypeColumn() { return Phone.TYPE; } protected String getLabelColumn() { return Phone.LABEL; } protected CharSequence getTypeLabel(Resources res, Integer type, CharSequence label) { final int labelRes = getTypeLabelResource(type); if (type == null) { return res.getText(labelRes); } else if (isCustom(type)) { return res.getString(labelRes, label == null ? "" : label); } else { return res.getText(labelRes); } } @Override public CharSequence inflateUsing(Context context, ContentValues values) { final Integer type = values.getAsInteger(getTypeColumn()); final String label = values.getAsString(getLabelColumn()); return getTypeLabel(context.getResources(), type, label); } @Override public String toString() { return this.getClass().getSimpleName(); } } public static class PhoneActionInflater extends CommonInflater { @Override protected boolean isCustom(Integer type) { return ContactDisplayUtils.isCustomPhoneType(type); } @Override protected int getTypeLabelResource(Integer type) { return ContactDisplayUtils.getPhoneLabelResourceId(type); } } public static class PhoneActionAltInflater extends CommonInflater { @Override protected boolean isCustom(Integer type) { return ContactDisplayUtils.isCustomPhoneType(type); } @Override protected int getTypeLabelResource(Integer type) { return ContactDisplayUtils.getSmsLabelResourceId(type); } } public static class EmailActionInflater extends CommonInflater { @Override protected int getTypeLabelResource(Integer type) { if (type == null) { return R.string.email; } switch (type) { case Email.TYPE_HOME: return R.string.email_home; case Email.TYPE_WORK: return R.string.email_work; case Email.TYPE_OTHER: return R.string.email_other; case Email.TYPE_MOBILE: return R.string.email_mobile; default: return R.string.email_custom; } } } public static class EventActionInflater extends CommonInflater { @Override protected int getTypeLabelResource(Integer type) { return Event.getTypeResource(type); } } public static class RelationActionInflater extends CommonInflater { @Override protected int getTypeLabelResource(Integer type) { return Relation.getTypeLabelResource(type == null ? Relation.TYPE_CUSTOM : type); } } public static class PostalActionInflater extends CommonInflater { @Override protected int getTypeLabelResource(Integer type) { if (type == null) { return R.string.map_other; } switch (type) { case StructuredPostal.TYPE_HOME: return R.string.map_home; case StructuredPostal.TYPE_WORK: return R.string.map_work; case StructuredPostal.TYPE_OTHER: return R.string.map_other; default: return R.string.map_custom; } } } public static class ImActionInflater extends CommonInflater { @Override protected String getTypeColumn() { return Im.PROTOCOL; } @Override protected String getLabelColumn() { return Im.CUSTOM_PROTOCOL; } @Override protected int getTypeLabelResource(Integer type) { if (type == null) { return R.string.chat; } switch (type) { case Im.PROTOCOL_AIM: return R.string.chat_aim; case Im.PROTOCOL_MSN: return R.string.chat_msn; case Im.PROTOCOL_YAHOO: return R.string.chat_yahoo; case Im.PROTOCOL_SKYPE: return R.string.chat_skype; case Im.PROTOCOL_QQ: return R.string.chat_qq; case Im.PROTOCOL_GOOGLE_TALK: return R.string.chat_gtalk; case Im.PROTOCOL_ICQ: return R.string.chat_icq; case Im.PROTOCOL_JABBER: return R.string.chat_jabber; case Im.PROTOCOL_NETMEETING: return R.string.chat; default: return R.string.chat; } } } // TODO Extract it to its own class, and move all KindBuilders to it as well. private static class KindParser { public static final KindParser INSTANCE = new KindParser(); private final Map mBuilders = new ArrayMap<>(); private KindParser() { addBuilder(new NameKindBuilder()); addBuilder(new NicknameKindBuilder()); addBuilder(new PhoneKindBuilder()); addBuilder(new EmailKindBuilder()); addBuilder(new StructuredPostalKindBuilder()); addBuilder(new ImKindBuilder()); addBuilder(new OrganizationKindBuilder()); addBuilder(new PhotoKindBuilder()); addBuilder(new NoteKindBuilder()); addBuilder(new WebsiteKindBuilder()); addBuilder(new SipAddressKindBuilder()); addBuilder(new GroupMembershipKindBuilder()); addBuilder(new EventKindBuilder()); addBuilder(new RelationshipKindBuilder()); } private void addBuilder(KindBuilder builder) { mBuilders.put(builder.getTagName(), builder); } /** * Takes a {@link XmlPullParser} at the start of a DataKind tag, parses it and returns {@link * DataKind}s. (Usually just one, but there are three for the "name" kind.) * *

This method returns a list, because we need to add 3 kinds for the name data kind. * (structured, display and phonetic) */ public List parseDataKindTag( Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { final String kind = getAttr(attrs, Attr.KIND); final KindBuilder builder = mBuilders.get(kind); if (builder != null) { return builder.parseDataKind(context, parser, attrs); } else { throw new DefinitionException("Undefined data kind '" + kind + "'"); } } } private abstract static class KindBuilder { public abstract String getTagName(); /** DataKind tag parser specific to each kind. Subclasses must implement it. */ public abstract List parseDataKind( Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException; /** Creates a new {@link DataKind}, and also parses the child Type tags in the DataKind tag. */ protected final DataKind newDataKind( Context context, XmlPullParser parser, AttributeSet attrs, boolean isPseudo, String mimeType, String typeColumn, int titleRes, int weight, StringInflater actionHeader, StringInflater actionBody) throws DefinitionException, XmlPullParserException, IOException { LogUtil.d("BaseAccountType.newDataKind", "Adding DataKind: " + mimeType); final DataKind kind = new DataKind(mimeType, titleRes, weight, true); kind.typeColumn = typeColumn; kind.actionHeader = actionHeader; kind.actionBody = actionBody; kind.fieldList = new ArrayList<>(); // Get more information from the tag... // A pseudo data kind doesn't have corresponding tag the XML, so we skip this. if (!isPseudo) { kind.typeOverallMax = getAttr(attrs, Attr.MAX_OCCURRENCE, -1); // Process "Type" tags. // If a kind has the type column, contacts.xml must have at least one type // definition. Otherwise, it mustn't have a type definition. if (kind.typeColumn != null) { // Parse and add types. kind.typeList = new ArrayList<>(); parseTypes(parser, attrs, kind, true); if (kind.typeList.size() == 0) { throw new DefinitionException("Kind " + kind.mimeType + " must have at least one type"); } } else { // Make sure it has no types. parseTypes(parser, attrs, kind, false /* can't have types */); } } return kind; } /** * Parses Type elements in a DataKind element, and if {@code canHaveTypes} is true adds them to * the given {@link DataKind}. Otherwise the {@link DataKind} can't have a type, so throws * {@link DefinitionException}. */ private void parseTypes( XmlPullParser parser, AttributeSet attrs, DataKind kind, boolean canHaveTypes) throws DefinitionException, XmlPullParserException, IOException { final int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { final int depth = parser.getDepth(); if (type != XmlPullParser.START_TAG || depth != outerDepth + 1) { continue; // Not direct child tag } final String tag = parser.getName(); if (Tag.TYPE.equals(tag)) { if (canHaveTypes) { kind.typeList.add(parseTypeTag(attrs, kind)); } else { throw new DefinitionException("Kind " + kind.mimeType + " can't have types"); } } else { throw new DefinitionException("Unknown tag: " + tag); } } } /** * Parses a single Type element and returns an {@link EditType} built from it. Uses {@link * #buildEditTypeForTypeTag} defined in subclasses to actually build an {@link EditType}. */ private EditType parseTypeTag(AttributeSet attrs, DataKind kind) throws DefinitionException { final String typeName = getAttr(attrs, Attr.TYPE); final EditType et = buildEditTypeForTypeTag(attrs, typeName); if (et == null) { throw new DefinitionException( "Undefined type '" + typeName + "' for data kind '" + kind.mimeType + "'"); } et.specificMax = getAttr(attrs, Attr.MAX_OCCURRENCE, -1); return et; } /** * Returns an {@link EditType} for the given "type". Subclasses may optionally use the * attributes in the tag to set optional values. (e.g. "yearOptional" for the event kind) */ protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) { return null; } protected final void throwIfList(DataKind kind) throws DefinitionException { if (kind.typeOverallMax != 1) { throw new DefinitionException("Kind " + kind.mimeType + " must have 'overallMax=\"1\"'"); } } } /** DataKind parser for Name. (structured, display, phonetic) */ private static class NameKindBuilder extends KindBuilder { private static void checkAttributeTrue(boolean value, String attrName) throws DefinitionException { if (!value) { throw new DefinitionException(attrName + " must be true"); } } @Override public String getTagName() { return "name"; } @Override public List parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { // Build 3 data kinds: // - StructuredName.CONTENT_ITEM_TYPE // - DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME // - DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME final boolean displayOrderPrimary = context.getResources().getBoolean(R.bool.config_editor_field_order_primary); final boolean supportsDisplayName = getAttr(attrs, "supportsDisplayName", false); final boolean supportsPrefix = getAttr(attrs, "supportsPrefix", false); final boolean supportsMiddleName = getAttr(attrs, "supportsMiddleName", false); final boolean supportsSuffix = getAttr(attrs, "supportsSuffix", false); final boolean supportsPhoneticFamilyName = getAttr(attrs, "supportsPhoneticFamilyName", false); final boolean supportsPhoneticMiddleName = getAttr(attrs, "supportsPhoneticMiddleName", false); final boolean supportsPhoneticGivenName = getAttr(attrs, "supportsPhoneticGivenName", false); // For now, every things must be supported. checkAttributeTrue(supportsDisplayName, "supportsDisplayName"); checkAttributeTrue(supportsPrefix, "supportsPrefix"); checkAttributeTrue(supportsMiddleName, "supportsMiddleName"); checkAttributeTrue(supportsSuffix, "supportsSuffix"); checkAttributeTrue(supportsPhoneticFamilyName, "supportsPhoneticFamilyName"); checkAttributeTrue(supportsPhoneticMiddleName, "supportsPhoneticMiddleName"); checkAttributeTrue(supportsPhoneticGivenName, "supportsPhoneticGivenName"); final List kinds = new ArrayList<>(); // Structured name final DataKind ks = newDataKind( context, parser, attrs, false, StructuredName.CONTENT_ITEM_TYPE, null, R.string.nameLabelsGroup, Weight.NONE, new SimpleInflater(R.string.nameLabelsGroup), new SimpleInflater(Nickname.NAME)); throwIfList(ks); kinds.add(ks); // Note about setLongForm/setShortForm below. // We need to set this only when the type supports display name. (=supportsDisplayName) // Otherwise (i.e. Exchange) we don't set these flags, but instead make some fields // "optional". ks.fieldList.add( new EditField(StructuredName.DISPLAY_NAME, R.string.full_name, FLAGS_PERSON_NAME)); ks.fieldList.add( new EditField(StructuredName.PREFIX, R.string.name_prefix, FLAGS_PERSON_NAME) .setLongForm(true)); ks.fieldList.add( new EditField(StructuredName.FAMILY_NAME, R.string.name_family, FLAGS_PERSON_NAME) .setLongForm(true)); ks.fieldList.add( new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle, FLAGS_PERSON_NAME) .setLongForm(true)); ks.fieldList.add( new EditField(StructuredName.GIVEN_NAME, R.string.name_given, FLAGS_PERSON_NAME) .setLongForm(true)); ks.fieldList.add( new EditField(StructuredName.SUFFIX, R.string.name_suffix, FLAGS_PERSON_NAME) .setLongForm(true)); ks.fieldList.add( new EditField( StructuredName.PHONETIC_FAMILY_NAME, R.string.name_phonetic_family, FLAGS_PHONETIC)); ks.fieldList.add( new EditField( StructuredName.PHONETIC_MIDDLE_NAME, R.string.name_phonetic_middle, FLAGS_PHONETIC)); ks.fieldList.add( new EditField( StructuredName.PHONETIC_GIVEN_NAME, R.string.name_phonetic_given, FLAGS_PHONETIC)); // Display name final DataKind kd = newDataKind( context, parser, attrs, true, DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME, null, R.string.nameLabelsGroup, Weight.NONE, new SimpleInflater(R.string.nameLabelsGroup), new SimpleInflater(Nickname.NAME)); kd.typeOverallMax = 1; kinds.add(kd); kd.fieldList.add( new EditField(StructuredName.DISPLAY_NAME, R.string.full_name, FLAGS_PERSON_NAME) .setShortForm(true)); if (!displayOrderPrimary) { kd.fieldList.add( new EditField(StructuredName.PREFIX, R.string.name_prefix, FLAGS_PERSON_NAME) .setLongForm(true)); kd.fieldList.add( new EditField(StructuredName.FAMILY_NAME, R.string.name_family, FLAGS_PERSON_NAME) .setLongForm(true)); kd.fieldList.add( new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle, FLAGS_PERSON_NAME) .setLongForm(true)); kd.fieldList.add( new EditField(StructuredName.GIVEN_NAME, R.string.name_given, FLAGS_PERSON_NAME) .setLongForm(true)); kd.fieldList.add( new EditField(StructuredName.SUFFIX, R.string.name_suffix, FLAGS_PERSON_NAME) .setLongForm(true)); } else { kd.fieldList.add( new EditField(StructuredName.PREFIX, R.string.name_prefix, FLAGS_PERSON_NAME) .setLongForm(true)); kd.fieldList.add( new EditField(StructuredName.GIVEN_NAME, R.string.name_given, FLAGS_PERSON_NAME) .setLongForm(true)); kd.fieldList.add( new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle, FLAGS_PERSON_NAME) .setLongForm(true)); kd.fieldList.add( new EditField(StructuredName.FAMILY_NAME, R.string.name_family, FLAGS_PERSON_NAME) .setLongForm(true)); kd.fieldList.add( new EditField(StructuredName.SUFFIX, R.string.name_suffix, FLAGS_PERSON_NAME) .setLongForm(true)); } // Phonetic name final DataKind kp = newDataKind( context, parser, attrs, true, DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME, null, R.string.name_phonetic, Weight.NONE, new SimpleInflater(R.string.nameLabelsGroup), new SimpleInflater(Nickname.NAME)); kp.typeOverallMax = 1; kinds.add(kp); // We may want to change the order depending on displayOrderPrimary too. kp.fieldList.add( new EditField( DataKind.PSEUDO_COLUMN_PHONETIC_NAME, R.string.name_phonetic, FLAGS_PHONETIC) .setShortForm(true)); kp.fieldList.add( new EditField( StructuredName.PHONETIC_FAMILY_NAME, R.string.name_phonetic_family, FLAGS_PHONETIC) .setLongForm(true)); kp.fieldList.add( new EditField( StructuredName.PHONETIC_MIDDLE_NAME, R.string.name_phonetic_middle, FLAGS_PHONETIC) .setLongForm(true)); kp.fieldList.add( new EditField( StructuredName.PHONETIC_GIVEN_NAME, R.string.name_phonetic_given, FLAGS_PHONETIC) .setLongForm(true)); return kinds; } } private static class NicknameKindBuilder extends KindBuilder { @Override public String getTagName() { return "nickname"; } @Override public List parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { final DataKind kind = newDataKind( context, parser, attrs, false, Nickname.CONTENT_ITEM_TYPE, null, R.string.nicknameLabelsGroup, Weight.NICKNAME, new SimpleInflater(R.string.nicknameLabelsGroup), new SimpleInflater(Nickname.NAME)); kind.fieldList.add( new EditField(Nickname.NAME, R.string.nicknameLabelsGroup, FLAGS_PERSON_NAME)); kind.defaultValues = new ContentValues(); kind.defaultValues.put(Nickname.TYPE, Nickname.TYPE_DEFAULT); throwIfList(kind); List result = new ArrayList<>(); result.add(kind); return result; } } private static class PhoneKindBuilder extends KindBuilder { /** Just to avoid line-wrapping... */ protected static EditType build(int type, boolean secondary) { return new EditType(type, Phone.getTypeLabelResource(type)).setSecondary(secondary); } @Override public String getTagName() { return "phone"; } @Override public List parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { final DataKind kind = newDataKind( context, parser, attrs, false, Phone.CONTENT_ITEM_TYPE, Phone.TYPE, R.string.phoneLabelsGroup, Weight.PHONE, new PhoneActionInflater(), new SimpleInflater(Phone.NUMBER)); kind.iconAltRes = R.drawable.quantum_ic_message_vd_theme_24; kind.iconAltDescriptionRes = R.string.sms; kind.actionAltHeader = new PhoneActionAltInflater(); kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE)); List result = new ArrayList<>(); result.add(kind); return result; } @Override protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) { if ("home".equals(type)) { return build(Phone.TYPE_HOME, false); } if ("mobile".equals(type)) { return build(Phone.TYPE_MOBILE, false); } if ("work".equals(type)) { return build(Phone.TYPE_WORK, false); } if ("fax_work".equals(type)) { return build(Phone.TYPE_FAX_WORK, true); } if ("fax_home".equals(type)) { return build(Phone.TYPE_FAX_HOME, true); } if ("pager".equals(type)) { return build(Phone.TYPE_PAGER, true); } if ("other".equals(type)) { return build(Phone.TYPE_OTHER, false); } if ("callback".equals(type)) { return build(Phone.TYPE_CALLBACK, true); } if ("car".equals(type)) { return build(Phone.TYPE_CAR, true); } if ("company_main".equals(type)) { return build(Phone.TYPE_COMPANY_MAIN, true); } if ("isdn".equals(type)) { return build(Phone.TYPE_ISDN, true); } if ("main".equals(type)) { return build(Phone.TYPE_MAIN, true); } if ("other_fax".equals(type)) { return build(Phone.TYPE_OTHER_FAX, true); } if ("radio".equals(type)) { return build(Phone.TYPE_RADIO, true); } if ("telex".equals(type)) { return build(Phone.TYPE_TELEX, true); } if ("tty_tdd".equals(type)) { return build(Phone.TYPE_TTY_TDD, true); } if ("work_mobile".equals(type)) { return build(Phone.TYPE_WORK_MOBILE, true); } if ("work_pager".equals(type)) { return build(Phone.TYPE_WORK_PAGER, true); } // Note "assistant" used to be a custom column for the fallback type, but not anymore. if ("assistant".equals(type)) { return build(Phone.TYPE_ASSISTANT, true); } if ("mms".equals(type)) { return build(Phone.TYPE_MMS, true); } if ("custom".equals(type)) { return build(Phone.TYPE_CUSTOM, true).setCustomColumn(Phone.LABEL); } return null; } } private static class EmailKindBuilder extends KindBuilder { @Override public String getTagName() { return "email"; } @Override public List parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { final DataKind kind = newDataKind( context, parser, attrs, false, Email.CONTENT_ITEM_TYPE, Email.TYPE, R.string.emailLabelsGroup, Weight.EMAIL, new EmailActionInflater(), new SimpleInflater(Email.DATA)); kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL)); List result = new ArrayList<>(); result.add(kind); return result; } @Override protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) { // EditType is mutable, so we need to create a new instance every time. if ("home".equals(type)) { return buildEmailType(Email.TYPE_HOME); } if ("work".equals(type)) { return buildEmailType(Email.TYPE_WORK); } if ("other".equals(type)) { return buildEmailType(Email.TYPE_OTHER); } if ("mobile".equals(type)) { return buildEmailType(Email.TYPE_MOBILE); } if ("custom".equals(type)) { return buildEmailType(Email.TYPE_CUSTOM).setSecondary(true).setCustomColumn(Email.LABEL); } return null; } } private static class StructuredPostalKindBuilder extends KindBuilder { @Override public String getTagName() { return "postal"; } @Override public List parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { final DataKind kind = newDataKind( context, parser, attrs, false, StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.TYPE, R.string.postalLabelsGroup, Weight.STRUCTURED_POSTAL, new PostalActionInflater(), new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS)); if (getAttr(attrs, "needsStructured", false)) { if (Locale.JAPANESE.getLanguage().equals(Locale.getDefault().getLanguage())) { // Japanese order kind.fieldList.add( new EditField(StructuredPostal.COUNTRY, R.string.postal_country, FLAGS_POSTAL) .setOptional(true)); kind.fieldList.add( new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode, FLAGS_POSTAL)); kind.fieldList.add( new EditField(StructuredPostal.REGION, R.string.postal_region, FLAGS_POSTAL)); kind.fieldList.add( new EditField(StructuredPostal.CITY, R.string.postal_city, FLAGS_POSTAL)); kind.fieldList.add( new EditField(StructuredPostal.STREET, R.string.postal_street, FLAGS_POSTAL)); } else { // Generic order kind.fieldList.add( new EditField(StructuredPostal.STREET, R.string.postal_street, FLAGS_POSTAL)); kind.fieldList.add( new EditField(StructuredPostal.CITY, R.string.postal_city, FLAGS_POSTAL)); kind.fieldList.add( new EditField(StructuredPostal.REGION, R.string.postal_region, FLAGS_POSTAL)); kind.fieldList.add( new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode, FLAGS_POSTAL)); kind.fieldList.add( new EditField(StructuredPostal.COUNTRY, R.string.postal_country, FLAGS_POSTAL) .setOptional(true)); } } else { kind.maxLinesForDisplay = MAX_LINES_FOR_POSTAL_ADDRESS; kind.fieldList.add( new EditField( StructuredPostal.FORMATTED_ADDRESS, R.string.postal_address, FLAGS_POSTAL)); } List result = new ArrayList<>(); result.add(kind); return result; } @Override protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) { // EditType is mutable, so we need to create a new instance every time. if ("home".equals(type)) { return buildPostalType(StructuredPostal.TYPE_HOME); } if ("work".equals(type)) { return buildPostalType(StructuredPostal.TYPE_WORK); } if ("other".equals(type)) { return buildPostalType(StructuredPostal.TYPE_OTHER); } if ("custom".equals(type)) { return buildPostalType(StructuredPostal.TYPE_CUSTOM) .setSecondary(true) .setCustomColumn(Email.LABEL); } return null; } } private static class ImKindBuilder extends KindBuilder { @Override public String getTagName() { return "im"; } @Override public List parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { // IM is special: // - It uses "protocol" as the custom label field // - Its TYPE is fixed to TYPE_OTHER final DataKind kind = newDataKind( context, parser, attrs, false, Im.CONTENT_ITEM_TYPE, Im.PROTOCOL, R.string.imLabelsGroup, Weight.IM, new ImActionInflater(), new SimpleInflater(Im.DATA) // header / action ); kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL)); kind.defaultValues = new ContentValues(); kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER); List result = new ArrayList<>(); result.add(kind); return result; } @Override protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) { if ("aim".equals(type)) { return buildImType(Im.PROTOCOL_AIM); } if ("msn".equals(type)) { return buildImType(Im.PROTOCOL_MSN); } if ("yahoo".equals(type)) { return buildImType(Im.PROTOCOL_YAHOO); } if ("skype".equals(type)) { return buildImType(Im.PROTOCOL_SKYPE); } if ("qq".equals(type)) { return buildImType(Im.PROTOCOL_QQ); } if ("google_talk".equals(type)) { return buildImType(Im.PROTOCOL_GOOGLE_TALK); } if ("icq".equals(type)) { return buildImType(Im.PROTOCOL_ICQ); } if ("jabber".equals(type)) { return buildImType(Im.PROTOCOL_JABBER); } if ("custom".equals(type)) { return buildImType(Im.PROTOCOL_CUSTOM) .setSecondary(true) .setCustomColumn(Im.CUSTOM_PROTOCOL); } return null; } } private static class OrganizationKindBuilder extends KindBuilder { @Override public String getTagName() { return "organization"; } @Override public List parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { final DataKind kind = newDataKind( context, parser, attrs, false, Organization.CONTENT_ITEM_TYPE, null, R.string.organizationLabelsGroup, Weight.ORGANIZATION, new SimpleInflater(R.string.organizationLabelsGroup), ORGANIZATION_BODY_INFLATER); kind.fieldList.add( new EditField(Organization.COMPANY, R.string.ghostData_company, FLAGS_GENERIC_NAME)); kind.fieldList.add( new EditField(Organization.TITLE, R.string.ghostData_title, FLAGS_GENERIC_NAME)); throwIfList(kind); List result = new ArrayList<>(); result.add(kind); return result; } } private static class PhotoKindBuilder extends KindBuilder { @Override public String getTagName() { return "photo"; } @Override public List parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { final DataKind kind = newDataKind( context, parser, attrs, false, Photo.CONTENT_ITEM_TYPE, null /* no type */, Weight.NONE, -1, null, null // no header, no body ); kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1)); throwIfList(kind); List result = new ArrayList<>(); result.add(kind); return result; } } private static class NoteKindBuilder extends KindBuilder { @Override public String getTagName() { return "note"; } @Override public List parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { final DataKind kind = newDataKind( context, parser, attrs, false, Note.CONTENT_ITEM_TYPE, null, R.string.label_notes, Weight.NOTE, new SimpleInflater(R.string.label_notes), new SimpleInflater(Note.NOTE)); kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE)); kind.maxLinesForDisplay = MAX_LINES_FOR_NOTE; throwIfList(kind); List result = new ArrayList<>(); result.add(kind); return result; } } private static class WebsiteKindBuilder extends KindBuilder { @Override public String getTagName() { return "website"; } @Override public List parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { final DataKind kind = newDataKind( context, parser, attrs, false, Website.CONTENT_ITEM_TYPE, null, R.string.websiteLabelsGroup, Weight.WEBSITE, new SimpleInflater(R.string.websiteLabelsGroup), new SimpleInflater(Website.URL)); kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE)); kind.defaultValues = new ContentValues(); kind.defaultValues.put(Website.TYPE, Website.TYPE_OTHER); List result = new ArrayList<>(); result.add(kind); return result; } } private static class SipAddressKindBuilder extends KindBuilder { @Override public String getTagName() { return "sip_address"; } @Override public List parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { final DataKind kind = newDataKind( context, parser, attrs, false, SipAddress.CONTENT_ITEM_TYPE, null, R.string.label_sip_address, Weight.SIP_ADDRESS, new SimpleInflater(R.string.label_sip_address), new SimpleInflater(SipAddress.SIP_ADDRESS)); kind.fieldList.add( new EditField(SipAddress.SIP_ADDRESS, R.string.label_sip_address, FLAGS_SIP_ADDRESS)); throwIfList(kind); List result = new ArrayList<>(); result.add(kind); return result; } } private static class GroupMembershipKindBuilder extends KindBuilder { @Override public String getTagName() { return "group_membership"; } @Override public List parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { final DataKind kind = newDataKind( context, parser, attrs, false, GroupMembership.CONTENT_ITEM_TYPE, null, R.string.groupsLabel, Weight.GROUP_MEMBERSHIP, null, null); kind.fieldList.add(new EditField(GroupMembership.GROUP_ROW_ID, -1, -1)); kind.maxLinesForDisplay = MAX_LINES_FOR_GROUP; throwIfList(kind); List result = new ArrayList<>(); result.add(kind); return result; } } /** * Event DataKind parser. * *

Event DataKind is used only for Google/Exchange types, so this parser is not used for now. */ private static class EventKindBuilder extends KindBuilder { @Override public String getTagName() { return "event"; } @Override public List parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { final DataKind kind = newDataKind( context, parser, attrs, false, Event.CONTENT_ITEM_TYPE, Event.TYPE, R.string.eventLabelsGroup, Weight.EVENT, new EventActionInflater(), new SimpleInflater(Event.START_DATE)); kind.fieldList.add(new EditField(Event.DATA, R.string.eventLabelsGroup, FLAGS_EVENT)); if (getAttr(attrs, Attr.DATE_WITH_TIME, false)) { kind.dateFormatWithoutYear = CommonDateUtils.NO_YEAR_DATE_AND_TIME_FORMAT; kind.dateFormatWithYear = CommonDateUtils.DATE_AND_TIME_FORMAT; } else { kind.dateFormatWithoutYear = CommonDateUtils.NO_YEAR_DATE_FORMAT; kind.dateFormatWithYear = CommonDateUtils.FULL_DATE_FORMAT; } List result = new ArrayList<>(); result.add(kind); return result; } @Override protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) { final boolean yo = getAttr(attrs, Attr.YEAR_OPTIONAL, false); if ("birthday".equals(type)) { return buildEventType(Event.TYPE_BIRTHDAY, yo).setSpecificMax(1); } if ("anniversary".equals(type)) { return buildEventType(Event.TYPE_ANNIVERSARY, yo); } if ("other".equals(type)) { return buildEventType(Event.TYPE_OTHER, yo); } if ("custom".equals(type)) { return buildEventType(Event.TYPE_CUSTOM, yo) .setSecondary(true) .setCustomColumn(Event.LABEL); } return null; } } /** * Relationship DataKind parser. * *

Relationship DataKind is used only for Google/Exchange types, so this parser is not used for * now. */ private static class RelationshipKindBuilder extends KindBuilder { @Override public String getTagName() { return "relationship"; } @Override public List parseDataKind(Context context, XmlPullParser parser, AttributeSet attrs) throws DefinitionException, XmlPullParserException, IOException { final DataKind kind = newDataKind( context, parser, attrs, false, Relation.CONTENT_ITEM_TYPE, Relation.TYPE, R.string.relationLabelsGroup, Weight.RELATIONSHIP, new RelationActionInflater(), new SimpleInflater(Relation.NAME)); kind.fieldList.add( new EditField(Relation.DATA, R.string.relationLabelsGroup, FLAGS_RELATION)); kind.defaultValues = new ContentValues(); kind.defaultValues.put(Relation.TYPE, Relation.TYPE_SPOUSE); List result = new ArrayList<>(); result.add(kind); return result; } @Override protected EditType buildEditTypeForTypeTag(AttributeSet attrs, String type) { // EditType is mutable, so we need to create a new instance every time. if ("assistant".equals(type)) { return buildRelationType(Relation.TYPE_ASSISTANT); } if ("brother".equals(type)) { return buildRelationType(Relation.TYPE_BROTHER); } if ("child".equals(type)) { return buildRelationType(Relation.TYPE_CHILD); } if ("domestic_partner".equals(type)) { return buildRelationType(Relation.TYPE_DOMESTIC_PARTNER); } if ("father".equals(type)) { return buildRelationType(Relation.TYPE_FATHER); } if ("friend".equals(type)) { return buildRelationType(Relation.TYPE_FRIEND); } if ("manager".equals(type)) { return buildRelationType(Relation.TYPE_MANAGER); } if ("mother".equals(type)) { return buildRelationType(Relation.TYPE_MOTHER); } if ("parent".equals(type)) { return buildRelationType(Relation.TYPE_PARENT); } if ("partner".equals(type)) { return buildRelationType(Relation.TYPE_PARTNER); } if ("referred_by".equals(type)) { return buildRelationType(Relation.TYPE_REFERRED_BY); } if ("relative".equals(type)) { return buildRelationType(Relation.TYPE_RELATIVE); } if ("sister".equals(type)) { return buildRelationType(Relation.TYPE_SISTER); } if ("spouse".equals(type)) { return buildRelationType(Relation.TYPE_SPOUSE); } if ("custom".equals(type)) { return buildRelationType(Relation.TYPE_CUSTOM) .setSecondary(true) .setCustomColumn(Relation.LABEL); } return null; } } }