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