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