blob: 2911975d529ea7c08c1c59a2f8006e32aed1e458 [file] [log] [blame]
/*
* 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.model.dataitem.DataKind;
import com.android.contacts.common.util.CommonDateUtils;
import com.android.contacts.common.util.ContactDisplayUtils;
import com.android.dialer.common.LogUtil;
import com.android.dialer.contacts.resources.R;
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<String, KindBuilder> 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.)
*
* <p>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 abstract static 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,
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<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 = 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<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,
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<DataKind> 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<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,
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<DataKind> 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<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,
new EmailActionInflater(),
new SimpleInflater(Email.DATA));
kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
List<DataKind> 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<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,
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<DataKind> 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<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,
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<DataKind> 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<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,
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<DataKind> result = new ArrayList<>();
result.add(kind);
return result;
}
}
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 */,
Weight.NONE,
-1,
null,
null // no header, no body
);
kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
throwIfList(kind);
List<DataKind> result = new ArrayList<>();
result.add(kind);
return result;
}
}
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,
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<DataKind> result = new ArrayList<>();
result.add(kind);
return result;
}
}
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,
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<DataKind> result = new ArrayList<>();
result.add(kind);
return result;
}
}
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,
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<DataKind> result = new ArrayList<>();
result.add(kind);
return result;
}
}
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,
null,
null);
kind.fieldList.add(new EditField(GroupMembership.GROUP_ROW_ID, -1, -1));
kind.maxLinesForDisplay = MAX_LINES_FOR_GROUP;
throwIfList(kind);
List<DataKind> result = new ArrayList<>();
result.add(kind);
return result;
}
}
/**
* Event DataKind parser.
*
* <p>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,
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<DataKind> 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.
*
* <p>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,
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<DataKind> 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;
}
}
}