EntityModifier.java revision d2fdb9095e0009408b89e24048154ea7a8a14413
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
192ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkeyimport com.android.contacts.model.ContactsSource.DataKind;
206f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkeyimport com.android.contacts.model.ContactsSource.EditField;
212ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkeyimport com.android.contacts.model.ContactsSource.EditType;
22d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport com.android.contacts.model.EntityDelta.ValuesDelta;
23aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkeyimport com.google.android.collect.Lists;
242ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
252ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkeyimport android.content.ContentValues;
26802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkeyimport android.content.Context;
27802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkeyimport android.database.Cursor;
28d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.os.Bundle;
292ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkeyimport android.provider.ContactsContract.Data;
30d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.provider.ContactsContract.Intents;
317f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkeyimport android.provider.ContactsContract.RawContacts;
327f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkeyimport android.provider.ContactsContract.CommonDataKinds.BaseTypes;
33d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.provider.ContactsContract.CommonDataKinds.Email;
34d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.provider.ContactsContract.CommonDataKinds.Im;
35d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.provider.ContactsContract.CommonDataKinds.Phone;
36a6ff67e83082c7953d3b70181549ff4cabce2ebaFred Quintanaimport android.provider.ContactsContract.CommonDataKinds.Photo;
37d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.provider.ContactsContract.CommonDataKinds.StructuredName;
38d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
39d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.provider.ContactsContract.Intents.Insert;
40a6ff67e83082c7953d3b70181549ff4cabce2ebaFred Quintanaimport android.provider.ContactsContract;
41d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkeyimport android.text.TextUtils;
426f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkeyimport android.util.Log;
4307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkeyimport android.util.SparseIntArray;
442ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
4507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkeyimport java.util.ArrayList;
4607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkeyimport java.util.Iterator;
472ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkeyimport java.util.List;
482ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
492ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey/**
508d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey * Helper methods for modifying an {@link EntityDelta}, such as inserting
512ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * new rows, or enforcing {@link ContactsSource}.
522ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey */
532ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkeypublic class EntityModifier {
546f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey    private static final String TAG = "EntityModifier";
556f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey
562ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    /**
578d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     * For the given {@link EntityDelta}, determine if the given
582ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * {@link DataKind} could be inserted under specific
592ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * {@link ContactsSource}.
602ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     */
618d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    public static boolean canInsert(EntityDelta state, DataKind kind) {
6207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Insert possible when have valid types and under overall maximum
63ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey        final int visibleCount = state.getMimeEntriesCount(kind.mimeType, true);
6407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final boolean validTypes = hasValidTypes(state, kind);
6507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final boolean validOverall = (kind.typeOverallMax == -1)
66ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey                || (visibleCount < kind.typeOverallMax);
6707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        return (validTypes && validOverall);
6807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    }
6907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
708d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    public static boolean hasValidTypes(EntityDelta state, DataKind kind) {
71d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        if (EntityModifier.hasEditTypes(kind)) {
72d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            return (getValidTypes(state, kind).size() > 0);
73d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        } else {
74d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            return true;
75d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        }
762ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    }
772ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
782ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    /**
79aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey     * Ensure that at least one of the given {@link DataKind} exists in the
80aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey     * given {@link EntityDelta} state, and try creating one if none exist.
81aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey     */
82aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey    public static void ensureKindExists(EntityDelta state, ContactsSource source, String mimeType) {
83aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey        final DataKind kind = source.getKindForMimetype(mimeType);
84ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey        final boolean hasChild = state.getMimeEntriesCount(mimeType, true) > 0;
85aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey
86aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey        if (!hasChild && kind != null) {
87aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey            // Create child when none exists and valid kind
88aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey            insertChild(state, kind);
89aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey        }
90aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey    }
91aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey
92aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey    /**
938d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     * For the given {@link EntityDelta} and {@link DataKind}, return the
942ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * list possible {@link EditType} options available based on
952ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * {@link ContactsSource}.
962ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     */
978d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    public static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind) {
9807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        return getValidTypes(state, kind, null, true, null);
9907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    }
10007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
10107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    /**
1028d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     * For the given {@link EntityDelta} and {@link DataKind}, return the
10307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * list possible {@link EditType} options available based on
10407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * {@link ContactsSource}.
10507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *
10607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * @param forceInclude Always include this {@link EditType} in the returned
10707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *            list, even when an otherwise-invalid choice. This is useful
10807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *            when showing a dialog that includes the current type.
10907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     */
1108d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    public static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind,
1112ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey            EditType forceInclude) {
11207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        return getValidTypes(state, kind, forceInclude, true, null);
11307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    }
11407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
11507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    /**
1168d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     * For the given {@link EntityDelta} and {@link DataKind}, return the
11707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * list possible {@link EditType} options available based on
11807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * {@link ContactsSource}.
11907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *
12007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * @param forceInclude Always include this {@link EditType} in the returned
12107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *            list, even when an otherwise-invalid choice. This is useful
12207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *            when showing a dialog that includes the current type.
12307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * @param includeSecondary If true, include any valid types marked as
12407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *            {@link EditType#secondary}.
12507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * @param typeCount When provided, will be used for the frequency count of
12607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     *            each {@link EditType}, otherwise built using
1278d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     *            {@link #getTypeFrequencies(EntityDelta, DataKind)}.
12807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     */
1298d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    private static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind,
13007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            EditType forceInclude, boolean includeSecondary, SparseIntArray typeCount) {
131aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey        final ArrayList<EditType> validTypes = Lists.newArrayList();
13207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
13307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Bail early if no types provided
13407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (!hasEditTypes(kind)) return validTypes;
13507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
13607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (typeCount == null) {
13707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            // Build frequency counts if not provided
13807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            typeCount = getTypeFrequencies(state, kind);
13907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        }
14007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
14107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Build list of valid types
14207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final int overallCount = typeCount.get(FREQUENCY_TOTAL);
14307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        for (EditType type : kind.typeList) {
144d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final boolean validOverall = (kind.typeOverallMax == -1 ? true
145d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                    : overallCount < kind.typeOverallMax);
146d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final boolean validSpecific = (type.specificMax == -1 ? true : typeCount
147d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                    .get(type.rawValue) < type.specificMax);
14807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            final boolean validSecondary = (includeSecondary ? true : !type.secondary);
14907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            final boolean forcedInclude = type.equals(forceInclude);
150d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            if (forcedInclude || (validOverall && validSpecific && validSecondary)) {
15107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey                // Type is valid when no limit, under limit, or forced include
15207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey                validTypes.add(type);
15307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            }
15407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        }
15507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
15607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        return validTypes;
15707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    }
15807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
15907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    private static final int FREQUENCY_TOTAL = Integer.MIN_VALUE;
16007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
16107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    /**
16207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * Count up the frequency that each {@link EditType} appears in the given
1638d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     * {@link EntityDelta}. The returned {@link SparseIntArray} maps from
16407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * {@link EditType#rawValue} to counts, with the total overall count stored
16507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * as {@link #FREQUENCY_TOTAL}.
16607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     */
1678d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    private static SparseIntArray getTypeFrequencies(EntityDelta state, DataKind kind) {
16807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final SparseIntArray typeCount = new SparseIntArray();
16907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
17007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Find all entries for this kind, bailing early if none found
1718d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey        final List<ValuesDelta> mimeEntries = state.getMimeEntries(kind.mimeType);
17207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (mimeEntries == null) return typeCount;
17307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
17407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        int totalCount = 0;
1758d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey        for (ValuesDelta entry : mimeEntries) {
17607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            // Only count visible entries
17707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            if (!entry.isVisible()) continue;
17807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            totalCount++;
17907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
18007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            final EditType type = getCurrentType(entry, kind);
18107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            if (type != null) {
18207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey                final int count = typeCount.get(type.rawValue);
18307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey                typeCount.put(type.rawValue, count + 1);
18407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            }
18507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        }
18607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        typeCount.put(FREQUENCY_TOTAL, totalCount);
18707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        return typeCount;
1882ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    }
1892ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
1902ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    /**
1912ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * Check if the given {@link DataKind} has multiple types that should be
1922ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * displayed for users to pick.
1932ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     */
1942ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    public static boolean hasEditTypes(DataKind kind) {
1952ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey        return kind.typeList != null && kind.typeList.size() > 0;
1962ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    }
1972ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
1982ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    /**
1992ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * Find the {@link EditType} that describes the given
2008d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     * {@link ValuesDelta} row, assuming the given {@link DataKind} dictates
2012ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * the possible types.
2022ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     */
2038d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    public static EditType getCurrentType(ValuesDelta entry, DataKind kind) {
20407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final Long rawValue = entry.getAsLong(kind.typeColumn);
20507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (rawValue == null) return null;
20607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        return getType(kind, rawValue.intValue());
20707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    }
20807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
20907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    /**
21011d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar     * Find the {@link EditType} that describes the given {@link ContentValues} row,
21111d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar     * assuming the given {@link DataKind} dictates the possible types.
21211d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar     */
21311d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar    public static EditType getCurrentType(ContentValues entry, DataKind kind) {
21411d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar        if (kind.typeColumn == null) return null;
21549d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey        final Integer rawValue = entry.getAsInteger(kind.typeColumn);
21649d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey        if (rawValue == null) return null;
21711d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar        return getType(kind, rawValue);
21811d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar    }
21911d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar
22011d628c52c16a18c0caf40df0ce43396e7592ffcEvan Millar    /**
221802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey     * Find the {@link EditType} that describes the given {@link Cursor} row,
222802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey     * assuming the given {@link DataKind} dictates the possible types.
223802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey     */
224802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey    public static EditType getCurrentType(Cursor cursor, DataKind kind) {
225ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey        if (kind.typeColumn == null) return null;
226802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey        final int index = cursor.getColumnIndex(kind.typeColumn);
22749d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey        if (index == -1) return null;
228802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey        final int rawValue = cursor.getInt(index);
229802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey        return getType(kind, rawValue);
230802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey    }
231802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey
232802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey    /**
23307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * Find the {@link EditType} with the given {@link EditType#rawValue}.
23407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     */
23507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    public static EditType getType(DataKind kind, int rawValue) {
2362ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey        for (EditType type : kind.typeList) {
2372ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey            if (type.rawValue == rawValue) {
2382ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey                return type;
2392ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey            }
2402ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey        }
2412ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey        return null;
2422ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    }
2432ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
2442ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    /**
24549d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey     * Return the precedence for the the given {@link EditType#rawValue}, where
24649d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey     * lower numbers are higher precedence.
24749d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey     */
24849d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey    public static int getTypePrecedence(DataKind kind, int rawValue) {
24949d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey        for (int i = 0; i < kind.typeList.size(); i++) {
25049d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey            final EditType type = kind.typeList.get(i);
25149d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey            if (type.rawValue == rawValue) {
25249d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey                return i;
25349d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey            }
25449d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey        }
25549d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey        return Integer.MAX_VALUE;
25649d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey    }
25749d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey
25849d17b3e7692ae9442c342db236fa93d4a837c28Jeff Sharkey    /**
25907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * Find the best {@link EditType} for a potential insert. The "best" is the
26007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * first primary type that doesn't already exist. When all valid types
26107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * exist, we pick the last valid option.
26207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     */
2638d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey    public static EditType getBestValidType(EntityDelta state, DataKind kind,
264d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            boolean includeSecondary, int exactValue) {
26507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Shortcut when no types
26607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (kind.typeColumn == null) return null;
26707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
26807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Find type counts and valid primary types, bail if none
26907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final SparseIntArray typeCount = getTypeFrequencies(state, kind);
27007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final ArrayList<EditType> validTypes = getValidTypes(state, kind, null, includeSecondary,
27107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey                typeCount);
27207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (validTypes.size() == 0) return null;
27307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
27407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Keep track of the last valid type
27507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        final EditType lastType = validTypes.get(validTypes.size() - 1);
27607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
27707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Remove any types that already exist
27807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        Iterator<EditType> iterator = validTypes.iterator();
27907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        while (iterator.hasNext()) {
28007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            final EditType type = iterator.next();
28107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            final int count = typeCount.get(type.rawValue);
28207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
283e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            if (exactValue == type.rawValue) {
284d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                // Found exact value match
285d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                return type;
286d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            }
287d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
28807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            if (count > 0) {
28907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey                // Type already appears, so don't consider
29007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey                iterator.remove();
29107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            }
29207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        }
29307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
29407c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // Use the best remaining, otherwise the last valid
29507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (validTypes.size() > 0) {
29607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            return validTypes.get(0);
29707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        } else {
29807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            return lastType;
29907c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        }
30007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    }
30107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
30207c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    /**
3032ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     * Insert a new child of kind {@link DataKind} into the given
304d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * {@link EntityDelta}. Tries using the best {@link EditType} found using
305d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * {@link #getBestValidType(EntityDelta, DataKind, boolean, int)}.
3062ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     */
307d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey    public static ValuesDelta insertChild(EntityDelta state, DataKind kind) {
30807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        // First try finding a valid primary
309d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        EditType bestType = getBestValidType(state, kind, false, Integer.MIN_VALUE);
31007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (bestType == null) {
31107c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            // No valid primary found, so expand search to secondary
312d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            bestType = getBestValidType(state, kind, true, Integer.MIN_VALUE);
31307c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        }
314d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        return insertChild(state, kind, bestType);
31507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    }
31607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey
31707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey    /**
31807c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     * Insert a new child of kind {@link DataKind} into the given
3198d9767d79f9fe2a09ee6e981b1fede7e9863d62aJeff Sharkey     * {@link EntityDelta}, marked with the given {@link EditType}.
32007c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey     */
321d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey    public static ValuesDelta insertChild(EntityDelta state, DataKind kind, EditType type) {
322d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        // Bail early if invalid kind
323d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        if (kind == null) return null;
324d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
3252ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey        final ContentValues after = new ContentValues();
3262ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
327e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey        // Our parent CONTACT_ID is provided later
3282ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey        after.put(Data.MIMETYPE, kind.mimeType);
329e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey
330e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey        // Fill-in with any requested default values
331e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey        if (kind.defaultValues != null) {
332e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey            after.putAll(kind.defaultValues);
333e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey        }
334e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey
33507c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey        if (kind.typeColumn != null && type != null) {
33607c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            // Set type, if provided
33707c2e41b5ce7abeb9dd14e5d700a8fb928723330Jeff Sharkey            after.put(kind.typeColumn, type.rawValue);
338e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey        }
3392ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
340d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        final ValuesDelta child = ValuesDelta.fromAfter(after);
341d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        state.addEntry(child);
342d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        return child;
3432ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    }
3442ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
345d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey    /**
3467f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey     * Processing to trim any empty {@link ValuesDelta} and {@link EntityDelta}
3477f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey     * from the given {@link EntitySet}, assuming the given {@link Sources}
3487f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey     * dictates the structure for various fields. This method ignores rows not
3497f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey     * described by the {@link ContactsSource}.
3507f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey     */
3517f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey    public static void trimEmpty(EntitySet set, Sources sources) {
3527f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        for (EntityDelta state : set) {
3537f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey            final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
3547f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey            final ContactsSource source = sources.getInflatedSource(accountType,
3557f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey                    ContactsSource.LEVEL_MIMETYPES);
3567f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey            trimEmpty(state, source);
3577f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        }
3587f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey    }
3597f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey
3607f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey    /**
3616f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey     * Processing to trim any empty {@link ValuesDelta} rows from the given
3626f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey     * {@link EntityDelta}, assuming the given {@link ContactsSource} dictates
3636f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey     * the structure for various fields. This method ignores rows not described
3646f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey     * by the {@link ContactsSource}.
3656f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey     */
3667f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey    public static void trimEmpty(EntityDelta state, ContactsSource source) {
3677f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        boolean hasValues = false;
3687f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey
3696f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey        // Walk through entries for each well-known kind
3706f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey        for (DataKind kind : source.getSortedDataKinds()) {
3716f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            final String mimeType = kind.mimeType;
3726f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            final ArrayList<ValuesDelta> entries = state.getMimeEntries(mimeType);
3736f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            if (entries == null) continue;
3746f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey
3756f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            for (ValuesDelta entry : entries) {
3767f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey                // Skip any values that haven't been touched
3776f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey                final boolean touched = entry.isInsert() || entry.isUpdate();
3787f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey                if (!touched) {
3797f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey                    hasValues = true;
3807f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey                    continue;
3817f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey                }
3827f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey
383a6ff67e83082c7953d3b70181549ff4cabce2ebaFred Quintana                // Test and remove this row if empty and it isn't a photo from google
384a6ff67e83082c7953d3b70181549ff4cabce2ebaFred Quintana                final boolean isGoogleSource = TextUtils.equals(GoogleSource.ACCOUNT_TYPE,
385a6ff67e83082c7953d3b70181549ff4cabce2ebaFred Quintana                        state.getValues().getAsString(RawContacts.ACCOUNT_TYPE));
386a6ff67e83082c7953d3b70181549ff4cabce2ebaFred Quintana                final boolean isPhoto = TextUtils.equals(Photo.CONTENT_ITEM_TYPE, kind.mimeType);
387a6ff67e83082c7953d3b70181549ff4cabce2ebaFred Quintana                final boolean isGooglePhoto = isPhoto && isGoogleSource;
388a6ff67e83082c7953d3b70181549ff4cabce2ebaFred Quintana                if (EntityModifier.isEmpty(entry, kind) && !isGooglePhoto) {
3896f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey                    // TODO: remove this verbose logging
3906f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey                    Log.w(TAG, "Trimming: " + entry.toString());
3916f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey                    entry.markDeleted();
392d2fdb9095e0009408b89e24048154ea7a8a14413Megha Joshi                } else if (!entry.isFromTemplate()) {
3937f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey                    hasValues = true;
3946f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey                }
3956f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            }
3966f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey        }
3977f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey
3987f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        if (!hasValues) {
3997f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey            // Trim overall entity if no children exist
4007f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey            state.markDeleted();
4017f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        }
4026f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey    }
4036f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey
4046f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey    /**
4056f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey     * Test if the given {@link ValuesDelta} would be considered "empty" in
4066f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey     * terms of {@link DataKind#fieldList}.
4076f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey     */
4086f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey    public static boolean isEmpty(ValuesDelta values, DataKind kind) {
4096f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey        boolean hasValues = false;
4106f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey        for (EditField field : kind.fieldList) {
4116f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            // If any field has values, we're not empty
4126f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            final String value = values.getAsString(field.column);
4136f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            if (!TextUtils.isEmpty(value)) {
4146f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey                hasValues = true;
4156f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey            }
4166f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey        }
4176f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey
4186f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey        return !hasValues;
4196f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey    }
4206f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey
4216f8d46b5fb96c2bead1317aae93a73fc89b093abJeff Sharkey    /**
422d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * Parse the given {@link Bundle} into the given {@link EntityDelta} state,
423d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * assuming the extras defined through {@link Intents}.
424d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     */
425aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey    public static void parseExtras(Context context, ContactsSource source, EntityDelta state,
426aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey            Bundle extras) {
427d9798aefc844dd9ce29da085cb8ab7e769f63e75Jeff Sharkey        if (extras == null || extras.size() == 0) {
428d9798aefc844dd9ce29da085cb8ab7e769f63e75Jeff Sharkey            // Bail early if no useful data
429d9798aefc844dd9ce29da085cb8ab7e769f63e75Jeff Sharkey            return;
430d9798aefc844dd9ce29da085cb8ab7e769f63e75Jeff Sharkey        }
431d9798aefc844dd9ce29da085cb8ab7e769f63e75Jeff Sharkey
432d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        {
433d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            // StructuredName
434ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey            EntityModifier.ensureKindExists(state, source, StructuredName.CONTENT_ITEM_TYPE);
435ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey            final ValuesDelta child = state.getPrimaryEntry(StructuredName.CONTENT_ITEM_TYPE);
436ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey
437d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final String name = extras.getString(Insert.NAME);
438ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey            if (!TextUtils.isEmpty(name) && TextUtils.isGraphic(name)) {
439ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey                child.put(StructuredName.GIVEN_NAME, name);
440ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey            }
441ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey
442d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final String phoneticName = extras.getString(Insert.PHONETIC_NAME);
443ad40a919d318c7aa26f252d3f0fe541e2c44a211Jeff Sharkey            if (!TextUtils.isEmpty(phoneticName) && TextUtils.isGraphic(phoneticName)) {
444d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                child.put(StructuredName.PHONETIC_GIVEN_NAME, phoneticName);
445d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            }
446d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        }
447d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
448d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        {
449d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            // StructuredPostal
450d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final DataKind kind = source.getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE);
451d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            parseExtras(state, kind, extras, Insert.POSTAL_TYPE, Insert.POSTAL,
452a62e6db031a02bb04a57049839545e378d3b72c2Neel Parekh                    StructuredPostal.STREET);
453d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        }
454d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
455d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        {
456d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            // Phone
457d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final DataKind kind = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
458d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            parseExtras(state, kind, extras, Insert.PHONE_TYPE, Insert.PHONE, Phone.NUMBER);
459d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            parseExtras(state, kind, extras, Insert.SECONDARY_PHONE_TYPE, Insert.SECONDARY_PHONE,
460d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                    Phone.NUMBER);
461d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            parseExtras(state, kind, extras, Insert.TERTIARY_PHONE_TYPE, Insert.TERTIARY_PHONE,
462d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                    Phone.NUMBER);
463d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        }
464d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
465d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        {
466d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            // Email
467d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final DataKind kind = source.getKindForMimetype(Email.CONTENT_ITEM_TYPE);
468d9798aefc844dd9ce29da085cb8ab7e769f63e75Jeff Sharkey            parseExtras(state, kind, extras, Insert.EMAIL_TYPE, Insert.EMAIL, Email.DATA);
469d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            parseExtras(state, kind, extras, Insert.SECONDARY_EMAIL_TYPE, Insert.SECONDARY_EMAIL,
470d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                    Email.DATA);
471d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            parseExtras(state, kind, extras, Insert.TERTIARY_EMAIL_TYPE, Insert.TERTIARY_EMAIL,
472d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey                    Email.DATA);
473d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        }
474d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
475d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        {
476d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            // Im
477d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            final DataKind kind = source.getKindForMimetype(Im.CONTENT_ITEM_TYPE);
478e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            fixupLegacyImType(extras);
479d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            parseExtras(state, kind, extras, Insert.IM_PROTOCOL, Insert.IM_HANDLE, Im.DATA);
480d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        }
481d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey    }
482d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
483d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey    /**
484e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey     * Attempt to parse legacy {@link Insert#IM_PROTOCOL} values, replacing them
485e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey     * with updated values.
486e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey     */
487e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey    private static void fixupLegacyImType(Bundle bundle) {
488e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey        final String encodedString = bundle.getString(Insert.IM_PROTOCOL);
489e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey        if (encodedString == null) return;
490e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey
491e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey        try {
492e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            final Object protocol = android.provider.Contacts.ContactMethods
493e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey                    .decodeImProtocol(encodedString);
494e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            if (protocol instanceof Integer) {
495e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey                bundle.putInt(Insert.IM_PROTOCOL, (Integer)protocol);
496e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            } else {
497e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey                bundle.putString(Insert.IM_PROTOCOL, (String)protocol);
498e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            }
499e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey        } catch (IllegalArgumentException e) {
500e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            // Ignore exception when legacy parser fails
501e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey        }
502e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey    }
503e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey
504e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey    /**
505d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * Parse a specific entry from the given {@link Bundle} and insert into the
506d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * given {@link EntityDelta}. Silently skips the insert when missing value
507d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * or no valid {@link EditType} found.
508d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     *
509d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * @param typeExtra {@link Bundle} key that holds the incoming
510d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     *            {@link EditType#rawValue} value.
511d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * @param valueExtra {@link Bundle} key that holds the incoming value.
512d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     * @param valueColumn Column to write value into {@link ValuesDelta}.
513d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey     */
514d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey    public static void parseExtras(EntityDelta state, DataKind kind, Bundle extras,
515d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            String typeExtra, String valueExtra, String valueColumn) {
5167f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        final CharSequence value = extras.getCharSequence(valueExtra);
5177f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey
5187f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        // Bail early if source doesn't handle this type
5197f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        if (kind == null) return;
520d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
521d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        // Bail when can't insert type, or value missing
522d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        final boolean canInsert = EntityModifier.canInsert(state, kind);
523d9798aefc844dd9ce29da085cb8ab7e769f63e75Jeff Sharkey        final boolean validValue = (value != null && TextUtils.isGraphic(value));
524d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        if (!validValue || !canInsert) return;
525d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
5266164461a80cf46ecc4b9d4de21a8c2662d5ac220Jeff Sharkey        // Find exact type when requested, otherwise best available type
5276164461a80cf46ecc4b9d4de21a8c2662d5ac220Jeff Sharkey        final boolean hasType = extras.containsKey(typeExtra);
5286164461a80cf46ecc4b9d4de21a8c2662d5ac220Jeff Sharkey        final int typeValue = extras.getInt(typeExtra, hasType ? BaseTypes.TYPE_CUSTOM
5296164461a80cf46ecc4b9d4de21a8c2662d5ac220Jeff Sharkey                : Integer.MIN_VALUE);
530d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        final EditType editType = EntityModifier.getBestValidType(state, kind, true, typeValue);
531d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
532d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        // Create data row and fill with value
533d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        final ValuesDelta child = EntityModifier.insertChild(state, kind, editType);
5347f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey        child.put(valueColumn, value.toString());
535d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey
536d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        if (editType != null && editType.customColumn != null) {
537d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey            // Write down label when custom type picked
538e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            final String customType = extras.getString(typeExtra);
539e50d64d1e9eb321735c94a15b1a20a59a19cc421Jeff Sharkey            child.put(editType.customColumn, customType);
540d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey        }
541d046a039e41deab0635c3327cd40c3896e39acadJeff Sharkey    }
5422ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey}
543