/* Copyright © 2013-2014, Silent Circle, LLC. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Any redistribution, use, or modification is done solely for personal benefit and not for any commercial purpose or for monetary gain * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Silent Circle nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SILENT CIRCLE, LLC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * This implementation is edited version of original Android sources. */ /* * 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.silentcircle.contacts.model.account; import android.content.ContentValues; import android.content.Context; import android.content.res.Resources; import android.util.AttributeSet; import android.util.Log; import android.view.inputmethod.EditorInfo; import com.silentcircle.contacts.model.dataitem.DataKind; import com.silentcircle.contacts.R; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.BaseTypes; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Email; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Event; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.GroupMembership; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Im; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Nickname; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Note; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Organization; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Phone; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Photo; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.SipAddress; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.StructuredName; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.StructuredPostal; //import com.android.contacts.util.DateUtils; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.List; import java.util.Locale; import java.util.Map; public abstract class BaseAccountType extends AccountType { private static final String TAG = "BaseAccountType"; 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; // since SIP addresses have the same basic format as email addresses protected static final int FLAGS_SIP_ADDRESS = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS; 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; private interface Tag { static final String DATA_KIND = "DataKind"; static final String TYPE = "Type"; } private interface Attr { static final String MAX_OCCURRENCE = "maxOccurs"; static final String DATE_WITH_TIME = "dateWithTime"; static final String YEAR_OPTIONAL = "yearOptional"; static final String KIND = "kind"; static final String TYPE = "type"; } private interface Weight { static final int NONE = -1; static final int ORGANIZATION = 5; static final int PHONE = 10; static final int SIP_ADDRESS = 12; static final int IM = 14; static final int EMAIL = 16; static final int STRUCTURED_POSTAL = 25; static final int NOTE = 110; static final int NICKNAME = 115; static final int WEBSITE = 120; static final int EVENT = 150; static final int RELATIONSHIP = 160; static final int GROUP_MEMBERSHIP = 999; } public BaseAccountType() { this.accountType = null; this.dataSet = null; this.titleRes = R.string.account_phone; this.iconRes = R.drawable.ic_launcher; } protected static EditType buildPhoneType(int type) { return new EditType(type, LabelHelper.PhoneLabel.getTypeLabelResource(type)); } protected static EditType buildEmailType(int type) { return new EditType(type, LabelHelper.EmailLabel.getTypeLabelResource(type)); } protected static EditType buildPostalType(int type) { return new EditType(type, LabelHelper.StructuredPostalLabel.getTypeLabelResource(type)); } protected static EditType buildImType(int type) { return new EditType(type, LabelHelper.ImLabel.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)); // } protected DataKind addDataKindStructuredName(Context context) throws DefinitionException { DataKind kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE, R.string.nameLabelsGroup, -1, true, R.layout.structured_name_editor_view)); kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup); kind.actionBody = new SimpleInflater(Nickname.NAME); kind.typeOverallMax = 1; kind.fieldList = Lists.newArrayList(); 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, -1, true, R.layout.text_fields_editor_view)); kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup); kind.actionBody = new SimpleInflater(Nickname.NAME); kind.typeOverallMax = 1; kind.fieldList = Lists.newArrayList(); 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, -1, true, R.layout.phonetic_name_editor_view)); kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup); kind.actionBody = new SimpleInflater(Nickname.NAME); kind.typeOverallMax = 1; kind.fieldList = Lists.newArrayList(); 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, 115, true, R.layout.text_fields_editor_view)); 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 = Lists.newArrayList(); 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, 10, true, R.layout.text_fields_editor_view)); kind.iconAltRes = R.drawable.ic_text_holo_light; 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 = Lists.newArrayList(); kind.typeList.add(buildPhoneType(Phone.TYPE_SILENT)); 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 = Lists.newArrayList(); 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, R.layout.text_fields_editor_view)); kind.actionHeader = new EmailActionInflater(); kind.actionBody = new SimpleInflater(Email.DATA); kind.typeColumn = Email.TYPE; kind.typeList = Lists.newArrayList(); 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 = Lists.newArrayList(); 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, 25, true, R.layout.text_fields_editor_view)); kind.actionHeader = new PostalActionInflater(); kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS); kind.typeColumn = StructuredPostal.TYPE; kind.typeList = Lists.newArrayList(); 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 = Lists.newArrayList(); 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, R.layout.text_fields_editor_view)); 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 = Lists.newArrayList(); kind.typeList.add(buildImType(Im.PROTOCOL_SILENT)); // 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 = Lists.newArrayList(); 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, 5, true, R.layout.text_fields_editor_view)); kind.actionHeader = new SimpleInflater(Organization.COMPANY); kind.actionBody = new SimpleInflater(Organization.TITLE); kind.typeOverallMax = 1; kind.fieldList = Lists.newArrayList(); 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, -1, true, -1)); kind.typeOverallMax = 1; kind.fieldList = Lists.newArrayList(); 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, 110, true, R.layout.text_fields_editor_view)); kind.typeOverallMax = 1; kind.actionHeader = new SimpleInflater(R.string.label_notes); kind.actionBody = new SimpleInflater(Note.NOTE); kind.fieldList = Lists.newArrayList(); 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, 120, true, R.layout.text_fields_editor_view)); // 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 = Lists.newArrayList(); // 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, R.layout.text_fields_editor_view)); kind.typeOverallMax = 1; kind.actionHeader = new SimpleInflater(R.string.label_sip_address); kind.actionBody = new SimpleInflater(SipAddress.SIP_ADDRESS); kind.fieldList = Lists.newArrayList(); kind.fieldList.add(new EditField(SipAddress.SIP_ADDRESS, R.string.label_sip_address, FLAGS_SIP_ADDRESS)); return kind; } protected DataKind addDataKindGroupMembership(Context context) throws DefinitionException { DataKind kind = addKind(new DataKind(GroupMembership.CONTENT_ITEM_TYPE, R.string.groupsLabel, 999, true, -1)); kind.typeOverallMax = 1; kind.fieldList = Lists.newArrayList(); kind.fieldList.add(new EditField(GroupMembership.GROUP_ROW_ID, -1, -1)); kind.maxLinesForDisplay = MAX_LINES_FOR_GROUP; return kind; } /** * 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 static abstract 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 type == Phone.TYPE_CUSTOM; } @Override protected int getTypeLabelResource(Integer type) { if (type == null) return R.string.call_other; switch (type) { case Phone.TYPE_HOME: return R.string.call_home; case Phone.TYPE_MOBILE: return R.string.call_mobile; case Phone.TYPE_WORK: return R.string.call_work; case Phone.TYPE_SILENT: return R.string.call_silent; // case Phone.TYPE_FAX_WORK: return R.string.call_fax_work; // case Phone.TYPE_FAX_HOME: return R.string.call_fax_home; // case Phone.TYPE_PAGER: return R.string.call_pager; // case Phone.TYPE_OTHER: return R.string.call_other; // case Phone.TYPE_CALLBACK: return R.string.call_callback; // case Phone.TYPE_CAR: return R.string.call_car; // case Phone.TYPE_COMPANY_MAIN: return R.string.call_company_main; // case Phone.TYPE_ISDN: return R.string.call_isdn; // case Phone.TYPE_MAIN: return R.string.call_main; // case Phone.TYPE_OTHER_FAX: return R.string.call_other_fax; // case Phone.TYPE_RADIO: return R.string.call_radio; // case Phone.TYPE_TELEX: return R.string.call_telex; // case Phone.TYPE_TTY_TDD: return R.string.call_tty_tdd; // case Phone.TYPE_WORK_MOBILE: return R.string.call_work_mobile; // case Phone.TYPE_WORK_PAGER: return R.string.call_work_pager; // case Phone.TYPE_ASSISTANT: return R.string.call_assistant; // case Phone.TYPE_MMS: return R.string.call_mms; default: return R.string.call_custom; } } } public static class PhoneActionAltInflater extends CommonInflater { @Override protected boolean isCustom(Integer type) { return type == Phone.TYPE_CUSTOM; } @Override protected int getTypeLabelResource(Integer type) { if (type == null) return R.string.sms_other; switch (type) { case Phone.TYPE_HOME: return R.string.sms_home; case Phone.TYPE_MOBILE: return R.string.sms_mobile; case Phone.TYPE_WORK: return R.string.sms_work; // case Phone.TYPE_FAX_WORK: return R.string.sms_fax_work; // case Phone.TYPE_FAX_HOME: return R.string.sms_fax_home; // case Phone.TYPE_PAGER: return R.string.sms_pager; // case Phone.TYPE_OTHER: return R.string.sms_other; // case Phone.TYPE_CALLBACK: return R.string.sms_callback; // case Phone.TYPE_CAR: return R.string.sms_car; // case Phone.TYPE_COMPANY_MAIN: return R.string.sms_company_main; // case Phone.TYPE_ISDN: return R.string.sms_isdn; // case Phone.TYPE_MAIN: return R.string.sms_main; // case Phone.TYPE_OTHER_FAX: return R.string.sms_other_fax; // case Phone.TYPE_RADIO: return R.string.sms_radio; // case Phone.TYPE_TELEX: return R.string.sms_telex; // case Phone.TYPE_TTY_TDD: return R.string.sms_tty_tdd; // case Phone.TYPE_WORK_MOBILE: return R.string.sms_work_mobile; // case Phone.TYPE_WORK_PAGER: return R.string.sms_work_pager; // case Phone.TYPE_ASSISTANT: return R.string.sms_assistant; // case Phone.TYPE_MMS: return R.string.sms_mms; default: return R.string.sms_custom; } } } 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_SILENT: return R.string.chat_silent; 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; } } } @Override public boolean isGroupMembershipEditable() { return true; } /** * 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 { Log.w(TAG, "Skipping unknown tag " + tag); } } } // 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); } // 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<String, KindBuilder> mBuilders = Maps.newHashMap(); 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<DataKind> 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 static abstract class KindBuilder { public abstract String getTagName(); /** * DataKind tag parser specific to each kind. Subclasses must implement it. */ public abstract List<DataKind> 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, int editorLayoutResourceId, StringInflater actionHeader, StringInflater actionBody) throws DefinitionException, XmlPullParserException, IOException { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Adding DataKind: " + mimeType); } final DataKind kind = new DataKind(mimeType, titleRes, weight, true, editorLayoutResourceId); kind.typeColumn = typeColumn; kind.actionHeader = actionHeader; kind.actionBody = actionBody; kind.fieldList = Lists.newArrayList(); // 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 = Lists.newArrayList(); parseTypes(context, 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(context, 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(Context context, 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(parser, 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(XmlPullParser parser, 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 { @Override public String getTagName() { return "name"; } private static void checkAttributeTrue(boolean value, String attrName) throws DefinitionException { if (!value) { throw new DefinitionException(attrName + " must be true"); } } @Override public List<DataKind> 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<DataKind> kinds = Lists.newArrayList(); // Structured name final DataKind ks = newDataKind(context, parser, attrs, false, StructuredName.CONTENT_ITEM_TYPE, null, R.string.nameLabelsGroup, Weight.NONE, R.layout.structured_name_editor_view, 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, R.layout.text_fields_editor_view, 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, R.layout.phonetic_name_editor_view, 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<DataKind> 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, R.layout.text_fields_editor_view, 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); return Lists.newArrayList(kind); } } private static class PhoneKindBuilder extends KindBuilder { @Override public String getTagName() { return "phone"; } @Override public List<DataKind> 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, R.layout.text_fields_editor_view, new PhoneActionInflater(), new SimpleInflater(Phone.NUMBER)); kind.iconAltRes = R.drawable.ic_text_holo_light; kind.iconAltDescriptionRes = R.string.sms; kind.actionAltHeader = new PhoneActionAltInflater(); kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE)); return Lists.newArrayList(kind); } /** Just to avoid line-wrapping... */ protected static EditType build(int type, boolean secondary) { return new EditType(type, LabelHelper.PhoneLabel.getTypeLabelResource(type)).setSecondary(secondary); } @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 ("silentphone".equals(type)) return build(Phone.TYPE_SILENT, 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<DataKind> 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, R.layout.text_fields_editor_view, new EmailActionInflater(), new SimpleInflater(Email.DATA)); kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL)); return Lists.newArrayList(kind); } @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<DataKind> 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, R.layout.text_fields_editor_view, 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)); } return Lists.newArrayList(kind); } @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<DataKind> 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, R.layout.text_fields_editor_view, 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); return Lists.newArrayList(kind); } @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 ("silenttext".equals(type)) return buildImType(Im.PROTOCOL_SILENT); 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<DataKind> 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, R.layout.text_fields_editor_view , new SimpleInflater(Organization.COMPANY), new SimpleInflater(Organization.TITLE)); 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); return Lists.newArrayList(kind); } } private static class PhotoKindBuilder extends KindBuilder { @Override public String getTagName() { return "photo"; } @Override public List<DataKind> 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 */, -1, Weight.NONE, -1, null, null // no header, no body ); kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1)); throwIfList(kind); return Lists.newArrayList(kind); } } private static class NoteKindBuilder extends KindBuilder { @Override public String getTagName() { return "note"; } @Override public List<DataKind> 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, R.layout.text_fields_editor_view, 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); return Lists.newArrayList(kind); } } // private static class WebsiteKindBuilder extends KindBuilder { // @Override // public String getTagName() { // return "website"; // } // // @Override // public List<DataKind> 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, // R.layout.text_fields_editor_view, // 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); // // return Lists.newArrayList(kind); // } // } private static class SipAddressKindBuilder extends KindBuilder { @Override public String getTagName() { return "sip_address"; } @Override public List<DataKind> 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, R.layout.text_fields_editor_view, 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); return Lists.newArrayList(kind); } } private static class GroupMembershipKindBuilder extends KindBuilder { @Override public String getTagName() { return "group_membership"; } @Override public List<DataKind> 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, -1, null, null); kind.fieldList.add(new EditField(GroupMembership.GROUP_ROW_ID, -1, -1)); kind.maxLinesForDisplay = MAX_LINES_FOR_GROUP; throwIfList(kind); return Lists.newArrayList(kind); } } /** * 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<DataKind> 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, // R.layout.event_field_editor_view, // 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 = DateUtils.NO_YEAR_DATE_AND_TIME_FORMAT; // kind.dateFormatWithYear = DateUtils.DATE_AND_TIME_FORMAT; // } else { // kind.dateFormatWithoutYear = DateUtils.NO_YEAR_DATE_FORMAT; // kind.dateFormatWithYear = DateUtils.FULL_DATE_FORMAT; // } // // return Lists.newArrayList(kind); // } // // @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<DataKind> 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, // R.layout.text_fields_editor_view, // 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); // // return Lists.newArrayList(kind); // } // // @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; // } // } }