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; 18 19 import android.content.ContentValues; 20 import android.content.Context; 21 import android.database.Cursor; 22 import android.net.Uri; 23 import android.os.Bundle; 24 import android.provider.ContactsContract; 25 import android.provider.ContactsContract.CommonDataKinds.BaseTypes; 26 import android.provider.ContactsContract.CommonDataKinds.Email; 27 import android.provider.ContactsContract.CommonDataKinds.Event; 28 import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 29 import android.provider.ContactsContract.CommonDataKinds.Im; 30 import android.provider.ContactsContract.CommonDataKinds.Nickname; 31 import android.provider.ContactsContract.CommonDataKinds.Note; 32 import android.provider.ContactsContract.CommonDataKinds.Organization; 33 import android.provider.ContactsContract.CommonDataKinds.Phone; 34 import android.provider.ContactsContract.CommonDataKinds.Photo; 35 import android.provider.ContactsContract.CommonDataKinds.Relation; 36 import android.provider.ContactsContract.CommonDataKinds.SipAddress; 37 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 38 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 39 import android.provider.ContactsContract.CommonDataKinds.Website; 40 import android.provider.ContactsContract.Data; 41 import android.provider.ContactsContract.Intents; 42 import android.provider.ContactsContract.Intents.Insert; 43 import android.provider.ContactsContract.RawContacts; 44 import android.text.TextUtils; 45 import android.util.Log; 46 import android.util.SparseArray; 47 import android.util.SparseIntArray; 48 49 import com.android.contacts.common.ContactsUtils; 50 import com.android.contacts.common.model.AccountTypeManager; 51 import com.android.contacts.common.model.ValuesDelta; 52 import com.android.contacts.common.util.CommonDateUtils; 53 import com.android.contacts.common.util.DateUtils; 54 import com.android.contacts.common.util.NameConverter; 55 import com.android.contacts.common.model.account.AccountType; 56 import com.android.contacts.common.model.account.AccountType.EditField; 57 import com.android.contacts.common.model.account.AccountType.EditType; 58 import com.android.contacts.common.model.account.AccountType.EventEditType; 59 import com.android.contacts.common.model.account.GoogleAccountType; 60 import com.android.contacts.common.model.dataitem.DataKind; 61 import com.android.contacts.common.model.dataitem.PhoneDataItem; 62 import com.android.contacts.common.model.dataitem.StructuredNameDataItem; 63 64 import java.text.ParsePosition; 65 import java.util.ArrayList; 66 import java.util.Arrays; 67 import java.util.Calendar; 68 import java.util.Date; 69 import java.util.HashSet; 70 import java.util.Iterator; 71 import java.util.List; 72 import java.util.Locale; 73 import java.util.Set; 74 75 /** 76 * Helper methods for modifying an {@link RawContactDelta}, such as inserting 77 * new rows, or enforcing {@link AccountType}. 78 */ 79 public class RawContactModifier { 80 private static final String TAG = RawContactModifier.class.getSimpleName(); 81 82 /** Set to true in order to view logs on entity operations */ 83 private static final boolean DEBUG = false; 84 85 /** 86 * For the given {@link RawContactDelta}, determine if the given 87 * {@link DataKind} could be inserted under specific 88 * {@link AccountType}. 89 */ 90 public static boolean canInsert(RawContactDelta state, DataKind kind) { 91 // Insert possible when have valid types and under overall maximum 92 final int visibleCount = state.getMimeEntriesCount(kind.mimeType, true); 93 final boolean validTypes = hasValidTypes(state, kind); 94 final boolean validOverall = (kind.typeOverallMax == -1) 95 || (visibleCount < kind.typeOverallMax); 96 return (validTypes && validOverall); 97 } 98 99 public static boolean hasValidTypes(RawContactDelta state, DataKind kind) { 100 if (RawContactModifier.hasEditTypes(kind)) { 101 return (getValidTypes(state, kind).size() > 0); 102 } else { 103 return true; 104 } 105 } 106 107 /** 108 * Ensure that at least one of the given {@link DataKind} exists in the 109 * given {@link RawContactDelta} state, and try creating one if none exist. 110 * @return The child (either newly created or the first existing one), or null if the 111 * account doesn't support this {@link DataKind}. 112 */ 113 public static ValuesDelta ensureKindExists( 114 RawContactDelta state, AccountType accountType, String mimeType) { 115 final DataKind kind = accountType.getKindForMimetype(mimeType); 116 final boolean hasChild = state.getMimeEntriesCount(mimeType, true) > 0; 117 118 if (kind != null) { 119 if (hasChild) { 120 // Return the first entry. 121 return state.getMimeEntries(mimeType).get(0); 122 } else { 123 // Create child when none exists and valid kind 124 final ValuesDelta child = insertChild(state, kind); 125 if (kind.mimeType.equals(Photo.CONTENT_ITEM_TYPE)) { 126 child.setFromTemplate(true); 127 } 128 return child; 129 } 130 } 131 return null; 132 } 133 134 /** 135 * For the given {@link RawContactDelta} and {@link DataKind}, return the 136 * list possible {@link EditType} options available based on 137 * {@link AccountType}. 138 */ 139 public static ArrayList<EditType> getValidTypes(RawContactDelta state, DataKind kind) { 140 return getValidTypes(state, kind, null, true, null); 141 } 142 143 /** 144 * For the given {@link RawContactDelta} and {@link DataKind}, return the 145 * list possible {@link EditType} options available based on 146 * {@link AccountType}. 147 * 148 * @param forceInclude Always include this {@link EditType} in the returned 149 * list, even when an otherwise-invalid choice. This is useful 150 * when showing a dialog that includes the current type. 151 */ 152 public static ArrayList<EditType> getValidTypes(RawContactDelta state, DataKind kind, 153 EditType forceInclude) { 154 return getValidTypes(state, kind, forceInclude, true, null); 155 } 156 157 /** 158 * For the given {@link RawContactDelta} and {@link DataKind}, return the 159 * list possible {@link EditType} options available based on 160 * {@link AccountType}. 161 * 162 * @param forceInclude Always include this {@link EditType} in the returned 163 * list, even when an otherwise-invalid choice. This is useful 164 * when showing a dialog that includes the current type. 165 * @param includeSecondary If true, include any valid types marked as 166 * {@link EditType#secondary}. 167 * @param typeCount When provided, will be used for the frequency count of 168 * each {@link EditType}, otherwise built using 169 * {@link #getTypeFrequencies(RawContactDelta, DataKind)}. 170 */ 171 private static ArrayList<EditType> getValidTypes(RawContactDelta state, DataKind kind, 172 EditType forceInclude, boolean includeSecondary, SparseIntArray typeCount) { 173 final ArrayList<EditType> validTypes = new ArrayList<EditType>(); 174 175 // Bail early if no types provided 176 if (!hasEditTypes(kind)) return validTypes; 177 178 if (typeCount == null) { 179 // Build frequency counts if not provided 180 typeCount = getTypeFrequencies(state, kind); 181 } 182 183 // Build list of valid types 184 final int overallCount = typeCount.get(FREQUENCY_TOTAL); 185 for (EditType type : kind.typeList) { 186 final boolean validOverall = (kind.typeOverallMax == -1 ? true 187 : overallCount < kind.typeOverallMax); 188 final boolean validSpecific = (type.specificMax == -1 ? true : typeCount 189 .get(type.rawValue) < type.specificMax); 190 final boolean validSecondary = (includeSecondary ? true : !type.secondary); 191 final boolean forcedInclude = type.equals(forceInclude); 192 if (forcedInclude || (validOverall && validSpecific && validSecondary)) { 193 // Type is valid when no limit, under limit, or forced include 194 validTypes.add(type); 195 } 196 } 197 198 return validTypes; 199 } 200 201 private static final int FREQUENCY_TOTAL = Integer.MIN_VALUE; 202 203 /** 204 * Count up the frequency that each {@link EditType} appears in the given 205 * {@link RawContactDelta}. The returned {@link SparseIntArray} maps from 206 * {@link EditType#rawValue} to counts, with the total overall count stored 207 * as {@link #FREQUENCY_TOTAL}. 208 */ 209 private static SparseIntArray getTypeFrequencies(RawContactDelta state, DataKind kind) { 210 final SparseIntArray typeCount = new SparseIntArray(); 211 212 // Find all entries for this kind, bailing early if none found 213 final List<ValuesDelta> mimeEntries = state.getMimeEntries(kind.mimeType); 214 if (mimeEntries == null) return typeCount; 215 216 int totalCount = 0; 217 for (ValuesDelta entry : mimeEntries) { 218 // Only count visible entries 219 if (!entry.isVisible()) continue; 220 totalCount++; 221 222 final EditType type = getCurrentType(entry, kind); 223 if (type != null) { 224 final int count = typeCount.get(type.rawValue); 225 typeCount.put(type.rawValue, count + 1); 226 } 227 } 228 typeCount.put(FREQUENCY_TOTAL, totalCount); 229 return typeCount; 230 } 231 232 /** 233 * Check if the given {@link DataKind} has multiple types that should be 234 * displayed for users to pick. 235 */ 236 public static boolean hasEditTypes(DataKind kind) { 237 return kind.typeList != null && kind.typeList.size() > 0; 238 } 239 240 /** 241 * Find the {@link EditType} that describes the given 242 * {@link ValuesDelta} row, assuming the given {@link DataKind} dictates 243 * the possible types. 244 */ 245 public static EditType getCurrentType(ValuesDelta entry, DataKind kind) { 246 final Long rawValue = entry.getAsLong(kind.typeColumn); 247 if (rawValue == null) return null; 248 return getType(kind, rawValue.intValue()); 249 } 250 251 /** 252 * Find the {@link EditType} that describes the given {@link ContentValues} row, 253 * assuming the given {@link DataKind} dictates the possible types. 254 */ 255 public static EditType getCurrentType(ContentValues entry, DataKind kind) { 256 if (kind.typeColumn == null) return null; 257 final Integer rawValue = entry.getAsInteger(kind.typeColumn); 258 if (rawValue == null) return null; 259 return getType(kind, rawValue); 260 } 261 262 /** 263 * Find the {@link EditType} that describes the given {@link Cursor} row, 264 * assuming the given {@link DataKind} dictates the possible types. 265 */ 266 public static EditType getCurrentType(Cursor cursor, DataKind kind) { 267 if (kind.typeColumn == null) return null; 268 final int index = cursor.getColumnIndex(kind.typeColumn); 269 if (index == -1) return null; 270 final int rawValue = cursor.getInt(index); 271 return getType(kind, rawValue); 272 } 273 274 /** 275 * Find the {@link EditType} with the given {@link EditType#rawValue}. 276 */ 277 public static EditType getType(DataKind kind, int rawValue) { 278 for (EditType type : kind.typeList) { 279 if (type.rawValue == rawValue) { 280 return type; 281 } 282 } 283 return null; 284 } 285 286 /** 287 * Return the precedence for the the given {@link EditType#rawValue}, where 288 * lower numbers are higher precedence. 289 */ 290 public static int getTypePrecedence(DataKind kind, int rawValue) { 291 for (int i = 0; i < kind.typeList.size(); i++) { 292 final EditType type = kind.typeList.get(i); 293 if (type.rawValue == rawValue) { 294 return i; 295 } 296 } 297 return Integer.MAX_VALUE; 298 } 299 300 /** 301 * Find the best {@link EditType} for a potential insert. The "best" is the 302 * first primary type that doesn't already exist. When all valid types 303 * exist, we pick the last valid option. 304 */ 305 public static EditType getBestValidType(RawContactDelta state, DataKind kind, 306 boolean includeSecondary, int exactValue) { 307 // Shortcut when no types 308 if (kind == null || kind.typeColumn == null) return null; 309 310 // Find type counts and valid primary types, bail if none 311 final SparseIntArray typeCount = getTypeFrequencies(state, kind); 312 final ArrayList<EditType> validTypes = getValidTypes(state, kind, null, includeSecondary, 313 typeCount); 314 if (validTypes.size() == 0) return null; 315 316 // Keep track of the last valid type 317 final EditType lastType = validTypes.get(validTypes.size() - 1); 318 319 // Remove any types that already exist 320 Iterator<EditType> iterator = validTypes.iterator(); 321 while (iterator.hasNext()) { 322 final EditType type = iterator.next(); 323 final int count = typeCount.get(type.rawValue); 324 325 if (exactValue == type.rawValue) { 326 // Found exact value match 327 return type; 328 } 329 330 if (count > 0) { 331 // Type already appears, so don't consider 332 iterator.remove(); 333 } 334 } 335 336 // Use the best remaining, otherwise the last valid 337 if (validTypes.size() > 0) { 338 return validTypes.get(0); 339 } else { 340 return lastType; 341 } 342 } 343 344 /** 345 * Insert a new child of kind {@link DataKind} into the given 346 * {@link RawContactDelta}. Tries using the best {@link EditType} found using 347 * {@link #getBestValidType(RawContactDelta, DataKind, boolean, int)}. 348 */ 349 public static ValuesDelta insertChild(RawContactDelta state, DataKind kind) { 350 // Bail early if invalid kind 351 if (kind == null) return null; 352 // First try finding a valid primary 353 EditType bestType = getBestValidType(state, kind, false, Integer.MIN_VALUE); 354 if (bestType == null) { 355 // No valid primary found, so expand search to secondary 356 bestType = getBestValidType(state, kind, true, Integer.MIN_VALUE); 357 } 358 return insertChild(state, kind, bestType); 359 } 360 361 /** 362 * Insert a new child of kind {@link DataKind} into the given 363 * {@link RawContactDelta}, marked with the given {@link EditType}. 364 */ 365 public static ValuesDelta insertChild(RawContactDelta state, DataKind kind, EditType type) { 366 // Bail early if invalid kind 367 if (kind == null) return null; 368 final ContentValues after = new ContentValues(); 369 370 // Our parent CONTACT_ID is provided later 371 after.put(Data.MIMETYPE, kind.mimeType); 372 373 // Fill-in with any requested default values 374 if (kind.defaultValues != null) { 375 after.putAll(kind.defaultValues); 376 } 377 378 if (kind.typeColumn != null && type != null) { 379 // Set type, if provided 380 after.put(kind.typeColumn, type.rawValue); 381 } 382 383 final ValuesDelta child = ValuesDelta.fromAfter(after); 384 state.addEntry(child); 385 return child; 386 } 387 388 /** 389 * Processing to trim any empty {@link ValuesDelta} and {@link RawContactDelta} 390 * from the given {@link RawContactDeltaList}, assuming the given {@link AccountTypeManager} 391 * dictates the structure for various fields. This method ignores rows not 392 * described by the {@link AccountType}. 393 */ 394 public static void trimEmpty(RawContactDeltaList set, AccountTypeManager accountTypes) { 395 for (RawContactDelta state : set) { 396 ValuesDelta values = state.getValues(); 397 final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE); 398 final String dataSet = values.getAsString(RawContacts.DATA_SET); 399 final AccountType type = accountTypes.getAccountType(accountType, dataSet); 400 trimEmpty(state, type); 401 } 402 } 403 404 public static boolean hasChanges(RawContactDeltaList set, AccountTypeManager accountTypes) { 405 if (set.isMarkedForSplitting() || set.isMarkedForJoining()) { 406 return true; 407 } 408 409 for (RawContactDelta state : set) { 410 ValuesDelta values = state.getValues(); 411 final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE); 412 final String dataSet = values.getAsString(RawContacts.DATA_SET); 413 final AccountType type = accountTypes.getAccountType(accountType, dataSet); 414 if (hasChanges(state, type)) { 415 return true; 416 } 417 } 418 return false; 419 } 420 421 /** 422 * Processing to trim any empty {@link ValuesDelta} rows from the given 423 * {@link RawContactDelta}, assuming the given {@link AccountType} dictates 424 * the structure for various fields. This method ignores rows not described 425 * by the {@link AccountType}. 426 */ 427 public static void trimEmpty(RawContactDelta state, AccountType accountType) { 428 boolean hasValues = false; 429 430 // Walk through entries for each well-known kind 431 for (DataKind kind : accountType.getSortedDataKinds()) { 432 final String mimeType = kind.mimeType; 433 final ArrayList<ValuesDelta> entries = state.getMimeEntries(mimeType); 434 if (entries == null) continue; 435 436 for (ValuesDelta entry : entries) { 437 // Skip any values that haven't been touched 438 final boolean touched = entry.isInsert() || entry.isUpdate(); 439 if (!touched) { 440 hasValues = true; 441 continue; 442 } 443 444 // Test and remove this row if empty and it isn't a photo from google 445 final boolean isGoogleAccount = TextUtils.equals(GoogleAccountType.ACCOUNT_TYPE, 446 state.getValues().getAsString(RawContacts.ACCOUNT_TYPE)); 447 final boolean isPhoto = TextUtils.equals(Photo.CONTENT_ITEM_TYPE, kind.mimeType); 448 final boolean isGooglePhoto = isPhoto && isGoogleAccount; 449 450 if (RawContactModifier.isEmpty(entry, kind) && !isGooglePhoto) { 451 if (DEBUG) { 452 Log.v(TAG, "Trimming: " + entry.toString()); 453 } 454 entry.markDeleted(); 455 } else if (!entry.isFromTemplate()) { 456 hasValues = true; 457 } 458 } 459 } 460 if (!hasValues) { 461 // Trim overall entity if no children exist 462 state.markDeleted(); 463 } 464 } 465 466 private static boolean hasChanges(RawContactDelta state, AccountType accountType) { 467 for (DataKind kind : accountType.getSortedDataKinds()) { 468 final String mimeType = kind.mimeType; 469 final ArrayList<ValuesDelta> entries = state.getMimeEntries(mimeType); 470 if (entries == null) continue; 471 472 for (ValuesDelta entry : entries) { 473 // An empty Insert must be ignored, because it won't save anything (an example 474 // is an empty name that stays empty) 475 final boolean isRealInsert = entry.isInsert() && !isEmpty(entry, kind); 476 if (isRealInsert || entry.isUpdate() || entry.isDelete()) { 477 return true; 478 } 479 } 480 } 481 return false; 482 } 483 484 /** 485 * Test if the given {@link ValuesDelta} would be considered "empty" in 486 * terms of {@link DataKind#fieldList}. 487 */ 488 public static boolean isEmpty(ValuesDelta values, DataKind kind) { 489 if (Photo.CONTENT_ITEM_TYPE.equals(kind.mimeType)) { 490 return values.isInsert() && values.getAsByteArray(Photo.PHOTO) == null; 491 } 492 493 // No defined fields mean this row is always empty 494 if (kind.fieldList == null) return true; 495 496 for (EditField field : kind.fieldList) { 497 // If any field has values, we're not empty 498 final String value = values.getAsString(field.column); 499 if (ContactsUtils.isGraphic(value)) { 500 return false; 501 } 502 } 503 504 return true; 505 } 506 507 /** 508 * Compares corresponding fields in values1 and values2. Only the fields 509 * declared by the DataKind are taken into consideration. 510 */ 511 protected static boolean areEqual(ValuesDelta values1, ContentValues values2, DataKind kind) { 512 if (kind.fieldList == null) return false; 513 514 for (EditField field : kind.fieldList) { 515 final String value1 = values1.getAsString(field.column); 516 final String value2 = values2.getAsString(field.column); 517 if (!TextUtils.equals(value1, value2)) { 518 return false; 519 } 520 } 521 522 return true; 523 } 524 525 /** 526 * Parse the given {@link Bundle} into the given {@link RawContactDelta} state, 527 * assuming the extras defined through {@link Intents}. 528 */ 529 public static void parseExtras(Context context, AccountType accountType, RawContactDelta state, 530 Bundle extras) { 531 if (extras == null || extras.size() == 0) { 532 // Bail early if no useful data 533 return; 534 } 535 536 parseStructuredNameExtra(context, accountType, state, extras); 537 parseStructuredPostalExtra(accountType, state, extras); 538 539 { 540 // Phone 541 final DataKind kind = accountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 542 parseExtras(state, kind, extras, Insert.PHONE_TYPE, Insert.PHONE, Phone.NUMBER); 543 parseExtras(state, kind, extras, Insert.SECONDARY_PHONE_TYPE, Insert.SECONDARY_PHONE, 544 Phone.NUMBER); 545 parseExtras(state, kind, extras, Insert.TERTIARY_PHONE_TYPE, Insert.TERTIARY_PHONE, 546 Phone.NUMBER); 547 } 548 549 { 550 // Email 551 final DataKind kind = accountType.getKindForMimetype(Email.CONTENT_ITEM_TYPE); 552 parseExtras(state, kind, extras, Insert.EMAIL_TYPE, Insert.EMAIL, Email.DATA); 553 parseExtras(state, kind, extras, Insert.SECONDARY_EMAIL_TYPE, Insert.SECONDARY_EMAIL, 554 Email.DATA); 555 parseExtras(state, kind, extras, Insert.TERTIARY_EMAIL_TYPE, Insert.TERTIARY_EMAIL, 556 Email.DATA); 557 } 558 559 { 560 // Im 561 final DataKind kind = accountType.getKindForMimetype(Im.CONTENT_ITEM_TYPE); 562 fixupLegacyImType(extras); 563 parseExtras(state, kind, extras, Insert.IM_PROTOCOL, Insert.IM_HANDLE, Im.DATA); 564 } 565 566 // Organization 567 final boolean hasOrg = extras.containsKey(Insert.COMPANY) 568 || extras.containsKey(Insert.JOB_TITLE); 569 final DataKind kindOrg = accountType.getKindForMimetype(Organization.CONTENT_ITEM_TYPE); 570 if (hasOrg && RawContactModifier.canInsert(state, kindOrg)) { 571 final ValuesDelta child = RawContactModifier.insertChild(state, kindOrg); 572 573 final String company = extras.getString(Insert.COMPANY); 574 if (ContactsUtils.isGraphic(company)) { 575 child.put(Organization.COMPANY, company); 576 } 577 578 final String title = extras.getString(Insert.JOB_TITLE); 579 if (ContactsUtils.isGraphic(title)) { 580 child.put(Organization.TITLE, title); 581 } 582 } 583 584 // Notes 585 final boolean hasNotes = extras.containsKey(Insert.NOTES); 586 final DataKind kindNotes = accountType.getKindForMimetype(Note.CONTENT_ITEM_TYPE); 587 if (hasNotes && RawContactModifier.canInsert(state, kindNotes)) { 588 final ValuesDelta child = RawContactModifier.insertChild(state, kindNotes); 589 590 final String notes = extras.getString(Insert.NOTES); 591 if (ContactsUtils.isGraphic(notes)) { 592 child.put(Note.NOTE, notes); 593 } 594 } 595 596 // Arbitrary additional data 597 ArrayList<ContentValues> values = extras.getParcelableArrayList(Insert.DATA); 598 if (values != null) { 599 parseValues(state, accountType, values); 600 } 601 } 602 603 private static void parseStructuredNameExtra( 604 Context context, AccountType accountType, RawContactDelta state, Bundle extras) { 605 // StructuredName 606 RawContactModifier.ensureKindExists(state, accountType, StructuredName.CONTENT_ITEM_TYPE); 607 final ValuesDelta child = state.getPrimaryEntry(StructuredName.CONTENT_ITEM_TYPE); 608 609 final String name = extras.getString(Insert.NAME); 610 if (ContactsUtils.isGraphic(name)) { 611 final DataKind kind = accountType.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE); 612 boolean supportsDisplayName = false; 613 if (kind.fieldList != null) { 614 for (EditField field : kind.fieldList) { 615 if (StructuredName.DISPLAY_NAME.equals(field.column)) { 616 supportsDisplayName = true; 617 break; 618 } 619 } 620 } 621 622 if (supportsDisplayName) { 623 child.put(StructuredName.DISPLAY_NAME, name); 624 } else { 625 Uri uri = ContactsContract.AUTHORITY_URI.buildUpon() 626 .appendPath("complete_name") 627 .appendQueryParameter(StructuredName.DISPLAY_NAME, name) 628 .build(); 629 Cursor cursor = context.getContentResolver().query(uri, 630 new String[]{ 631 StructuredName.PREFIX, 632 StructuredName.GIVEN_NAME, 633 StructuredName.MIDDLE_NAME, 634 StructuredName.FAMILY_NAME, 635 StructuredName.SUFFIX, 636 }, null, null, null); 637 638 if (cursor != null) { 639 try { 640 if (cursor.moveToFirst()) { 641 child.put(StructuredName.PREFIX, cursor.getString(0)); 642 child.put(StructuredName.GIVEN_NAME, cursor.getString(1)); 643 child.put(StructuredName.MIDDLE_NAME, cursor.getString(2)); 644 child.put(StructuredName.FAMILY_NAME, cursor.getString(3)); 645 child.put(StructuredName.SUFFIX, cursor.getString(4)); 646 } 647 } finally { 648 cursor.close(); 649 } 650 } 651 } 652 } 653 654 final String phoneticName = extras.getString(Insert.PHONETIC_NAME); 655 if (ContactsUtils.isGraphic(phoneticName)) { 656 StructuredNameDataItem dataItem = NameConverter.parsePhoneticName(phoneticName, null); 657 child.put(StructuredName.PHONETIC_FAMILY_NAME, dataItem.getPhoneticFamilyName()); 658 child.put(StructuredName.PHONETIC_MIDDLE_NAME, dataItem.getPhoneticMiddleName()); 659 child.put(StructuredName.PHONETIC_GIVEN_NAME, dataItem.getPhoneticGivenName()); 660 } 661 } 662 663 private static void parseStructuredPostalExtra( 664 AccountType accountType, RawContactDelta state, Bundle extras) { 665 // StructuredPostal 666 final DataKind kind = accountType.getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE); 667 final ValuesDelta child = parseExtras(state, kind, extras, Insert.POSTAL_TYPE, 668 Insert.POSTAL, StructuredPostal.FORMATTED_ADDRESS); 669 String address = child == null ? null 670 : child.getAsString(StructuredPostal.FORMATTED_ADDRESS); 671 if (!TextUtils.isEmpty(address)) { 672 boolean supportsFormatted = false; 673 if (kind.fieldList != null) { 674 for (EditField field : kind.fieldList) { 675 if (StructuredPostal.FORMATTED_ADDRESS.equals(field.column)) { 676 supportsFormatted = true; 677 break; 678 } 679 } 680 } 681 682 if (!supportsFormatted) { 683 child.put(StructuredPostal.STREET, address); 684 child.putNull(StructuredPostal.FORMATTED_ADDRESS); 685 } 686 } 687 } 688 689 private static void parseValues( 690 RawContactDelta state, AccountType accountType, 691 ArrayList<ContentValues> dataValueList) { 692 for (ContentValues values : dataValueList) { 693 String mimeType = values.getAsString(Data.MIMETYPE); 694 if (TextUtils.isEmpty(mimeType)) { 695 Log.e(TAG, "Mimetype is required. Ignoring: " + values); 696 continue; 697 } 698 699 // Won't override the contact name 700 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) { 701 continue; 702 } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) { 703 values.remove(PhoneDataItem.KEY_FORMATTED_PHONE_NUMBER); 704 final Integer type = values.getAsInteger(Phone.TYPE); 705 // If the provided phone number provides a custom phone type but not a label, 706 // replace it with mobile (by default) to avoid the "Enter custom label" from 707 // popping up immediately upon entering the ContactEditorFragment 708 if (type != null && type == Phone.TYPE_CUSTOM && 709 TextUtils.isEmpty(values.getAsString(Phone.LABEL))) { 710 values.put(Phone.TYPE, Phone.TYPE_MOBILE); 711 } 712 } 713 714 DataKind kind = accountType.getKindForMimetype(mimeType); 715 if (kind == null) { 716 Log.e(TAG, "Mimetype not supported for account type " 717 + accountType.getAccountTypeAndDataSet() + ". Ignoring: " + values); 718 continue; 719 } 720 721 ValuesDelta entry = ValuesDelta.fromAfter(values); 722 if (isEmpty(entry, kind)) { 723 continue; 724 } 725 726 ArrayList<ValuesDelta> entries = state.getMimeEntries(mimeType); 727 728 if ((kind.typeOverallMax != 1) || GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) { 729 // Check for duplicates 730 boolean addEntry = true; 731 int count = 0; 732 if (entries != null && entries.size() > 0) { 733 for (ValuesDelta delta : entries) { 734 if (!delta.isDelete()) { 735 if (areEqual(delta, values, kind)) { 736 addEntry = false; 737 break; 738 } 739 count++; 740 } 741 } 742 } 743 744 if (kind.typeOverallMax != -1 && count >= kind.typeOverallMax) { 745 Log.e(TAG, "Mimetype allows at most " + kind.typeOverallMax 746 + " entries. Ignoring: " + values); 747 addEntry = false; 748 } 749 750 if (addEntry) { 751 addEntry = adjustType(entry, entries, kind); 752 } 753 754 if (addEntry) { 755 state.addEntry(entry); 756 } 757 } else { 758 // Non-list entries should not be overridden 759 boolean addEntry = true; 760 if (entries != null && entries.size() > 0) { 761 for (ValuesDelta delta : entries) { 762 if (!delta.isDelete() && !isEmpty(delta, kind)) { 763 addEntry = false; 764 break; 765 } 766 } 767 if (addEntry) { 768 for (ValuesDelta delta : entries) { 769 delta.markDeleted(); 770 } 771 } 772 } 773 774 if (addEntry) { 775 addEntry = adjustType(entry, entries, kind); 776 } 777 778 if (addEntry) { 779 state.addEntry(entry); 780 } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType)){ 781 // Note is most likely to contain large amounts of text 782 // that we don't want to drop on the ground. 783 for (ValuesDelta delta : entries) { 784 if (!isEmpty(delta, kind)) { 785 delta.put(Note.NOTE, delta.getAsString(Note.NOTE) + "\n" 786 + values.getAsString(Note.NOTE)); 787 break; 788 } 789 } 790 } else { 791 Log.e(TAG, "Will not override mimetype " + mimeType + ". Ignoring: " 792 + values); 793 } 794 } 795 } 796 } 797 798 /** 799 * Checks if the data kind allows addition of another entry (e.g. Exchange only 800 * supports two "work" phone numbers). If not, tries to switch to one of the 801 * unused types. If successful, returns true. 802 */ 803 private static boolean adjustType( 804 ValuesDelta entry, ArrayList<ValuesDelta> entries, DataKind kind) { 805 if (kind.typeColumn == null || kind.typeList == null || kind.typeList.size() == 0) { 806 return true; 807 } 808 809 Integer typeInteger = entry.getAsInteger(kind.typeColumn); 810 int type = typeInteger != null ? typeInteger : kind.typeList.get(0).rawValue; 811 812 if (isTypeAllowed(type, entries, kind)) { 813 entry.put(kind.typeColumn, type); 814 return true; 815 } 816 817 // Specified type is not allowed - choose the first available type that is allowed 818 int size = kind.typeList.size(); 819 for (int i = 0; i < size; i++) { 820 EditType editType = kind.typeList.get(i); 821 if (isTypeAllowed(editType.rawValue, entries, kind)) { 822 entry.put(kind.typeColumn, editType.rawValue); 823 return true; 824 } 825 } 826 827 return false; 828 } 829 830 /** 831 * Checks if a new entry of the specified type can be added to the raw 832 * contact. For example, Exchange only supports two "work" phone numbers, so 833 * addition of a third would not be allowed. 834 */ 835 private static boolean isTypeAllowed(int type, ArrayList<ValuesDelta> entries, DataKind kind) { 836 int max = 0; 837 int size = kind.typeList.size(); 838 for (int i = 0; i < size; i++) { 839 EditType editType = kind.typeList.get(i); 840 if (editType.rawValue == type) { 841 max = editType.specificMax; 842 break; 843 } 844 } 845 846 if (max == 0) { 847 // This type is not allowed at all 848 return false; 849 } 850 851 if (max == -1) { 852 // Unlimited instances of this type are allowed 853 return true; 854 } 855 856 return getEntryCountByType(entries, kind.typeColumn, type) < max; 857 } 858 859 /** 860 * Counts occurrences of the specified type in the supplied entry list. 861 * 862 * @return The count of occurrences of the type in the entry list. 0 if entries is 863 * {@literal null} 864 */ 865 private static int getEntryCountByType(ArrayList<ValuesDelta> entries, String typeColumn, 866 int type) { 867 int count = 0; 868 if (entries != null) { 869 for (ValuesDelta entry : entries) { 870 Integer typeInteger = entry.getAsInteger(typeColumn); 871 if (typeInteger != null && typeInteger == type) { 872 count++; 873 } 874 } 875 } 876 return count; 877 } 878 879 /** 880 * Attempt to parse legacy {@link Insert#IM_PROTOCOL} values, replacing them 881 * with updated values. 882 */ 883 @SuppressWarnings("deprecation") 884 private static void fixupLegacyImType(Bundle bundle) { 885 final String encodedString = bundle.getString(Insert.IM_PROTOCOL); 886 if (encodedString == null) return; 887 888 try { 889 final Object protocol = android.provider.Contacts.ContactMethods 890 .decodeImProtocol(encodedString); 891 if (protocol instanceof Integer) { 892 bundle.putInt(Insert.IM_PROTOCOL, (Integer)protocol); 893 } else { 894 bundle.putString(Insert.IM_PROTOCOL, (String)protocol); 895 } 896 } catch (IllegalArgumentException e) { 897 // Ignore exception when legacy parser fails 898 } 899 } 900 901 /** 902 * Parse a specific entry from the given {@link Bundle} and insert into the 903 * given {@link RawContactDelta}. Silently skips the insert when missing value 904 * or no valid {@link EditType} found. 905 * 906 * @param typeExtra {@link Bundle} key that holds the incoming 907 * {@link EditType#rawValue} value. 908 * @param valueExtra {@link Bundle} key that holds the incoming value. 909 * @param valueColumn Column to write value into {@link ValuesDelta}. 910 */ 911 public static ValuesDelta parseExtras(RawContactDelta state, DataKind kind, Bundle extras, 912 String typeExtra, String valueExtra, String valueColumn) { 913 final CharSequence value = extras.getCharSequence(valueExtra); 914 915 // Bail early if account type doesn't handle this MIME type 916 if (kind == null) return null; 917 918 // Bail when can't insert type, or value missing 919 final boolean canInsert = RawContactModifier.canInsert(state, kind); 920 final boolean validValue = (value != null && TextUtils.isGraphic(value)); 921 if (!validValue || !canInsert) return null; 922 923 // Find exact type when requested, otherwise best available type 924 final boolean hasType = extras.containsKey(typeExtra); 925 final int typeValue = extras.getInt(typeExtra, hasType ? BaseTypes.TYPE_CUSTOM 926 : Integer.MIN_VALUE); 927 final EditType editType = RawContactModifier.getBestValidType(state, kind, true, typeValue); 928 929 // Create data row and fill with value 930 final ValuesDelta child = RawContactModifier.insertChild(state, kind, editType); 931 child.put(valueColumn, value.toString()); 932 933 if (editType != null && editType.customColumn != null) { 934 // Write down label when custom type picked 935 final String customType = extras.getString(typeExtra); 936 child.put(editType.customColumn, customType); 937 } 938 939 return child; 940 } 941 942 /** 943 * Generic mime types with type support (e.g. TYPE_HOME). 944 * Here, "type support" means if the data kind has CommonColumns#TYPE or not. Data kinds which 945 * have their own migrate methods aren't listed here. 946 */ 947 private static final Set<String> sGenericMimeTypesWithTypeSupport = new HashSet<String>( 948 Arrays.asList(Phone.CONTENT_ITEM_TYPE, 949 Email.CONTENT_ITEM_TYPE, 950 Im.CONTENT_ITEM_TYPE, 951 Nickname.CONTENT_ITEM_TYPE, 952 Website.CONTENT_ITEM_TYPE, 953 Relation.CONTENT_ITEM_TYPE, 954 SipAddress.CONTENT_ITEM_TYPE)); 955 private static final Set<String> sGenericMimeTypesWithoutTypeSupport = new HashSet<String>( 956 Arrays.asList(Organization.CONTENT_ITEM_TYPE, 957 Note.CONTENT_ITEM_TYPE, 958 Photo.CONTENT_ITEM_TYPE, 959 GroupMembership.CONTENT_ITEM_TYPE)); 960 // CommonColumns.TYPE cannot be accessed as it is protected interface, so use 961 // Phone.TYPE instead. 962 private static final String COLUMN_FOR_TYPE = Phone.TYPE; 963 private static final String COLUMN_FOR_LABEL = Phone.LABEL; 964 private static final int TYPE_CUSTOM = Phone.TYPE_CUSTOM; 965 966 /** 967 * Migrates old RawContactDelta to newly created one with a new restriction supplied from 968 * newAccountType. 969 * 970 * This is only for account switch during account creation (which must be insert operation). 971 */ 972 public static void migrateStateForNewContact(Context context, 973 RawContactDelta oldState, RawContactDelta newState, 974 AccountType oldAccountType, AccountType newAccountType) { 975 if (newAccountType == oldAccountType) { 976 // Just copying all data in oldState isn't enough, but we can still rely on a lot of 977 // shortcuts. 978 for (DataKind kind : newAccountType.getSortedDataKinds()) { 979 final String mimeType = kind.mimeType; 980 // The fields with short/long form capability must be treated properly. 981 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) { 982 migrateStructuredName(context, oldState, newState, kind); 983 } else { 984 List<ValuesDelta> entryList = oldState.getMimeEntries(mimeType); 985 if (entryList != null && !entryList.isEmpty()) { 986 for (ValuesDelta entry : entryList) { 987 ContentValues values = entry.getAfter(); 988 if (values != null) { 989 newState.addEntry(ValuesDelta.fromAfter(values)); 990 } 991 } 992 } 993 } 994 } 995 } else { 996 // Migrate data supported by the new account type. 997 // All the other data inside oldState are silently dropped. 998 for (DataKind kind : newAccountType.getSortedDataKinds()) { 999 if (!kind.editable) continue; 1000 final String mimeType = kind.mimeType; 1001 if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType) 1002 || DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) { 1003 // Ignore pseudo data. 1004 continue; 1005 } else if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) { 1006 migrateStructuredName(context, oldState, newState, kind); 1007 } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType)) { 1008 migratePostal(oldState, newState, kind); 1009 } else if (Event.CONTENT_ITEM_TYPE.equals(mimeType)) { 1010 migrateEvent(oldState, newState, kind, null /* default Year */); 1011 } else if (sGenericMimeTypesWithoutTypeSupport.contains(mimeType)) { 1012 migrateGenericWithoutTypeColumn(oldState, newState, kind); 1013 } else if (sGenericMimeTypesWithTypeSupport.contains(mimeType)) { 1014 migrateGenericWithTypeColumn(oldState, newState, kind); 1015 } else { 1016 throw new IllegalStateException("Unexpected editable mime-type: " + mimeType); 1017 } 1018 } 1019 } 1020 } 1021 1022 /** 1023 * Checks {@link DataKind#isList} and {@link DataKind#typeOverallMax}, and restricts 1024 * the number of entries (ValuesDelta) inside newState. 1025 */ 1026 private static ArrayList<ValuesDelta> ensureEntryMaxSize(RawContactDelta newState, 1027 DataKind kind, ArrayList<ValuesDelta> mimeEntries) { 1028 if (mimeEntries == null) { 1029 return null; 1030 } 1031 1032 final int typeOverallMax = kind.typeOverallMax; 1033 if (typeOverallMax >= 0 && (mimeEntries.size() > typeOverallMax)) { 1034 ArrayList<ValuesDelta> newMimeEntries = new ArrayList<ValuesDelta>(typeOverallMax); 1035 for (int i = 0; i < typeOverallMax; i++) { 1036 newMimeEntries.add(mimeEntries.get(i)); 1037 } 1038 mimeEntries = newMimeEntries; 1039 } 1040 return mimeEntries; 1041 } 1042 1043 /** @hide Public only for testing. */ 1044 public static void migrateStructuredName( 1045 Context context, RawContactDelta oldState, RawContactDelta newState, 1046 DataKind newDataKind) { 1047 final ContentValues values = 1048 oldState.getPrimaryEntry(StructuredName.CONTENT_ITEM_TYPE).getAfter(); 1049 if (values == null) { 1050 return; 1051 } 1052 1053 boolean supportDisplayName = false; 1054 boolean supportPhoneticFullName = false; 1055 boolean supportPhoneticFamilyName = false; 1056 boolean supportPhoneticMiddleName = false; 1057 boolean supportPhoneticGivenName = false; 1058 for (EditField editField : newDataKind.fieldList) { 1059 if (StructuredName.DISPLAY_NAME.equals(editField.column)) { 1060 supportDisplayName = true; 1061 } 1062 if (DataKind.PSEUDO_COLUMN_PHONETIC_NAME.equals(editField.column)) { 1063 supportPhoneticFullName = true; 1064 } 1065 if (StructuredName.PHONETIC_FAMILY_NAME.equals(editField.column)) { 1066 supportPhoneticFamilyName = true; 1067 } 1068 if (StructuredName.PHONETIC_MIDDLE_NAME.equals(editField.column)) { 1069 supportPhoneticMiddleName = true; 1070 } 1071 if (StructuredName.PHONETIC_GIVEN_NAME.equals(editField.column)) { 1072 supportPhoneticGivenName = true; 1073 } 1074 } 1075 1076 // DISPLAY_NAME <-> PREFIX, GIVEN_NAME, MIDDLE_NAME, FAMILY_NAME, SUFFIX 1077 final String displayName = values.getAsString(StructuredName.DISPLAY_NAME); 1078 if (!TextUtils.isEmpty(displayName)) { 1079 if (!supportDisplayName) { 1080 // Old data has a display name, while the new account doesn't allow it. 1081 NameConverter.displayNameToStructuredName(context, displayName, values); 1082 1083 // We don't want to migrate unseen data which may confuse users after the creation. 1084 values.remove(StructuredName.DISPLAY_NAME); 1085 } 1086 } else { 1087 if (supportDisplayName) { 1088 // Old data does not have display name, while the new account requires it. 1089 values.put(StructuredName.DISPLAY_NAME, 1090 NameConverter.structuredNameToDisplayName(context, values)); 1091 for (String field : NameConverter.STRUCTURED_NAME_FIELDS) { 1092 values.remove(field); 1093 } 1094 } 1095 } 1096 1097 // Phonetic (full) name <-> PHONETIC_FAMILY_NAME, PHONETIC_MIDDLE_NAME, PHONETIC_GIVEN_NAME 1098 final String phoneticFullName = values.getAsString(DataKind.PSEUDO_COLUMN_PHONETIC_NAME); 1099 if (!TextUtils.isEmpty(phoneticFullName)) { 1100 if (!supportPhoneticFullName) { 1101 // Old data has a phonetic (full) name, while the new account doesn't allow it. 1102 final StructuredNameDataItem tmpItem = 1103 NameConverter.parsePhoneticName(phoneticFullName, null); 1104 values.remove(DataKind.PSEUDO_COLUMN_PHONETIC_NAME); 1105 if (supportPhoneticFamilyName) { 1106 values.put(StructuredName.PHONETIC_FAMILY_NAME, 1107 tmpItem.getPhoneticFamilyName()); 1108 } else { 1109 values.remove(StructuredName.PHONETIC_FAMILY_NAME); 1110 } 1111 if (supportPhoneticMiddleName) { 1112 values.put(StructuredName.PHONETIC_MIDDLE_NAME, 1113 tmpItem.getPhoneticMiddleName()); 1114 } else { 1115 values.remove(StructuredName.PHONETIC_MIDDLE_NAME); 1116 } 1117 if (supportPhoneticGivenName) { 1118 values.put(StructuredName.PHONETIC_GIVEN_NAME, 1119 tmpItem.getPhoneticGivenName()); 1120 } else { 1121 values.remove(StructuredName.PHONETIC_GIVEN_NAME); 1122 } 1123 } 1124 } else { 1125 if (supportPhoneticFullName) { 1126 // Old data does not have a phonetic (full) name, while the new account requires it. 1127 values.put(DataKind.PSEUDO_COLUMN_PHONETIC_NAME, 1128 NameConverter.buildPhoneticName( 1129 values.getAsString(StructuredName.PHONETIC_FAMILY_NAME), 1130 values.getAsString(StructuredName.PHONETIC_MIDDLE_NAME), 1131 values.getAsString(StructuredName.PHONETIC_GIVEN_NAME))); 1132 } 1133 if (!supportPhoneticFamilyName) { 1134 values.remove(StructuredName.PHONETIC_FAMILY_NAME); 1135 } 1136 if (!supportPhoneticMiddleName) { 1137 values.remove(StructuredName.PHONETIC_MIDDLE_NAME); 1138 } 1139 if (!supportPhoneticGivenName) { 1140 values.remove(StructuredName.PHONETIC_GIVEN_NAME); 1141 } 1142 } 1143 1144 newState.addEntry(ValuesDelta.fromAfter(values)); 1145 } 1146 1147 /** @hide Public only for testing. */ 1148 public static void migratePostal(RawContactDelta oldState, RawContactDelta newState, 1149 DataKind newDataKind) { 1150 final ArrayList<ValuesDelta> mimeEntries = ensureEntryMaxSize(newState, newDataKind, 1151 oldState.getMimeEntries(StructuredPostal.CONTENT_ITEM_TYPE)); 1152 if (mimeEntries == null || mimeEntries.isEmpty()) { 1153 return; 1154 } 1155 1156 boolean supportFormattedAddress = false; 1157 boolean supportStreet = false; 1158 final String firstColumn = newDataKind.fieldList.get(0).column; 1159 for (EditField editField : newDataKind.fieldList) { 1160 if (StructuredPostal.FORMATTED_ADDRESS.equals(editField.column)) { 1161 supportFormattedAddress = true; 1162 } 1163 if (StructuredPostal.STREET.equals(editField.column)) { 1164 supportStreet = true; 1165 } 1166 } 1167 1168 final Set<Integer> supportedTypes = new HashSet<Integer>(); 1169 if (newDataKind.typeList != null && !newDataKind.typeList.isEmpty()) { 1170 for (EditType editType : newDataKind.typeList) { 1171 supportedTypes.add(editType.rawValue); 1172 } 1173 } 1174 1175 for (ValuesDelta entry : mimeEntries) { 1176 final ContentValues values = entry.getAfter(); 1177 if (values == null) { 1178 continue; 1179 } 1180 final Integer oldType = values.getAsInteger(StructuredPostal.TYPE); 1181 if (!supportedTypes.contains(oldType)) { 1182 int defaultType; 1183 if (newDataKind.defaultValues != null) { 1184 defaultType = newDataKind.defaultValues.getAsInteger(StructuredPostal.TYPE); 1185 } else { 1186 defaultType = newDataKind.typeList.get(0).rawValue; 1187 } 1188 values.put(StructuredPostal.TYPE, defaultType); 1189 if (oldType != null && oldType == StructuredPostal.TYPE_CUSTOM) { 1190 values.remove(StructuredPostal.LABEL); 1191 } 1192 } 1193 1194 final String formattedAddress = values.getAsString(StructuredPostal.FORMATTED_ADDRESS); 1195 if (!TextUtils.isEmpty(formattedAddress)) { 1196 if (!supportFormattedAddress) { 1197 // Old data has a formatted address, while the new account doesn't allow it. 1198 values.remove(StructuredPostal.FORMATTED_ADDRESS); 1199 1200 // Unlike StructuredName we don't have logic to split it, so first 1201 // try to use street field and. If the new account doesn't have one, 1202 // then select first one anyway. 1203 if (supportStreet) { 1204 values.put(StructuredPostal.STREET, formattedAddress); 1205 } else { 1206 values.put(firstColumn, formattedAddress); 1207 } 1208 } 1209 } else { 1210 if (supportFormattedAddress) { 1211 // Old data does not have formatted address, while the new account requires it. 1212 // Unlike StructuredName we don't have logic to join multiple address values. 1213 // Use poor join heuristics for now. 1214 String[] structuredData; 1215 final boolean useJapaneseOrder = 1216 Locale.JAPANESE.getLanguage().equals(Locale.getDefault().getLanguage()); 1217 if (useJapaneseOrder) { 1218 structuredData = new String[] { 1219 values.getAsString(StructuredPostal.COUNTRY), 1220 values.getAsString(StructuredPostal.POSTCODE), 1221 values.getAsString(StructuredPostal.REGION), 1222 values.getAsString(StructuredPostal.CITY), 1223 values.getAsString(StructuredPostal.NEIGHBORHOOD), 1224 values.getAsString(StructuredPostal.STREET), 1225 values.getAsString(StructuredPostal.POBOX) }; 1226 } else { 1227 structuredData = new String[] { 1228 values.getAsString(StructuredPostal.POBOX), 1229 values.getAsString(StructuredPostal.STREET), 1230 values.getAsString(StructuredPostal.NEIGHBORHOOD), 1231 values.getAsString(StructuredPostal.CITY), 1232 values.getAsString(StructuredPostal.REGION), 1233 values.getAsString(StructuredPostal.POSTCODE), 1234 values.getAsString(StructuredPostal.COUNTRY) }; 1235 } 1236 final StringBuilder builder = new StringBuilder(); 1237 for (String elem : structuredData) { 1238 if (!TextUtils.isEmpty(elem)) { 1239 builder.append(elem + "\n"); 1240 } 1241 } 1242 values.put(StructuredPostal.FORMATTED_ADDRESS, builder.toString()); 1243 1244 values.remove(StructuredPostal.POBOX); 1245 values.remove(StructuredPostal.STREET); 1246 values.remove(StructuredPostal.NEIGHBORHOOD); 1247 values.remove(StructuredPostal.CITY); 1248 values.remove(StructuredPostal.REGION); 1249 values.remove(StructuredPostal.POSTCODE); 1250 values.remove(StructuredPostal.COUNTRY); 1251 } 1252 } 1253 1254 newState.addEntry(ValuesDelta.fromAfter(values)); 1255 } 1256 } 1257 1258 /** @hide Public only for testing. */ 1259 public static void migrateEvent(RawContactDelta oldState, RawContactDelta newState, 1260 DataKind newDataKind, Integer defaultYear) { 1261 final ArrayList<ValuesDelta> mimeEntries = ensureEntryMaxSize(newState, newDataKind, 1262 oldState.getMimeEntries(Event.CONTENT_ITEM_TYPE)); 1263 if (mimeEntries == null || mimeEntries.isEmpty()) { 1264 return; 1265 } 1266 1267 final SparseArray<EventEditType> allowedTypes = new SparseArray<EventEditType>(); 1268 for (EditType editType : newDataKind.typeList) { 1269 allowedTypes.put(editType.rawValue, (EventEditType) editType); 1270 } 1271 for (ValuesDelta entry : mimeEntries) { 1272 final ContentValues values = entry.getAfter(); 1273 if (values == null) { 1274 continue; 1275 } 1276 final String dateString = values.getAsString(Event.START_DATE); 1277 final Integer type = values.getAsInteger(Event.TYPE); 1278 if (type != null && (allowedTypes.indexOfKey(type) >= 0) 1279 && !TextUtils.isEmpty(dateString)) { 1280 EventEditType suitableType = allowedTypes.get(type); 1281 1282 final ParsePosition position = new ParsePosition(0); 1283 boolean yearOptional = false; 1284 Date date = CommonDateUtils.DATE_AND_TIME_FORMAT.parse(dateString, position); 1285 if (date == null) { 1286 yearOptional = true; 1287 date = CommonDateUtils.NO_YEAR_DATE_FORMAT.parse(dateString, position); 1288 } 1289 if (date != null) { 1290 if (yearOptional && !suitableType.isYearOptional()) { 1291 // The new EditType doesn't allow optional year. Supply default. 1292 final Calendar calendar = Calendar.getInstance(DateUtils.UTC_TIMEZONE, 1293 Locale.US); 1294 if (defaultYear == null) { 1295 defaultYear = calendar.get(Calendar.YEAR); 1296 } 1297 calendar.setTime(date); 1298 final int month = calendar.get(Calendar.MONTH); 1299 final int day = calendar.get(Calendar.DAY_OF_MONTH); 1300 // Exchange requires 8:00 for birthdays 1301 calendar.set(defaultYear, month, day, 1302 CommonDateUtils.DEFAULT_HOUR, 0, 0); 1303 values.put(Event.START_DATE, 1304 CommonDateUtils.FULL_DATE_FORMAT.format(calendar.getTime())); 1305 } 1306 } 1307 newState.addEntry(ValuesDelta.fromAfter(values)); 1308 } else { 1309 // Just drop it. 1310 } 1311 } 1312 } 1313 1314 /** @hide Public only for testing. */ 1315 public static void migrateGenericWithoutTypeColumn( 1316 RawContactDelta oldState, RawContactDelta newState, DataKind newDataKind) { 1317 final ArrayList<ValuesDelta> mimeEntries = ensureEntryMaxSize(newState, newDataKind, 1318 oldState.getMimeEntries(newDataKind.mimeType)); 1319 if (mimeEntries == null || mimeEntries.isEmpty()) { 1320 return; 1321 } 1322 1323 for (ValuesDelta entry : mimeEntries) { 1324 ContentValues values = entry.getAfter(); 1325 if (values != null) { 1326 newState.addEntry(ValuesDelta.fromAfter(values)); 1327 } 1328 } 1329 } 1330 1331 /** @hide Public only for testing. */ 1332 public static void migrateGenericWithTypeColumn( 1333 RawContactDelta oldState, RawContactDelta newState, DataKind newDataKind) { 1334 final ArrayList<ValuesDelta> mimeEntries = oldState.getMimeEntries(newDataKind.mimeType); 1335 if (mimeEntries == null || mimeEntries.isEmpty()) { 1336 return; 1337 } 1338 1339 // Note that type specified with the old account may be invalid with the new account, while 1340 // we want to preserve its data as much as possible. e.g. if a user typed a phone number 1341 // with a type which is valid with an old account but not with a new account, the user 1342 // probably wants to have the number with default type, rather than seeing complete data 1343 // loss. 1344 // 1345 // Specifically, this method works as follows: 1346 // 1. detect defaultType 1347 // 2. prepare constants & variables for iteration 1348 // 3. iterate over mimeEntries: 1349 // 3.1 stop iteration if total number of mimeEntries reached typeOverallMax specified in 1350 // DataKind 1351 // 3.2 replace unallowed types with defaultType 1352 // 3.3 check if the number of entries is below specificMax specified in AccountType 1353 1354 // Here, defaultType can be supplied in two ways 1355 // - via kind.defaultValues 1356 // - via kind.typeList.get(0).rawValue 1357 Integer defaultType = null; 1358 if (newDataKind.defaultValues != null) { 1359 defaultType = newDataKind.defaultValues.getAsInteger(COLUMN_FOR_TYPE); 1360 } 1361 final Set<Integer> allowedTypes = new HashSet<Integer>(); 1362 // key: type, value: the number of entries allowed for the type (specificMax) 1363 final SparseIntArray typeSpecificMaxMap = new SparseIntArray(); 1364 if (defaultType != null) { 1365 allowedTypes.add(defaultType); 1366 typeSpecificMaxMap.put(defaultType, -1); 1367 } 1368 // Note: typeList may be used in different purposes when defaultValues are specified. 1369 // Especially in IM, typeList contains available protocols (e.g. PROTOCOL_GOOGLE_TALK) 1370 // instead of "types" which we want to treate here (e.g. TYPE_HOME). So we don't add 1371 // anything other than defaultType into allowedTypes and typeSpecificMapMax. 1372 if (!Im.CONTENT_ITEM_TYPE.equals(newDataKind.mimeType) && 1373 newDataKind.typeList != null && !newDataKind.typeList.isEmpty()) { 1374 for (EditType editType : newDataKind.typeList) { 1375 allowedTypes.add(editType.rawValue); 1376 typeSpecificMaxMap.put(editType.rawValue, editType.specificMax); 1377 } 1378 if (defaultType == null) { 1379 defaultType = newDataKind.typeList.get(0).rawValue; 1380 } 1381 } 1382 1383 if (defaultType == null) { 1384 Log.w(TAG, "Default type isn't available for mimetype " + newDataKind.mimeType); 1385 } 1386 1387 final int typeOverallMax = newDataKind.typeOverallMax; 1388 1389 // key: type, value: the number of current entries. 1390 final SparseIntArray currentEntryCount = new SparseIntArray(); 1391 int totalCount = 0; 1392 1393 for (ValuesDelta entry : mimeEntries) { 1394 if (typeOverallMax != -1 && totalCount >= typeOverallMax) { 1395 break; 1396 } 1397 1398 final ContentValues values = entry.getAfter(); 1399 if (values == null) { 1400 continue; 1401 } 1402 1403 final Integer oldType = entry.getAsInteger(COLUMN_FOR_TYPE); 1404 final Integer typeForNewAccount; 1405 if (!allowedTypes.contains(oldType)) { 1406 // The new account doesn't support the type. 1407 if (defaultType != null) { 1408 typeForNewAccount = defaultType.intValue(); 1409 values.put(COLUMN_FOR_TYPE, defaultType.intValue()); 1410 if (oldType != null && oldType == TYPE_CUSTOM) { 1411 values.remove(COLUMN_FOR_LABEL); 1412 } 1413 } else { 1414 typeForNewAccount = null; 1415 values.remove(COLUMN_FOR_TYPE); 1416 } 1417 } else { 1418 typeForNewAccount = oldType; 1419 } 1420 if (typeForNewAccount != null) { 1421 final int specificMax = typeSpecificMaxMap.get(typeForNewAccount, 0); 1422 if (specificMax >= 0) { 1423 final int currentCount = currentEntryCount.get(typeForNewAccount, 0); 1424 if (currentCount >= specificMax) { 1425 continue; 1426 } 1427 currentEntryCount.put(typeForNewAccount, currentCount + 1); 1428 } 1429 } 1430 newState.addEntry(ValuesDelta.fromAfter(values)); 1431 totalCount++; 1432 } 1433 } 1434 } 1435