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