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