EntityModifier.java revision 6a91292251913b3c0c8add733326cc70eae4cf51
12ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey/*
22ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * Copyright (C) 2009 The Android Open Source Project
32ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey *
42ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
52ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * you may not use this file except in compliance with the License.
62ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * You may obtain a copy of the License at
72ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey *
82ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
92ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey *
102ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * Unless required by applicable law or agreed to in writing, software
112ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
122ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * See the License for the specific language governing permissions and
142ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * limitations under the License.
152ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey */
162ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
172ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkeypackage com.android.contacts.model;
182ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
19e31dac84479a4c7b356edfc062a447cdfb5efc69Jeff Sharkeyimport com.android.contacts.ContactsUtils;
2035769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmannimport com.android.contacts.model.BaseAccountType.DataKind;
2135769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmannimport com.android.contacts.model.BaseAccountType.EditField;
2235769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmannimport com.android.contacts.model.BaseAccountType.EditType;
23d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport com.android.contacts.model.EntityDelta.ValuesDelta;
24aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkeyimport com.google.android.collect.Lists;
252ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
262ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkeyimport android.content.ContentValues;
27802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkeyimport android.content.Context;
28802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkeyimport android.database.Cursor;
29d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.os.Bundle;
302ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkeyimport android.provider.ContactsContract.Data;
31d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.provider.ContactsContract.Intents;
327f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkeyimport android.provider.ContactsContract.RawContacts;
337f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkeyimport android.provider.ContactsContract.CommonDataKinds.BaseTypes;
34d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.provider.ContactsContract.CommonDataKinds.Email;
35d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.provider.ContactsContract.CommonDataKinds.Im;
3616c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkeyimport android.provider.ContactsContract.CommonDataKinds.Note;
3716c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkeyimport android.provider.ContactsContract.CommonDataKinds.Organization;
38d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.provider.ContactsContract.CommonDataKinds.Phone;
39a6ff67e83082c7953d3b70181549ff4cabce2ebaFred Quintanaimport android.provider.ContactsContract.CommonDataKinds.Photo;
40d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.provider.ContactsContract.CommonDataKinds.StructuredName;
41d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
42d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.provider.ContactsContract.Intents.Insert;
43d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.text.TextUtils;
446f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkeyimport android.util.Log;
4507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkeyimport android.util.SparseIntArray;
462ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
4707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkeyimport java.util.ArrayList;
4807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkeyimport java.util.Iterator;
492ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkeyimport java.util.List;
502ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
512ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey/**
528d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey * Helper methods for modifying an {@link EntityDelta}, such as inserting
5335769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann * new rows, or enforcing {@link BaseAccountType}.
542ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey */
552ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkeypublic class EntityModifier {
566f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey    private static final String TAG = "EntityModifier";
576f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey
582ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    /**
598d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     * For the given {@link EntityDelta}, determine if the given
602ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * {@link DataKind} could be inserted under specific
6135769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann     * {@link BaseAccountType}.
622ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     */
638d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    public static boolean canInsert(EntityDelta state, DataKind kind) {
6407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Insert possible when have valid types and under overall maximum
65ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey        final int visibleCount = state.getMimeEntriesCount(kind.mimeType, true);
6607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final boolean validTypes = hasValidTypes(state, kind);
6707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final boolean validOverall = (kind.typeOverallMax == -1)
68ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey                || (visibleCount < kind.typeOverallMax);
6907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        return (validTypes && validOverall);
7007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    }
7107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
728d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    public static boolean hasValidTypes(EntityDelta state, DataKind kind) {
73d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        if (EntityModifier.hasEditTypes(kind)) {
74d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            return (getValidTypes(state, kind).size() > 0);
75d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        } else {
76d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            return true;
77d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        }
782ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    }
792ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
802ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    /**
81aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey     * Ensure that at least one of the given {@link DataKind} exists in the
82aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey     * given {@link EntityDelta} state, and try creating one if none exist.
83aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey     */
8435769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann    public static void ensureKindExists(EntityDelta state, BaseAccountType source, String mimeType) {
85aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey        final DataKind kind = source.getKindForMimetype(mimeType);
86ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey        final boolean hasChild = state.getMimeEntriesCount(mimeType, true) > 0;
87aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey
88aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey        if (!hasChild && kind != null) {
89aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey            // Create child when none exists and valid kind
90e86b928be36bb3e7136796ad519e736325d1d2b1Megha Joshi            final ValuesDelta child = insertChild(state, kind);
91e86b928be36bb3e7136796ad519e736325d1d2b1Megha Joshi            if (kind.mimeType.equals(Photo.CONTENT_ITEM_TYPE)) {
92e86b928be36bb3e7136796ad519e736325d1d2b1Megha Joshi                child.setFromTemplate(true);
93e86b928be36bb3e7136796ad519e736325d1d2b1Megha Joshi            }
94aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey        }
95aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey    }
96aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey
97aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey    /**
988d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     * For the given {@link EntityDelta} and {@link DataKind}, return the
992ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * list possible {@link EditType} options available based on
10035769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann     * {@link BaseAccountType}.
1012ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     */
1028d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    public static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind) {
10307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        return getValidTypes(state, kind, null, true, null);
10407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    }
10507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
10607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    /**
1078d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     * For the given {@link EntityDelta} and {@link DataKind}, return the
10807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * list possible {@link EditType} options available based on
10935769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann     * {@link BaseAccountType}.
11007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *
11107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * @param forceInclude Always include this {@link EditType} in the returned
11207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *            list, even when an otherwise-invalid choice. This is useful
11307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *            when showing a dialog that includes the current type.
11407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     */
1158d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    public static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind,
1162ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey            EditType forceInclude) {
11707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        return getValidTypes(state, kind, forceInclude, true, null);
11807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    }
11907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
12007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    /**
1218d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     * For the given {@link EntityDelta} and {@link DataKind}, return the
12207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * list possible {@link EditType} options available based on
12335769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann     * {@link BaseAccountType}.
12407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *
12507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * @param forceInclude Always include this {@link EditType} in the returned
12607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *            list, even when an otherwise-invalid choice. This is useful
12707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *            when showing a dialog that includes the current type.
12807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * @param includeSecondary If true, include any valid types marked as
12907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *            {@link EditType#secondary}.
13007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * @param typeCount When provided, will be used for the frequency count of
13107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *            each {@link EditType}, otherwise built using
1328d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     *            {@link #getTypeFrequencies(EntityDelta, DataKind)}.
13307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     */
1348d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    private static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind,
13507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            EditType forceInclude, boolean includeSecondary, SparseIntArray typeCount) {
136aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey        final ArrayList<EditType> validTypes = Lists.newArrayList();
13707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
13807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Bail early if no types provided
13907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (!hasEditTypes(kind)) return validTypes;
14007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
14107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (typeCount == null) {
14207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            // Build frequency counts if not provided
14307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            typeCount = getTypeFrequencies(state, kind);
14407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        }
14507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
14607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Build list of valid types
14707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final int overallCount = typeCount.get(FREQUENCY_TOTAL);
14807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        for (EditType type : kind.typeList) {
149d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final boolean validOverall = (kind.typeOverallMax == -1 ? true
150d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                    : overallCount < kind.typeOverallMax);
151d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final boolean validSpecific = (type.specificMax == -1 ? true : typeCount
152d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                    .get(type.rawValue) < type.specificMax);
15307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            final boolean validSecondary = (includeSecondary ? true : !type.secondary);
15407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            final boolean forcedInclude = type.equals(forceInclude);
155d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            if (forcedInclude || (validOverall && validSpecific && validSecondary)) {
15607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey                // Type is valid when no limit, under limit, or forced include
15707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey                validTypes.add(type);
15807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            }
15907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        }
16007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
16107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        return validTypes;
16207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    }
16307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
16407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    private static final int FREQUENCY_TOTAL = Integer.MIN_VALUE;
16507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
16607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    /**
16707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * Count up the frequency that each {@link EditType} appears in the given
1688d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     * {@link EntityDelta}. The returned {@link SparseIntArray} maps from
16907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * {@link EditType#rawValue} to counts, with the total overall count stored
17007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * as {@link #FREQUENCY_TOTAL}.
17107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     */
1728d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    private static SparseIntArray getTypeFrequencies(EntityDelta state, DataKind kind) {
17307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final SparseIntArray typeCount = new SparseIntArray();
17407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
17507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Find all entries for this kind, bailing early if none found
1768d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey        final List<ValuesDelta> mimeEntries = state.getMimeEntries(kind.mimeType);
17707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (mimeEntries == null) return typeCount;
17807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
17907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        int totalCount = 0;
1808d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey        for (ValuesDelta entry : mimeEntries) {
18107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            // Only count visible entries
18207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            if (!entry.isVisible()) continue;
18307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            totalCount++;
18407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
18507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            final EditType type = getCurrentType(entry, kind);
18607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            if (type != null) {
18707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey                final int count = typeCount.get(type.rawValue);
18807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey                typeCount.put(type.rawValue, count + 1);
18907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            }
19007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        }
19107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        typeCount.put(FREQUENCY_TOTAL, totalCount);
19207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        return typeCount;
1932ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    }
1942ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
1952ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    /**
1962ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * Check if the given {@link DataKind} has multiple types that should be
1972ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * displayed for users to pick.
1982ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     */
1992ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    public static boolean hasEditTypes(DataKind kind) {
2002ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey        return kind.typeList != null && kind.typeList.size() > 0;
2012ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    }
2022ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
2032ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    /**
2042ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * Find the {@link EditType} that describes the given
2058d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     * {@link ValuesDelta} row, assuming the given {@link DataKind} dictates
2062ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * the possible types.
2072ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     */
2088d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    public static EditType getCurrentType(ValuesDelta entry, DataKind kind) {
20907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final Long rawValue = entry.getAsLong(kind.typeColumn);
21007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (rawValue == null) return null;
21107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        return getType(kind, rawValue.intValue());
21207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    }
21307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
21407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    /**
21511d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar     * Find the {@link EditType} that describes the given {@link ContentValues} row,
21611d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar     * assuming the given {@link DataKind} dictates the possible types.
21711d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar     */
21811d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar    public static EditType getCurrentType(ContentValues entry, DataKind kind) {
21911d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar        if (kind.typeColumn == null) return null;
22049d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey        final Integer rawValue = entry.getAsInteger(kind.typeColumn);
22149d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey        if (rawValue == null) return null;
22211d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar        return getType(kind, rawValue);
22311d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar    }
22411d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar
22511d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar    /**
226802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey     * Find the {@link EditType} that describes the given {@link Cursor} row,
227802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey     * assuming the given {@link DataKind} dictates the possible types.
228802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey     */
229802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey    public static EditType getCurrentType(Cursor cursor, DataKind kind) {
230ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey        if (kind.typeColumn == null) return null;
231802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey        final int index = cursor.getColumnIndex(kind.typeColumn);
23249d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey        if (index == -1) return null;
233802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey        final int rawValue = cursor.getInt(index);
234802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey        return getType(kind, rawValue);
235802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey    }
236802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey
237802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey    /**
23807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * Find the {@link EditType} with the given {@link EditType#rawValue}.
23907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     */
24007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    public static EditType getType(DataKind kind, int rawValue) {
2412ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey        for (EditType type : kind.typeList) {
2422ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey            if (type.rawValue == rawValue) {
2432ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey                return type;
2442ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey            }
2452ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey        }
2462ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey        return null;
2472ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    }
2482ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
2492ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    /**
25049d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey     * Return the precedence for the the given {@link EditType#rawValue}, where
25149d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey     * lower numbers are higher precedence.
25249d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey     */
25349d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey    public static int getTypePrecedence(DataKind kind, int rawValue) {
25449d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey        for (int i = 0; i < kind.typeList.size(); i++) {
25549d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey            final EditType type = kind.typeList.get(i);
25649d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey            if (type.rawValue == rawValue) {
25749d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey                return i;
25849d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey            }
25949d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey        }
26049d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey        return Integer.MAX_VALUE;
26149d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey    }
26249d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey
26349d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey    /**
26407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * Find the best {@link EditType} for a potential insert. The "best" is the
26507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * first primary type that doesn't already exist. When all valid types
26607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * exist, we pick the last valid option.
26707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     */
2688d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    public static EditType getBestValidType(EntityDelta state, DataKind kind,
269d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            boolean includeSecondary, int exactValue) {
27007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Shortcut when no types
27107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (kind.typeColumn == null) return null;
27207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
27307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Find type counts and valid primary types, bail if none
27407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final SparseIntArray typeCount = getTypeFrequencies(state, kind);
27507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final ArrayList<EditType> validTypes = getValidTypes(state, kind, null, includeSecondary,
27607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey                typeCount);
27707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (validTypes.size() == 0) return null;
27807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
27907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Keep track of the last valid type
28007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final EditType lastType = validTypes.get(validTypes.size() - 1);
28107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
28207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Remove any types that already exist
28307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        Iterator<EditType> iterator = validTypes.iterator();
28407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        while (iterator.hasNext()) {
28507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            final EditType type = iterator.next();
28607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            final int count = typeCount.get(type.rawValue);
28707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
288e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            if (exactValue == type.rawValue) {
289d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                // Found exact value match
290d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                return type;
291d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            }
292d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
29307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            if (count > 0) {
29407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey                // Type already appears, so don't consider
29507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey                iterator.remove();
29607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            }
29707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        }
29807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
29907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Use the best remaining, otherwise the last valid
30007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (validTypes.size() > 0) {
30107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            return validTypes.get(0);
30207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        } else {
30307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            return lastType;
30407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        }
30507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    }
30607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
30707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    /**
3082ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * Insert a new child of kind {@link DataKind} into the given
309d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * {@link EntityDelta}. Tries using the best {@link EditType} found using
310d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * {@link #getBestValidType(EntityDelta, DataKind, boolean, int)}.
3112ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     */
312d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey    public static ValuesDelta insertChild(EntityDelta state, DataKind kind) {
31307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // First try finding a valid primary
314d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        EditType bestType = getBestValidType(state, kind, false, Integer.MIN_VALUE);
31507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (bestType == null) {
31607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            // No valid primary found, so expand search to secondary
317d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            bestType = getBestValidType(state, kind, true, Integer.MIN_VALUE);
31807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        }
319d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        return insertChild(state, kind, bestType);
32007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    }
32107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
32207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    /**
32307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * Insert a new child of kind {@link DataKind} into the given
3248d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     * {@link EntityDelta}, marked with the given {@link EditType}.
32507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     */
326d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey    public static ValuesDelta insertChild(EntityDelta state, DataKind kind, EditType type) {
327d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        // Bail early if invalid kind
328d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        if (kind == null) return null;
3292ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey        final ContentValues after = new ContentValues();
3302ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
331e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey        // Our parent CONTACT_ID is provided later
3322ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey        after.put(Data.MIMETYPE, kind.mimeType);
333e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey
334e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey        // Fill-in with any requested default values
335e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey        if (kind.defaultValues != null) {
336e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey            after.putAll(kind.defaultValues);
337e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey        }
338e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey
33907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (kind.typeColumn != null && type != null) {
34007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            // Set type, if provided
34107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            after.put(kind.typeColumn, type.rawValue);
342e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey        }
3432ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
344d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        final ValuesDelta child = ValuesDelta.fromAfter(after);
345e86b928be36bb3e7136796ad519e736325d1d2b1Megha Joshi	state.addEntry(child);
346d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        return child;
3472ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    }
3482ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
349d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey    /**
3507f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey     * Processing to trim any empty {@link ValuesDelta} and {@link EntityDelta}
35135769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann     * from the given {@link EntityDeltaList}, assuming the given {@link AccountTypes}
3527f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey     * dictates the structure for various fields. This method ignores rows not
35335769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann     * described by the {@link BaseAccountType}.
3547f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey     */
35535769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann    public static void trimEmpty(EntityDeltaList set, AccountTypes sources) {
3567f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        for (EntityDelta state : set) {
3577f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey            final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
35835769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann            final BaseAccountType source = sources.getInflatedSource(accountType,
35935769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann                    BaseAccountType.LEVEL_MIMETYPES);
3607f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey            trimEmpty(state, source);
3617f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        }
3627f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey    }
3637f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey
3647f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey    /**
3656f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey     * Processing to trim any empty {@link ValuesDelta} rows from the given
36635769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann     * {@link EntityDelta}, assuming the given {@link BaseAccountType} dictates
3676f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey     * the structure for various fields. This method ignores rows not described
36835769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann     * by the {@link BaseAccountType}.
3696f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey     */
37035769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann    public static void trimEmpty(EntityDelta state, BaseAccountType source) {
3717f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        boolean hasValues = false;
3727f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey
3736f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey        // Walk through entries for each well-known kind
3746f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey        for (DataKind kind : source.getSortedDataKinds()) {
3756f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            final String mimeType = kind.mimeType;
3766f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            final ArrayList<ValuesDelta> entries = state.getMimeEntries(mimeType);
3776f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            if (entries == null) continue;
3786f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey
3796f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            for (ValuesDelta entry : entries) {
3807f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey                // Skip any values that haven't been touched
3816f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey                final boolean touched = entry.isInsert() || entry.isUpdate();
3827f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey                if (!touched) {
3837f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey                    hasValues = true;
3847f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey                    continue;
3857f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey                }
3867f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey
387a6ff67e83082c7953d3b70181549ff4cabce2ebaFred Quintana                // Test and remove this row if empty and it isn't a photo from google
38835769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann                final boolean isGoogleSource = TextUtils.equals(GoogleAccountType.ACCOUNT_TYPE,
389a6ff67e83082c7953d3b70181549ff4cabce2ebaFred Quintana                        state.getValues().getAsString(RawContacts.ACCOUNT_TYPE));
390a6ff67e83082c7953d3b70181549ff4cabce2ebaFred Quintana                final boolean isPhoto = TextUtils.equals(Photo.CONTENT_ITEM_TYPE, kind.mimeType);
391a6ff67e83082c7953d3b70181549ff4cabce2ebaFred Quintana                final boolean isGooglePhoto = isPhoto && isGoogleSource;
392e86b928be36bb3e7136796ad519e736325d1d2b1Megha Joshi
393a6ff67e83082c7953d3b70181549ff4cabce2ebaFred Quintana                if (EntityModifier.isEmpty(entry, kind) && !isGooglePhoto) {
3946f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey                    // TODO: remove this verbose logging
3956f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey                    Log.w(TAG, "Trimming: " + entry.toString());
3966f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey                    entry.markDeleted();
397d2fdb9095e0009408b89e24048154ea7a8a14413Megha Joshi                } else if (!entry.isFromTemplate()) {
3987f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey                    hasValues = true;
3996f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey                }
4006f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            }
4016f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey        }
4027f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        if (!hasValues) {
4037f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey            // Trim overall entity if no children exist
4047f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey            state.markDeleted();
4057f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        }
4066f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey    }
4076f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey
4086f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey    /**
4096f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey     * Test if the given {@link ValuesDelta} would be considered "empty" in
4106f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey     * terms of {@link DataKind#fieldList}.
4116f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey     */
4126f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey    public static boolean isEmpty(ValuesDelta values, DataKind kind) {
413dbeb965d19a3c03e78cd54d9fbe7337b4fbb06f2Jeff Sharkey        // No defined fields mean this row is always empty
414dbeb965d19a3c03e78cd54d9fbe7337b4fbb06f2Jeff Sharkey        if (kind.fieldList == null) return true;
415dbeb965d19a3c03e78cd54d9fbe7337b4fbb06f2Jeff Sharkey
4166f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey        boolean hasValues = false;
4176f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey        for (EditField field : kind.fieldList) {
4186f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            // If any field has values, we're not empty
4196f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            final String value = values.getAsString(field.column);
420e31dac84479a4c7b356edfc062a447cdfb5efc69Jeff Sharkey            if (ContactsUtils.isGraphic(value)) {
4216f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey                hasValues = true;
4226f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            }
4236f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey        }
4246f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey
4256f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey        return !hasValues;
4266f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey    }
4276f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey
4286f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey    /**
429d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * Parse the given {@link Bundle} into the given {@link EntityDelta} state,
430d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * assuming the extras defined through {@link Intents}.
431d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     */
43235769b804fbfd5a1fc0b2c36cd0a786d662c4334Daniel Lehmann    public static void parseExtras(Context context, BaseAccountType source, EntityDelta state,
433aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey            Bundle extras) {
434d9798aefc844dd9ce29da085cb8ab7e769f63e75Jeff Sharkey        if (extras == null || extras.size() == 0) {
435d9798aefc844dd9ce29da085cb8ab7e769f63e75Jeff Sharkey            // Bail early if no useful data
436d9798aefc844dd9ce29da085cb8ab7e769f63e75Jeff Sharkey            return;
437d9798aefc844dd9ce29da085cb8ab7e769f63e75Jeff Sharkey        }
438d9798aefc844dd9ce29da085cb8ab7e769f63e75Jeff Sharkey
439d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        {
440d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            // StructuredName
441ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey            EntityModifier.ensureKindExists(state, source, StructuredName.CONTENT_ITEM_TYPE);
442ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey            final ValuesDelta child = state.getPrimaryEntry(StructuredName.CONTENT_ITEM_TYPE);
443ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey
444d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final String name = extras.getString(Insert.NAME);
445e31dac84479a4c7b356edfc062a447cdfb5efc69Jeff Sharkey            if (ContactsUtils.isGraphic(name)) {
446e17b1e52e6895f8177b4192f34126000fcb3f983Daniel Lehmann                child.put(StructuredName.DISPLAY_NAME, name);
447ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey            }
448ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey
449d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final String phoneticName = extras.getString(Insert.PHONETIC_NAME);
450e31dac84479a4c7b356edfc062a447cdfb5efc69Jeff Sharkey            if (ContactsUtils.isGraphic(phoneticName)) {
451d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                child.put(StructuredName.PHONETIC_GIVEN_NAME, phoneticName);
452d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            }
453d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        }
454d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
455d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        {
456d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            // StructuredPostal
457d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final DataKind kind = source.getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE);
458d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            parseExtras(state, kind, extras, Insert.POSTAL_TYPE, Insert.POSTAL,
459e17b1e52e6895f8177b4192f34126000fcb3f983Daniel Lehmann                    StructuredPostal.FORMATTED_ADDRESS);
460d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        }
461d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
462d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        {
463d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            // Phone
464d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final DataKind kind = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
465d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            parseExtras(state, kind, extras, Insert.PHONE_TYPE, Insert.PHONE, Phone.NUMBER);
466d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            parseExtras(state, kind, extras, Insert.SECONDARY_PHONE_TYPE, Insert.SECONDARY_PHONE,
467d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                    Phone.NUMBER);
468d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            parseExtras(state, kind, extras, Insert.TERTIARY_PHONE_TYPE, Insert.TERTIARY_PHONE,
469d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                    Phone.NUMBER);
470d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        }
471d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
472d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        {
473d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            // Email
474d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final DataKind kind = source.getKindForMimetype(Email.CONTENT_ITEM_TYPE);
475d9798aefc844dd9ce29da085cb8ab7e769f63e75Jeff Sharkey            parseExtras(state, kind, extras, Insert.EMAIL_TYPE, Insert.EMAIL, Email.DATA);
476d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            parseExtras(state, kind, extras, Insert.SECONDARY_EMAIL_TYPE, Insert.SECONDARY_EMAIL,
477d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                    Email.DATA);
478d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            parseExtras(state, kind, extras, Insert.TERTIARY_EMAIL_TYPE, Insert.TERTIARY_EMAIL,
479d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                    Email.DATA);
480d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        }
481d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
482d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        {
483d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            // Im
484d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final DataKind kind = source.getKindForMimetype(Im.CONTENT_ITEM_TYPE);
485e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            fixupLegacyImType(extras);
486d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            parseExtras(state, kind, extras, Insert.IM_PROTOCOL, Insert.IM_HANDLE, Im.DATA);
487d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        }
48816c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey
48916c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey        // Organization
49016c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey        final boolean hasOrg = extras.containsKey(Insert.COMPANY)
49116c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey                || extras.containsKey(Insert.JOB_TITLE);
49216c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey        final DataKind kindOrg = source.getKindForMimetype(Organization.CONTENT_ITEM_TYPE);
49316c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey        if (hasOrg && EntityModifier.canInsert(state, kindOrg)) {
49416c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey            final ValuesDelta child = EntityModifier.insertChild(state, kindOrg);
49516c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey
49616c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey            final String company = extras.getString(Insert.COMPANY);
49716c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey            if (ContactsUtils.isGraphic(company)) {
49816c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey                child.put(Organization.COMPANY, company);
49916c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey            }
50016c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey
50116c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey            final String title = extras.getString(Insert.JOB_TITLE);
50216c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey            if (ContactsUtils.isGraphic(title)) {
50316c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey                child.put(Organization.TITLE, title);
50416c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey            }
50516c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey        }
50616c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey
50716c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey        // Notes
50816c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey        final boolean hasNotes = extras.containsKey(Insert.NOTES);
50916c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey        final DataKind kindNotes = source.getKindForMimetype(Note.CONTENT_ITEM_TYPE);
51016c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey        if (hasNotes && EntityModifier.canInsert(state, kindNotes)) {
51116c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey            final ValuesDelta child = EntityModifier.insertChild(state, kindNotes);
51216c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey
51316c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey            final String notes = extras.getString(Insert.NOTES);
51416c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey            if (ContactsUtils.isGraphic(notes)) {
51516c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey                child.put(Note.NOTE, notes);
51616c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey            }
51716c364a557d619a0396ed35249478dcf2c1ffba5Jeff Sharkey        }
5186a91292251913b3c0c8add733326cc70eae4cf51Dmitri Plotnikov
5196a91292251913b3c0c8add733326cc70eae4cf51Dmitri Plotnikov        // Arbitrary additional data
5206a91292251913b3c0c8add733326cc70eae4cf51Dmitri Plotnikov        {
5216a91292251913b3c0c8add733326cc70eae4cf51Dmitri Plotnikov//            ArrayList<ContentValues> values = extras.getParcelableArrayList(Insert.DATA);
5226a91292251913b3c0c8add733326cc70eae4cf51Dmitri Plotnikov//            if (values != null) {
5236a91292251913b3c0c8add733326cc70eae4cf51Dmitri Plotnikov//                parseValues(state, values);
5246a91292251913b3c0c8add733326cc70eae4cf51Dmitri Plotnikov//            }
5256a91292251913b3c0c8add733326cc70eae4cf51Dmitri Plotnikov        }
5266a91292251913b3c0c8add733326cc70eae4cf51Dmitri Plotnikov    }
5276a91292251913b3c0c8add733326cc70eae4cf51Dmitri Plotnikov
5286a91292251913b3c0c8add733326cc70eae4cf51Dmitri Plotnikov    private static void parseValues(EntityDelta state, ArrayList<ContentValues> values) {
5296a91292251913b3c0c8add733326cc70eae4cf51Dmitri Plotnikov        for (ContentValues contentValues : values) {
5306a91292251913b3c0c8add733326cc70eae4cf51Dmitri Plotnikov            String mimeType = contentValues.getAsString(Data.MIMETYPE);
5316a91292251913b3c0c8add733326cc70eae4cf51Dmitri Plotnikov        }
532d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey    }
533d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
534d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey    /**
535e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey     * Attempt to parse legacy {@link Insert#IM_PROTOCOL} values, replacing them
536e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey     * with updated values.
537e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey     */
538e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey    private static void fixupLegacyImType(Bundle bundle) {
539e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey        final String encodedString = bundle.getString(Insert.IM_PROTOCOL);
540e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey        if (encodedString == null) return;
541e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey
542e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey        try {
543e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            final Object protocol = android.provider.Contacts.ContactMethods
544e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey                    .decodeImProtocol(encodedString);
545e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            if (protocol instanceof Integer) {
546e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey                bundle.putInt(Insert.IM_PROTOCOL, (Integer)protocol);
547e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            } else {
548e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey                bundle.putString(Insert.IM_PROTOCOL, (String)protocol);
549e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            }
550e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey        } catch (IllegalArgumentException e) {
551e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            // Ignore exception when legacy parser fails
552e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey        }
553e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey    }
554e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey
555e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey    /**
556d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * Parse a specific entry from the given {@link Bundle} and insert into the
557d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * given {@link EntityDelta}. Silently skips the insert when missing value
558d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * or no valid {@link EditType} found.
559d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     *
560d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * @param typeExtra {@link Bundle} key that holds the incoming
561d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     *            {@link EditType#rawValue} value.
562d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * @param valueExtra {@link Bundle} key that holds the incoming value.
563d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * @param valueColumn Column to write value into {@link ValuesDelta}.
564d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     */
565d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey    public static void parseExtras(EntityDelta state, DataKind kind, Bundle extras,
566d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            String typeExtra, String valueExtra, String valueColumn) {
5677f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        final CharSequence value = extras.getCharSequence(valueExtra);
5687f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey
5697f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        // Bail early if source doesn't handle this type
5707f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        if (kind == null) return;
571d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
572d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        // Bail when can't insert type, or value missing
573d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        final boolean canInsert = EntityModifier.canInsert(state, kind);
574d9798aefc844dd9ce29da085cb8ab7e769f63e75Jeff Sharkey        final boolean validValue = (value != null && TextUtils.isGraphic(value));
575d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        if (!validValue || !canInsert) return;
576d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
5776164461a80cf46ecc4b9d4de21a8c2662d5ac220Jeff Sharkey        // Find exact type when requested, otherwise best available type
5786164461a80cf46ecc4b9d4de21a8c2662d5ac220Jeff Sharkey        final boolean hasType = extras.containsKey(typeExtra);
5796164461a80cf46ecc4b9d4de21a8c2662d5ac220Jeff Sharkey        final int typeValue = extras.getInt(typeExtra, hasType ? BaseTypes.TYPE_CUSTOM
5806164461a80cf46ecc4b9d4de21a8c2662d5ac220Jeff Sharkey                : Integer.MIN_VALUE);
581d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        final EditType editType = EntityModifier.getBestValidType(state, kind, true, typeValue);
582d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
583d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        // Create data row and fill with value
584d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        final ValuesDelta child = EntityModifier.insertChild(state, kind, editType);
5857f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        child.put(valueColumn, value.toString());
586d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
587d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        if (editType != null && editType.customColumn != null) {
588d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            // Write down label when custom type picked
589e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            final String customType = extras.getString(typeExtra);
590e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            child.put(editType.customColumn, customType);
591d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        }
592d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey    }
5932ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey}
594