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