public class

LegacyApiSupport

extends Object
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */
package com.android.providers.contacts;

import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.ExtensionsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.PhoneColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;

import android.accounts.Account;
import android.app.SearchManager;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDoneException;
import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.Contacts.ContactMethods;
import android.provider.Contacts.Extensions;
import android.provider.Contacts.People;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.Settings;
import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.util.Log;

import java.util.HashMap;
import java.util.Locale;

@SuppressWarnings("deprecation")
public class LegacyApiSupport {

    private static final String TAG = "ContactsProviderV1";

    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    private static final int PEOPLE = 1;
    private static final int PEOPLE_ID = 2;
    private static final int PEOPLE_UPDATE_CONTACT_TIME = 3;
    private static final int ORGANIZATIONS = 4;
    private static final int ORGANIZATIONS_ID = 5;
    private static final int PEOPLE_CONTACTMETHODS = 6;
    private static final int PEOPLE_CONTACTMETHODS_ID = 7;
    private static final int CONTACTMETHODS = 8;
    private static final int CONTACTMETHODS_ID = 9;
    private static final int PEOPLE_PHONES = 10;
    private static final int PEOPLE_PHONES_ID = 11;
    private static final int PHONES = 12;
    private static final int PHONES_ID = 13;
    private static final int EXTENSIONS = 14;
    private static final int EXTENSIONS_ID = 15;
    private static final int PEOPLE_EXTENSIONS = 16;
    private static final int PEOPLE_EXTENSIONS_ID = 17;
    private static final int GROUPS = 18;
    private static final int GROUPS_ID = 19;
    private static final int GROUPMEMBERSHIP = 20;
    private static final int GROUPMEMBERSHIP_ID = 21;
    private static final int PEOPLE_GROUPMEMBERSHIP = 22;
    private static final int PEOPLE_GROUPMEMBERSHIP_ID = 23;
    private static final int PEOPLE_PHOTO = 24;
    private static final int PHOTOS = 25;
    private static final int PHOTOS_ID = 26;
    private static final int PEOPLE_FILTER = 29;
    private static final int DELETED_PEOPLE = 30;
    private static final int DELETED_GROUPS = 31;
    private static final int SEARCH_SUGGESTIONS = 32;
    private static final int SEARCH_SHORTCUT = 33;
    private static final int PHONES_FILTER = 34;
    private static final int LIVE_FOLDERS_PEOPLE = 35;
    private static final int LIVE_FOLDERS_PEOPLE_GROUP_NAME = 36;
    private static final int LIVE_FOLDERS_PEOPLE_WITH_PHONES = 37;
    private static final int LIVE_FOLDERS_PEOPLE_FAVORITES = 38;
    private static final int CONTACTMETHODS_EMAIL = 39;
    private static final int GROUP_NAME_MEMBERS = 40;
    private static final int GROUP_SYSTEM_ID_MEMBERS = 41;
    private static final int PEOPLE_ORGANIZATIONS = 42;
    private static final int PEOPLE_ORGANIZATIONS_ID = 43;
    private static final int SETTINGS = 44;

    private static final String PEOPLE_JOINS =
            " LEFT OUTER JOIN data name ON (raw_contacts._id = name.raw_contact_id"
            + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = name.mimetype_id)"
                    + "='" + StructuredName.CONTENT_ITEM_TYPE + "')"
            + " LEFT OUTER JOIN data organization ON (raw_contacts._id = organization.raw_contact_id"
            + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = organization.mimetype_id)"
                    + "='" + Organization.CONTENT_ITEM_TYPE + "' AND organization.is_primary)"
            + " LEFT OUTER JOIN data email ON (raw_contacts._id = email.raw_contact_id"
            + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = email.mimetype_id)"
                    + "='" + Email.CONTENT_ITEM_TYPE + "' AND email.is_primary)"
            + " LEFT OUTER JOIN data note ON (raw_contacts._id = note.raw_contact_id"
            + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = note.mimetype_id)"
                    + "='" + Note.CONTENT_ITEM_TYPE + "')"
            + " LEFT OUTER JOIN data phone ON (raw_contacts._id = phone.raw_contact_id"
            + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = phone.mimetype_id)"
                    + "='" + Phone.CONTENT_ITEM_TYPE + "' AND phone.is_primary)";

    public static final String DATA_JOINS =
            " JOIN mimetypes ON (mimetypes._id = data.mimetype_id)"
            + " JOIN raw_contacts ON (raw_contacts._id = data.raw_contact_id)"
            + PEOPLE_JOINS;

    public static final String PRESENCE_JOINS =
            " LEFT OUTER JOIN " + Tables.PRESENCE +
            " ON (" + Tables.PRESENCE + "." + StatusUpdates.DATA_ID + "=" +
                    "(SELECT MAX(" + StatusUpdates.DATA_ID + ")" +
                    " FROM " + Tables.PRESENCE +
                    " WHERE people._id = " + PresenceColumns.RAW_CONTACT_ID + ")" +
            " )";

    private static final String PHONETIC_NAME_SQL = "trim(trim("
            + "ifnull(name." + StructuredName.PHONETIC_GIVEN_NAME + ",' ')||' '||"
            + "ifnull(name." + StructuredName.PHONETIC_MIDDLE_NAME + ",' '))||' '||"
            + "ifnull(name." + StructuredName.PHONETIC_FAMILY_NAME + ",' ')) ";

    private static final String CONTACT_METHOD_KIND_SQL =
            "CAST ((CASE WHEN mimetype='" + Email.CONTENT_ITEM_TYPE + "'"
                + " THEN " + android.provider.Contacts.KIND_EMAIL
                + " ELSE"
                    + " (CASE WHEN mimetype='" + Im.CONTENT_ITEM_TYPE +"'"
                        + " THEN " + android.provider.Contacts.KIND_IM
                        + " ELSE"
                        + " (CASE WHEN mimetype='" + StructuredPostal.CONTENT_ITEM_TYPE + "'"
                            + " THEN "  + android.provider.Contacts.KIND_POSTAL
                            + " ELSE"
                                + " NULL"
                            + " END)"
                        + " END)"
                + " END) AS INTEGER)";

    private static final String IM_PROTOCOL_SQL =
            "(CASE WHEN " + StatusUpdates.PROTOCOL + "=" + Im.PROTOCOL_CUSTOM
                + " THEN 'custom:'||" + StatusUpdates.CUSTOM_PROTOCOL
                + " ELSE 'pre:'||" + StatusUpdates.PROTOCOL
                + " END)";

    private static String CONTACT_METHOD_DATA_SQL =
            "(CASE WHEN " + Data.MIMETYPE + "='" + Im.CONTENT_ITEM_TYPE + "'"
                + " THEN (CASE WHEN " + Tables.DATA + "." + Im.PROTOCOL + "=" + Im.PROTOCOL_CUSTOM
                    + " THEN 'custom:'||" + Tables.DATA + "." + Im.CUSTOM_PROTOCOL
                    + " ELSE 'pre:'||" + Tables.DATA + "." + Im.PROTOCOL
                    + " END)"
                + " ELSE " + Tables.DATA + "." + Email.DATA
                + " END)";

    private static final Uri LIVE_FOLDERS_CONTACTS_URI = Uri.withAppendedPath(
            ContactsContract.AUTHORITY_URI, "live_folders/contacts");

    private static final Uri LIVE_FOLDERS_CONTACTS_WITH_PHONES_URI = Uri.withAppendedPath(
            ContactsContract.AUTHORITY_URI, "live_folders/contacts_with_phones");

    private static final Uri LIVE_FOLDERS_CONTACTS_FAVORITES_URI = Uri.withAppendedPath(
            ContactsContract.AUTHORITY_URI, "live_folders/favorites");

    private static final String CONTACTS_UPDATE_LASTTIMECONTACTED =
            "UPDATE " + Tables.CONTACTS +
            " SET " + Contacts.LAST_TIME_CONTACTED + "=? " +
            "WHERE " + Contacts._ID + "=?";
    private static final String RAWCONTACTS_UPDATE_LASTTIMECONTACTED =
            "UPDATE " + Tables.RAW_CONTACTS + " SET "
            + RawContacts.LAST_TIME_CONTACTED + "=? WHERE "
            + RawContacts._ID + "=?";

    private String[] mSelectionArgs1 = new String[1];
    private String[] mSelectionArgs2 = new String[2];

    public interface LegacyTables {
        public static final String PEOPLE = "view_v1_people";
        public static final String PEOPLE_JOIN_PRESENCE = "view_v1_people people " + PRESENCE_JOINS;
        public static final String GROUPS = "view_v1_groups";
        public static final String ORGANIZATIONS = "view_v1_organizations";
        public static final String CONTACT_METHODS = "view_v1_contact_methods";
        public static final String PHONES = "view_v1_phones";
        public static final String EXTENSIONS = "view_v1_extensions";
        public static final String GROUP_MEMBERSHIP = "view_v1_group_membership";
        public static final String PHOTOS = "view_v1_photos";
        public static final String SETTINGS = "v1_settings";
    }

    private static final String[] ORGANIZATION_MIME_TYPES = new String[] {
        Organization.CONTENT_ITEM_TYPE
    };

    private static final String[] CONTACT_METHOD_MIME_TYPES = new String[] {
        Email.CONTENT_ITEM_TYPE,
        Im.CONTENT_ITEM_TYPE,
        StructuredPostal.CONTENT_ITEM_TYPE,
    };

    private static final String[] PHONE_MIME_TYPES = new String[] {
        Phone.CONTENT_ITEM_TYPE
    };

    private static final String[] PHOTO_MIME_TYPES = new String[] {
        Photo.CONTENT_ITEM_TYPE
    };

    private static final String[] GROUP_MEMBERSHIP_MIME_TYPES = new String[] {
        GroupMembership.CONTENT_ITEM_TYPE
    };

    private static final String[] EXTENSION_MIME_TYPES = new String[] {
        android.provider.Contacts.Extensions.CONTENT_ITEM_TYPE
    };

    private interface IdQuery {
        String[] COLUMNS = { BaseColumns._ID };

        int _ID = 0;
    }

    /**
     * A custom data row that is used to store legacy photo data fields no
     * longer directly supported by the API.
     */
    private interface LegacyPhotoData {
        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/photo_v1_extras";

        public static final String PHOTO_DATA_ID = Data.DATA1;
        public static final String LOCAL_VERSION = Data.DATA2;
        public static final String DOWNLOAD_REQUIRED = Data.DATA3;
        public static final String EXISTS_ON_SERVER = Data.DATA4;
        public static final String SYNC_ERROR = Data.DATA5;
    }

    public static final String LEGACY_PHOTO_JOIN =
            " LEFT OUTER JOIN data legacy_photo ON (raw_contacts._id = legacy_photo.raw_contact_id"
            + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = legacy_photo.mimetype_id)"
                + "='" + LegacyPhotoData.CONTENT_ITEM_TYPE + "'"
            + " AND " + DataColumns.CONCRETE_ID + " = legacy_photo." + LegacyPhotoData.PHOTO_DATA_ID
            + ")";

    private static final HashMap<String, String> sPeopleProjectionMap;
    private static final HashMap<String, String> sOrganizationProjectionMap;
    private static final HashMap<String, String> sContactMethodProjectionMap;
    private static final HashMap<String, String> sPhoneProjectionMap;
    private static final HashMap<String, String> sExtensionProjectionMap;
    private static final HashMap<String, String> sGroupProjectionMap;
    private static final HashMap<String, String> sGroupMembershipProjectionMap;
    private static final HashMap<String, String> sPhotoProjectionMap;

    static {

        // Contacts URI matching table
        UriMatcher matcher = sUriMatcher;

        String authority = android.provider.Contacts.AUTHORITY;
        matcher.addURI(authority, "extensions", EXTENSIONS);
        matcher.addURI(authority, "extensions/#", EXTENSIONS_ID);
        matcher.addURI(authority, "groups", GROUPS);
        matcher.addURI(authority, "groups/#", GROUPS_ID);
        matcher.addURI(authority, "groups/name/*/members", GROUP_NAME_MEMBERS);
//        matcher.addURI(authority, "groups/name/*/members/filter/*",
//                GROUP_NAME_MEMBERS_FILTER);
        matcher.addURI(authority, "groups/system_id/*/members", GROUP_SYSTEM_ID_MEMBERS);
//        matcher.addURI(authority, "groups/system_id/*/members/filter/*",
//                GROUP_SYSTEM_ID_MEMBERS_FILTER);
        matcher.addURI(authority, "groupmembership", GROUPMEMBERSHIP);
        matcher.addURI(authority, "groupmembership/#", GROUPMEMBERSHIP_ID);
//        matcher.addURI(authority, "groupmembershipraw", GROUPMEMBERSHIP_RAW);
        matcher.addURI(authority, "people", PEOPLE);
//        matcher.addURI(authority, "people/strequent", PEOPLE_STREQUENT);
//        matcher.addURI(authority, "people/strequent/filter/*", PEOPLE_STREQUENT_FILTER);
        matcher.addURI(authority, "people/filter/*", PEOPLE_FILTER);
//        matcher.addURI(authority, "people/with_phones_filter/*",
//                PEOPLE_WITH_PHONES_FILTER);
//        matcher.addURI(authority, "people/with_email_or_im_filter/*",
//                PEOPLE_WITH_EMAIL_OR_IM_FILTER);
        matcher.addURI(authority, "people/#", PEOPLE_ID);
        matcher.addURI(authority, "people/#/extensions", PEOPLE_EXTENSIONS);
        matcher.addURI(authority, "people/#/extensions/#", PEOPLE_EXTENSIONS_ID);
        matcher.addURI(authority, "people/#/phones", PEOPLE_PHONES);
        matcher.addURI(authority, "people/#/phones/#", PEOPLE_PHONES_ID);
//        matcher.addURI(authority, "people/#/phones_with_presence",
//                PEOPLE_PHONES_WITH_PRESENCE);
        matcher.addURI(authority, "people/#/photo", PEOPLE_PHOTO);
//        matcher.addURI(authority, "people/#/photo/data", PEOPLE_PHOTO_DATA);
        matcher.addURI(authority, "people/#/contact_methods", PEOPLE_CONTACTMETHODS);
//        matcher.addURI(authority, "people/#/contact_methods_with_presence",
//                PEOPLE_CONTACTMETHODS_WITH_PRESENCE);
        matcher.addURI(authority, "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID);
        matcher.addURI(authority, "people/#/organizations", PEOPLE_ORGANIZATIONS);
        matcher.addURI(authority, "people/#/organizations/#", PEOPLE_ORGANIZATIONS_ID);
        matcher.addURI(authority, "people/#/groupmembership", PEOPLE_GROUPMEMBERSHIP);
        matcher.addURI(authority, "people/#/groupmembership/#", PEOPLE_GROUPMEMBERSHIP_ID);
//        matcher.addURI(authority, "people/raw", PEOPLE_RAW);
//        matcher.addURI(authority, "people/owner", PEOPLE_OWNER);
        matcher.addURI(authority, "people/#/update_contact_time",
                PEOPLE_UPDATE_CONTACT_TIME);
        matcher.addURI(authority, "deleted_people", DELETED_PEOPLE);
        matcher.addURI(authority, "deleted_groups", DELETED_GROUPS);
        matcher.addURI(authority, "phones", PHONES);
//        matcher.addURI(authority, "phones_with_presence", PHONES_WITH_PRESENCE);
        matcher.addURI(authority, "phones/filter/*", PHONES_FILTER);
//        matcher.addURI(authority, "phones/filter_name/*", PHONES_FILTER_NAME);
//        matcher.addURI(authority, "phones/mobile_filter_name/*",
//                PHONES_MOBILE_FILTER_NAME);
        matcher.addURI(authority, "phones/#", PHONES_ID);
        matcher.addURI(authority, "photos", PHOTOS);
        matcher.addURI(authority, "photos/#", PHOTOS_ID);
        matcher.addURI(authority, "contact_methods", CONTACTMETHODS);
        matcher.addURI(authority, "contact_methods/email", CONTACTMETHODS_EMAIL);
//        matcher.addURI(authority, "contact_methods/email/*", CONTACTMETHODS_EMAIL_FILTER);
        matcher.addURI(authority, "contact_methods/#", CONTACTMETHODS_ID);
//        matcher.addURI(authority, "contact_methods/with_presence",
//                CONTACTMETHODS_WITH_PRESENCE);
        matcher.addURI(authority, "organizations", ORGANIZATIONS);
        matcher.addURI(authority, "organizations/#", ORGANIZATIONS_ID);
//        matcher.addURI(authority, "voice_dialer_timestamp", VOICE_DIALER_TIMESTAMP);
        matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY,
                SEARCH_SUGGESTIONS);
        matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY + "/*",
                SEARCH_SUGGESTIONS);
        matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*",
                SEARCH_SHORTCUT);
        matcher.addURI(authority, "settings", SETTINGS);

        matcher.addURI(authority, "live_folders/people", LIVE_FOLDERS_PEOPLE);
        matcher.addURI(authority, "live_folders/people/*",
                LIVE_FOLDERS_PEOPLE_GROUP_NAME);
        matcher.addURI(authority, "live_folders/people_with_phones",
                LIVE_FOLDERS_PEOPLE_WITH_PHONES);
        matcher.addURI(authority, "live_folders/favorites",
                LIVE_FOLDERS_PEOPLE_FAVORITES);


        HashMap<String, String> peopleProjectionMap = new HashMap<String, String>();
        peopleProjectionMap.put(People.NAME, People.NAME);
        peopleProjectionMap.put(People.DISPLAY_NAME, People.DISPLAY_NAME);
        peopleProjectionMap.put(People.PHONETIC_NAME, People.PHONETIC_NAME);
        peopleProjectionMap.put(People.NOTES, People.NOTES);
        peopleProjectionMap.put(People.TIMES_CONTACTED, People.TIMES_CONTACTED);
        peopleProjectionMap.put(People.LAST_TIME_CONTACTED, People.LAST_TIME_CONTACTED);
        peopleProjectionMap.put(People.CUSTOM_RINGTONE, People.CUSTOM_RINGTONE);
        peopleProjectionMap.put(People.SEND_TO_VOICEMAIL, People.SEND_TO_VOICEMAIL);
        peopleProjectionMap.put(People.STARRED, People.STARRED);
        peopleProjectionMap.put(People.PRIMARY_ORGANIZATION_ID, People.PRIMARY_ORGANIZATION_ID);
        peopleProjectionMap.put(People.PRIMARY_EMAIL_ID, People.PRIMARY_EMAIL_ID);
        peopleProjectionMap.put(People.PRIMARY_PHONE_ID, People.PRIMARY_PHONE_ID);

        sPeopleProjectionMap = new HashMap<String, String>(peopleProjectionMap);
        sPeopleProjectionMap.put(People._ID, People._ID);
        sPeopleProjectionMap.put(People.NUMBER, People.NUMBER);
        sPeopleProjectionMap.put(People.TYPE, People.TYPE);
        sPeopleProjectionMap.put(People.LABEL, People.LABEL);
        sPeopleProjectionMap.put(People.NUMBER_KEY, People.NUMBER_KEY);
        sPeopleProjectionMap.put(People.IM_PROTOCOL, IM_PROTOCOL_SQL + " AS " + People.IM_PROTOCOL);
        sPeopleProjectionMap.put(People.IM_HANDLE, People.IM_HANDLE);
        sPeopleProjectionMap.put(People.IM_ACCOUNT, People.IM_ACCOUNT);
        sPeopleProjectionMap.put(People.PRESENCE_STATUS, People.PRESENCE_STATUS);
        sPeopleProjectionMap.put(People.PRESENCE_CUSTOM_STATUS,
                "(SELECT " + StatusUpdates.STATUS +
                " FROM " + Tables.STATUS_UPDATES +
                " JOIN " + Tables.DATA +
                "   ON(" + StatusUpdatesColumns.DATA_ID + "=" + DataColumns.CONCRETE_ID + ")" +
                " WHERE " + DataColumns.CONCRETE_RAW_CONTACT_ID + "=people." + People._ID +
                " ORDER BY " + StatusUpdates.STATUS_TIMESTAMP + " DESC " +
                " LIMIT 1" +
                ") AS " + People.PRESENCE_CUSTOM_STATUS);

        sOrganizationProjectionMap = new HashMap<String, String>();
        sOrganizationProjectionMap.put(android.provider.Contacts.Organizations._ID,
                android.provider.Contacts.Organizations._ID);
        sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.PERSON_ID,
                android.provider.Contacts.Organizations.PERSON_ID);
        sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.ISPRIMARY,
                android.provider.Contacts.Organizations.ISPRIMARY);
        sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.COMPANY,
                android.provider.Contacts.Organizations.COMPANY);
        sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.TYPE,
                android.provider.Contacts.Organizations.TYPE);
        sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.LABEL,
                android.provider.Contacts.Organizations.LABEL);
        sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.TITLE,
                android.provider.Contacts.Organizations.TITLE);

        sContactMethodProjectionMap = new HashMap<String, String>(peopleProjectionMap);
        sContactMethodProjectionMap.put(ContactMethods._ID, ContactMethods._ID);
        sContactMethodProjectionMap.put(ContactMethods.PERSON_ID, ContactMethods.PERSON_ID);
        sContactMethodProjectionMap.put(ContactMethods.KIND, ContactMethods.KIND);
        sContactMethodProjectionMap.put(ContactMethods.ISPRIMARY, ContactMethods.ISPRIMARY);
        sContactMethodProjectionMap.put(ContactMethods.TYPE, ContactMethods.TYPE);
        sContactMethodProjectionMap.put(ContactMethods.DATA, ContactMethods.DATA);
        sContactMethodProjectionMap.put(ContactMethods.LABEL, ContactMethods.LABEL);
        sContactMethodProjectionMap.put(ContactMethods.AUX_DATA, ContactMethods.AUX_DATA);

        sPhoneProjectionMap = new HashMap<String, String>(peopleProjectionMap);
        sPhoneProjectionMap.put(android.provider.Contacts.Phones._ID,
                android.provider.Contacts.Phones._ID);
        sPhoneProjectionMap.put(android.provider.Contacts.Phones.PERSON_ID,
                android.provider.Contacts.Phones.PERSON_ID);
        sPhoneProjectionMap.put(android.provider.Contacts.Phones.ISPRIMARY,
                android.provider.Contacts.Phones.ISPRIMARY);
        sPhoneProjectionMap.put(android.provider.Contacts.Phones.NUMBER,
                android.provider.Contacts.Phones.NUMBER);
        sPhoneProjectionMap.put(android.provider.Contacts.Phones.TYPE,
                android.provider.Contacts.Phones.TYPE);
        sPhoneProjectionMap.put(android.provider.Contacts.Phones.LABEL,
                android.provider.Contacts.Phones.LABEL);
        sPhoneProjectionMap.put(android.provider.Contacts.Phones.NUMBER_KEY,
                android.provider.Contacts.Phones.NUMBER_KEY);

        sExtensionProjectionMap = new HashMap<String, String>();
        sExtensionProjectionMap.put(android.provider.Contacts.Extensions._ID,
                android.provider.Contacts.Extensions._ID);
        sExtensionProjectionMap.put(android.provider.Contacts.Extensions.PERSON_ID,
                android.provider.Contacts.Extensions.PERSON_ID);
        sExtensionProjectionMap.put(android.provider.Contacts.Extensions.NAME,
                android.provider.Contacts.Extensions.NAME);
        sExtensionProjectionMap.put(android.provider.Contacts.Extensions.VALUE,
                android.provider.Contacts.Extensions.VALUE);

        sGroupProjectionMap = new HashMap<String, String>();
        sGroupProjectionMap.put(android.provider.Contacts.Groups._ID,
                android.provider.Contacts.Groups._ID);
        sGroupProjectionMap.put(android.provider.Contacts.Groups.NAME,
                android.provider.Contacts.Groups.NAME);
        sGroupProjectionMap.put(android.provider.Contacts.Groups.NOTES,
                android.provider.Contacts.Groups.NOTES);
        sGroupProjectionMap.put(android.provider.Contacts.Groups.SYSTEM_ID,
                android.provider.Contacts.Groups.SYSTEM_ID);

        sGroupMembershipProjectionMap = new HashMap<String, String>(sGroupProjectionMap);
        sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership._ID,
                android.provider.Contacts.GroupMembership._ID);
        sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership.PERSON_ID,
                android.provider.Contacts.GroupMembership.PERSON_ID);
        sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership.GROUP_ID,
                android.provider.Contacts.GroupMembership.GROUP_ID);
        sGroupMembershipProjectionMap.put(
                android.provider.Contacts.GroupMembership.GROUP_SYNC_ID,
                android.provider.Contacts.GroupMembership.GROUP_SYNC_ID);
        sGroupMembershipProjectionMap.put(
                android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT,
                android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT);
        sGroupMembershipProjectionMap.put(
                android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT_TYPE,
                android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT_TYPE);

        sPhotoProjectionMap = new HashMap<String, String>();
        sPhotoProjectionMap.put(android.provider.Contacts.Photos._ID,
                android.provider.Contacts.Photos._ID);
        sPhotoProjectionMap.put(android.provider.Contacts.Photos.PERSON_ID,
                android.provider.Contacts.Photos.PERSON_ID);
        sPhotoProjectionMap.put(android.provider.Contacts.Photos.DATA,
                android.provider.Contacts.Photos.DATA);
        sPhotoProjectionMap.put(android.provider.Contacts.Photos.LOCAL_VERSION,
                android.provider.Contacts.Photos.LOCAL_VERSION);
        sPhotoProjectionMap.put(android.provider.Contacts.Photos.DOWNLOAD_REQUIRED,
                android.provider.Contacts.Photos.DOWNLOAD_REQUIRED);
        sPhotoProjectionMap.put(android.provider.Contacts.Photos.EXISTS_ON_SERVER,
                android.provider.Contacts.Photos.EXISTS_ON_SERVER);
        sPhotoProjectionMap.put(android.provider.Contacts.Photos.SYNC_ERROR,
                android.provider.Contacts.Photos.SYNC_ERROR);
    }

    private final Context mContext;
    private final ContactsDatabaseHelper mDbHelper;
    private final ContactsProvider2 mContactsProvider;
    private final NameSplitter mPhoneticNameSplitter;
    private final GlobalSearchSupport mGlobalSearchSupport;

    private final SQLiteStatement mDataMimetypeQuery;
    private final SQLiteStatement mDataRawContactIdQuery;

    private final ContentValues mValues = new ContentValues();
    private final ContentValues mValues2 = new ContentValues();
    private final ContentValues mValues3 = new ContentValues();
    private boolean mDefaultAccountKnown;
    private Account mAccount;

    private long mMimetypeEmail;
    private long mMimetypeIm;
    private long mMimetypePostal;


    public LegacyApiSupport(Context context, ContactsDatabaseHelper contactsDatabaseHelper,
            ContactsProvider2 contactsProvider, GlobalSearchSupport globalSearchSupport) {
        mContext = context;
        mContactsProvider = contactsProvider;
        mDbHelper = contactsDatabaseHelper;
        mGlobalSearchSupport = globalSearchSupport;

        mPhoneticNameSplitter = new NameSplitter("", "", "", context
                .getString(com.android.internal.R.string.common_name_conjunctions), Locale
                .getDefault());

        SQLiteDatabase db = mDbHelper.getReadableDatabase();
        mDataMimetypeQuery = db.compileStatement(
                "SELECT " + DataColumns.MIMETYPE_ID +
                " FROM " + Tables.DATA +
                " WHERE " + Data._ID + "=?");

        mDataRawContactIdQuery = db.compileStatement(
                "SELECT " + Data.RAW_CONTACT_ID +
                " FROM " + Tables.DATA +
                " WHERE " + Data._ID + "=?");

        mMimetypeEmail = mDbHelper.getMimeTypeId(Email.CONTENT_ITEM_TYPE);
        mMimetypeIm = mDbHelper.getMimeTypeId(Im.CONTENT_ITEM_TYPE);
        mMimetypePostal = mDbHelper.getMimeTypeId(StructuredPostal.CONTENT_ITEM_TYPE);
    }

    private void ensureDefaultAccount() {
        if (!mDefaultAccountKnown) {
            mAccount = mContactsProvider.getDefaultAccount();
            mDefaultAccountKnown = true;
        }
    }

    public static void createDatabase(SQLiteDatabase db) {
        Log.i(TAG, "Bootstrapping database legacy support");
        createViews(db);
        createSettingsTable(db);
    }

    public static void createViews(SQLiteDatabase db) {

        String peopleColumns = "name." + StructuredName.DISPLAY_NAME
                        + " AS " + People.NAME + ", " +
                Tables.RAW_CONTACTS + "." + RawContactsColumns.DISPLAY_NAME
                        + " AS " + People.DISPLAY_NAME + ", " +
                PHONETIC_NAME_SQL
                        + " AS " + People.PHONETIC_NAME + " , " +
                "note." + Note.NOTE
                        + " AS " + People.NOTES + ", " +
                RawContacts.ACCOUNT_NAME + ", " +
                RawContacts.ACCOUNT_TYPE + ", " +
                Tables.RAW_CONTACTS + "." + RawContacts.TIMES_CONTACTED
                        + " AS " + People.TIMES_CONTACTED + ", " +
                Tables.RAW_CONTACTS + "." + RawContacts.LAST_TIME_CONTACTED
                        + " AS " + People.LAST_TIME_CONTACTED + ", " +
                Tables.RAW_CONTACTS + "." + RawContacts.CUSTOM_RINGTONE
                        + " AS " + People.CUSTOM_RINGTONE + ", " +
                Tables.RAW_CONTACTS + "." + RawContacts.SEND_TO_VOICEMAIL
                        + " AS " + People.SEND_TO_VOICEMAIL + ", " +
                Tables.RAW_CONTACTS + "." + RawContacts.STARRED
                        + " AS " + People.STARRED + ", " +
                "organization." + Data._ID
                        + " AS " + People.PRIMARY_ORGANIZATION_ID + ", " +
                "email." + Data._ID
                        + " AS " + People.PRIMARY_EMAIL_ID + ", " +
                "phone." + Data._ID
                        + " AS " + People.PRIMARY_PHONE_ID + ", " +
                "phone." + Phone.NUMBER
                        + " AS " + People.NUMBER + ", " +
                "phone." + Phone.TYPE
                        + " AS " + People.TYPE + ", " +
                "phone." + Phone.LABEL
                        + " AS " + People.LABEL + ", " +
                "phone." + PhoneColumns.NORMALIZED_NUMBER
                        + " AS " + People.NUMBER_KEY;

        db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.PEOPLE + ";");
        db.execSQL("CREATE VIEW " + LegacyTables.PEOPLE + " AS SELECT " +
                RawContactsColumns.CONCRETE_ID
                        + " AS " + android.provider.Contacts.People._ID + ", " +
                peopleColumns +
                " FROM " + Tables.RAW_CONTACTS + PEOPLE_JOINS +
                " WHERE " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0" +
                " AND " + RawContacts.IS_RESTRICTED + "=0" + ";");

        db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.ORGANIZATIONS + ";");
        db.execSQL("CREATE VIEW " + LegacyTables.ORGANIZATIONS + " AS SELECT " +
                DataColumns.CONCRETE_ID
                        + " AS " + android.provider.Contacts.Organizations._ID + ", " +
                Data.RAW_CONTACT_ID
                        + " AS " + android.provider.Contacts.Organizations.PERSON_ID + ", " +
                Data.IS_PRIMARY
                        + " AS " + android.provider.Contacts.Organizations.ISPRIMARY + ", " +
                RawContacts.ACCOUNT_NAME + ", " +
                RawContacts.ACCOUNT_TYPE + ", " +
                Organization.COMPANY
                        + " AS " + android.provider.Contacts.Organizations.COMPANY + ", " +
                Organization.TYPE
                        + " AS " + android.provider.Contacts.Organizations.TYPE + ", " +
                Organization.LABEL
                        + " AS " + android.provider.Contacts.Organizations.LABEL + ", " +
                Organization.TITLE
                        + " AS " + android.provider.Contacts.Organizations.TITLE +
                " FROM " + Tables.DATA_JOIN_MIMETYPE_RAW_CONTACTS +
                " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
                        + Organization.CONTENT_ITEM_TYPE + "'"
                        + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0"
                        + " AND " + RawContacts.IS_RESTRICTED + "=0" +
        ";");

        db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.CONTACT_METHODS + ";");
        db.execSQL("CREATE VIEW " + LegacyTables.CONTACT_METHODS + " AS SELECT " +
                DataColumns.CONCRETE_ID
                        + " AS " + ContactMethods._ID + ", " +
                DataColumns.CONCRETE_RAW_CONTACT_ID
                        + " AS " + ContactMethods.PERSON_ID + ", " +
                CONTACT_METHOD_KIND_SQL
                        + " AS " + ContactMethods.KIND + ", " +
                DataColumns.CONCRETE_IS_PRIMARY
                        + " AS " + ContactMethods.ISPRIMARY + ", " +
                Tables.DATA + "." + Email.TYPE
                        + " AS " + ContactMethods.TYPE + ", " +
                CONTACT_METHOD_DATA_SQL
                        + " AS " + ContactMethods.DATA + ", " +
                Tables.DATA + "." + Email.LABEL
                        + " AS " + ContactMethods.LABEL + ", " +
                DataColumns.CONCRETE_DATA14
                        + " AS " + ContactMethods.AUX_DATA + ", " +
                peopleColumns +
                " FROM " + Tables.DATA + DATA_JOINS +
                " WHERE " + ContactMethods.KIND + " IS NOT NULL"
                    + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0"
                    + " AND " + RawContacts.IS_RESTRICTED + "=0" +
        ";");


        db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.PHONES + ";");
        db.execSQL("CREATE VIEW " + LegacyTables.PHONES + " AS SELECT " +
                DataColumns.CONCRETE_ID
                        + " AS " + android.provider.Contacts.Phones._ID + ", " +
                DataColumns.CONCRETE_RAW_CONTACT_ID
                        + " AS " + android.provider.Contacts.Phones.PERSON_ID + ", " +
                DataColumns.CONCRETE_IS_PRIMARY
                        + " AS " + android.provider.Contacts.Phones.ISPRIMARY + ", " +
                Tables.DATA + "." + Phone.NUMBER
                        + " AS " + android.provider.Contacts.Phones.NUMBER + ", " +
                Tables.DATA + "." + Phone.TYPE
                        + " AS " + android.provider.Contacts.Phones.TYPE + ", " +
                Tables.DATA + "." + Phone.LABEL
                        + " AS " + android.provider.Contacts.Phones.LABEL + ", " +
                Tables.PHONE_LOOKUP + "." + PhoneLookupColumns.NORMALIZED_NUMBER
                        + " AS " + android.provider.Contacts.Phones.NUMBER_KEY + ", " +
                peopleColumns +
                " FROM " + Tables.DATA
                        + " JOIN " + Tables.PHONE_LOOKUP
                        + " ON (" + Tables.DATA + "._id = "
                                + Tables.PHONE_LOOKUP + "." + PhoneLookupColumns.DATA_ID + ")"
                        + DATA_JOINS +
                " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
                        + Phone.CONTENT_ITEM_TYPE + "'"
                        + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0"
                        + " AND " + RawContacts.IS_RESTRICTED + "=0" +
        ";");

        db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.EXTENSIONS + ";");
        db.execSQL("CREATE VIEW " + LegacyTables.EXTENSIONS + " AS SELECT " +
                DataColumns.CONCRETE_ID
                        + " AS " + android.provider.Contacts.Extensions._ID + ", " +
                DataColumns.CONCRETE_RAW_CONTACT_ID
                        + " AS " + android.provider.Contacts.Extensions.PERSON_ID + ", " +
                RawContacts.ACCOUNT_NAME + ", " +
                RawContacts.ACCOUNT_TYPE + ", " +
                ExtensionsColumns.NAME
                        + " AS " + android.provider.Contacts.Extensions.NAME + ", " +
                ExtensionsColumns.VALUE
                        + " AS " + android.provider.Contacts.Extensions.VALUE +
                " FROM " + Tables.DATA_JOIN_MIMETYPE_RAW_CONTACTS +
                " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
                        + android.provider.Contacts.Extensions.CONTENT_ITEM_TYPE + "'"
                        + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0"
                        + " AND " + RawContacts.IS_RESTRICTED + "=0" +
        ";");

        db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.GROUPS + ";");
        db.execSQL("CREATE VIEW " + LegacyTables.GROUPS + " AS SELECT " +
                GroupsColumns.CONCRETE_ID + " AS " + android.provider.Contacts.Groups._ID + ", " +
                Groups.ACCOUNT_NAME + ", " +
                Groups.ACCOUNT_TYPE + ", " +
                Groups.TITLE + " AS " + android.provider.Contacts.Groups.NAME + ", " +
                Groups.NOTES + " AS " + android.provider.Contacts.Groups.NOTES + " , " +
                Groups.SYSTEM_ID + " AS " + android.provider.Contacts.Groups.SYSTEM_ID +
                " FROM " + Tables.GROUPS +
        ";");

        db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.GROUP_MEMBERSHIP + ";");
        db.execSQL("CREATE VIEW " + LegacyTables.GROUP_MEMBERSHIP + " AS SELECT " +
                DataColumns.CONCRETE_ID
                        + " AS " + android.provider.Contacts.GroupMembership._ID + ", " +
                DataColumns.CONCRETE_RAW_CONTACT_ID
                        + " AS " + android.provider.Contacts.GroupMembership.PERSON_ID + ", " +
                Tables.RAW_CONTACTS + "." + RawContacts.ACCOUNT_NAME
                        + " AS " +  RawContacts.ACCOUNT_NAME + ", " +
                Tables.RAW_CONTACTS + "." + RawContacts.ACCOUNT_TYPE
                        + " AS " +  RawContacts.ACCOUNT_TYPE + ", " +
                GroupMembership.GROUP_ROW_ID
                        + " AS " + android.provider.Contacts.GroupMembership.GROUP_ID + ", " +
                Groups.TITLE
                        + " AS " + android.provider.Contacts.GroupMembership.NAME + ", " +
                Groups.NOTES
                        + " AS " + android.provider.Contacts.GroupMembership.NOTES + ", " +
                Groups.SYSTEM_ID
                        + " AS " + android.provider.Contacts.GroupMembership.SYSTEM_ID + ", " +
                GroupsColumns.CONCRETE_SOURCE_ID
                        + " AS "
                        + android.provider.Contacts.GroupMembership.GROUP_SYNC_ID + ", " +
                GroupsColumns.CONCRETE_ACCOUNT_NAME
                        + " AS "
                        + android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT + ", " +
                GroupsColumns.CONCRETE_ACCOUNT_TYPE
                        + " AS "
                        + android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT_TYPE +
                " FROM " + Tables.DATA_JOIN_PACKAGES_MIMETYPES_RAW_CONTACTS_GROUPS +
                " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
                        + GroupMembership.CONTENT_ITEM_TYPE + "'"
                        + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0" +
        ";");

        db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.PHOTOS + ";");
        db.execSQL("CREATE VIEW " + LegacyTables.PHOTOS + " AS SELECT " +
                DataColumns.CONCRETE_ID
                        + " AS " + android.provider.Contacts.Photos._ID + ", " +
                DataColumns.CONCRETE_RAW_CONTACT_ID
                        + " AS " + android.provider.Contacts.Photos.PERSON_ID + ", " +
                RawContacts.ACCOUNT_NAME + ", " +
                RawContacts.ACCOUNT_TYPE + ", " +
                Tables.DATA + "." + Photo.PHOTO
                        + " AS " + android.provider.Contacts.Photos.DATA + ", " +
                "legacy_photo." + LegacyPhotoData.EXISTS_ON_SERVER
                        + " AS " + android.provider.Contacts.Photos.EXISTS_ON_SERVER + ", " +
                "legacy_photo." + LegacyPhotoData.DOWNLOAD_REQUIRED
                        + " AS " + android.provider.Contacts.Photos.DOWNLOAD_REQUIRED + ", " +
                "legacy_photo." + LegacyPhotoData.LOCAL_VERSION
                        + " AS " + android.provider.Contacts.Photos.LOCAL_VERSION + ", " +
                "legacy_photo." + LegacyPhotoData.SYNC_ERROR
                        + " AS " + android.provider.Contacts.Photos.SYNC_ERROR +
                " FROM " + Tables.DATA + DATA_JOINS + LEGACY_PHOTO_JOIN +
                " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
                        + Photo.CONTENT_ITEM_TYPE + "'"
                        + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0"
                        + " AND " + RawContacts.IS_RESTRICTED + "=0" +
        ";");

    }

    public static void createSettingsTable(SQLiteDatabase db) {
        db.execSQL("DROP TABLE IF EXISTS " + LegacyTables.SETTINGS + ";");
        db.execSQL("CREATE TABLE " + LegacyTables.SETTINGS + " (" +
                android.provider.Contacts.Settings._ID + " INTEGER PRIMARY KEY," +
                android.provider.Contacts.Settings._SYNC_ACCOUNT + " TEXT," +
                android.provider.Contacts.Settings._SYNC_ACCOUNT_TYPE + " TEXT," +
                android.provider.Contacts.Settings.KEY + " STRING NOT NULL," +
                android.provider.Contacts.Settings.VALUE + " STRING " +
        ");");
    }

    public Uri insert(Uri uri, ContentValues values) {
        ensureDefaultAccount();
        final int match = sUriMatcher.match(uri);
        long id = 0;
        switch (match) {
            case PEOPLE:
                id = insertPeople(values);
                break;

            case ORGANIZATIONS:
                id = insertOrganization(values);
                break;

            case PEOPLE_CONTACTMETHODS: {
                long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
                id = insertContactMethod(rawContactId, values);
                break;
            }

            case CONTACTMETHODS: {
                long rawContactId = getRequiredValue(values, ContactMethods.PERSON_ID);
                id = insertContactMethod(rawContactId, values);
                break;
            }

            case PHONES: {
                long rawContactId = getRequiredValue(values,
                        android.provider.Contacts.Phones.PERSON_ID);
                id = insertPhone(rawContactId, values);
                break;
            }

            case PEOPLE_PHONES: {
                long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
                id = insertPhone(rawContactId, values);
                break;
            }

            case EXTENSIONS: {
                long rawContactId = getRequiredValue(values,
                        android.provider.Contacts.Extensions.PERSON_ID);
                id = insertExtension(rawContactId, values);
                break;
            }

            case GROUPS:
                id = insertGroup(values);
                break;

            case GROUPMEMBERSHIP: {
                long rawContactId = getRequiredValue(values,
                        android.provider.Contacts.GroupMembership.PERSON_ID);
                long groupId = getRequiredValue(values,
                        android.provider.Contacts.GroupMembership.GROUP_ID);
                id = insertGroupMembership(rawContactId, groupId);
                break;
            }

            default:
                throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
        }

        if (id < 0) {
            return null;
        }

        final Uri result = ContentUris.withAppendedId(uri, id);
        onChange(result);
        return result;
    }

    private long getRequiredValue(ContentValues values, String column) {
        if (!values.containsKey(column)) {
            throw new RuntimeException("Required value: " + column);
        }

        return values.getAsLong(column);
    }

    private long insertPeople(ContentValues values) {
        parsePeopleValues(values);

        Uri contactUri = mContactsProvider.insertInTransaction(RawContacts.CONTENT_URI, mValues);
        long rawContactId = ContentUris.parseId(contactUri);

        if (mValues2.size() != 0) {
            mValues2.put(Data.RAW_CONTACT_ID, rawContactId);
            mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues2);
        }
        if (mValues3.size() != 0) {
            mValues3.put(Data.RAW_CONTACT_ID, rawContactId);
            mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues3);
        }

        return rawContactId;
    }

    private long insertOrganization(ContentValues values) {
        parseOrganizationValues(values);
        ContactsDatabaseHelper.copyLongValue(mValues, Data.RAW_CONTACT_ID,
                values, android.provider.Contacts.Organizations.PERSON_ID);

        Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);

        return ContentUris.parseId(uri);
    }

    private long insertPhone(long rawContactId, ContentValues values) {
        parsePhoneValues(values);
        mValues.put(Data.RAW_CONTACT_ID, rawContactId);

        Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);

        return ContentUris.parseId(uri);
    }

    private long insertContactMethod(long rawContactId, ContentValues values) {
        Integer kind = values.getAsInteger(ContactMethods.KIND);
        if (kind == null) {
            throw new RuntimeException("Required value: " + ContactMethods.KIND);
        }

        parseContactMethodValues(kind, values);

        mValues.put(Data.RAW_CONTACT_ID, rawContactId);
        Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
        return ContentUris.parseId(uri);
    }

    private long insertExtension(long rawContactId, ContentValues values) {
        mValues.clear();

        mValues.put(Data.RAW_CONTACT_ID, rawContactId);
        mValues.put(Data.MIMETYPE, android.provider.Contacts.Extensions.CONTENT_ITEM_TYPE);

        parseExtensionValues(values);

        Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
        return ContentUris.parseId(uri);
    }

    private long insertGroup(ContentValues values) {
        parseGroupValues(values);

        if (mAccount != null) {
            mValues.put(Groups.ACCOUNT_NAME, mAccount.name);
            mValues.put(Groups.ACCOUNT_TYPE, mAccount.type);
        }

        Uri uri = mContactsProvider.insertInTransaction(Groups.CONTENT_URI, mValues);
        return ContentUris.parseId(uri);
    }

    private long insertGroupMembership(long rawContactId, long groupId) {
        mValues.clear();

        mValues.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
        mValues.put(GroupMembership.RAW_CONTACT_ID, rawContactId);
        mValues.put(GroupMembership.GROUP_ROW_ID, groupId);

        Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
        return ContentUris.parseId(uri);
    }

    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        ensureDefaultAccount();

        int match = sUriMatcher.match(uri);
        int count = 0;
        switch(match) {
            case PEOPLE_UPDATE_CONTACT_TIME: {
                count = updateContactTime(uri, values);
                break;
            }

            case PEOPLE_PHOTO: {
                long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
                return updatePhoto(rawContactId, values);
            }

            case SETTINGS: {
                return updateSettings(values);
            }

            case GROUPMEMBERSHIP:
            case GROUPMEMBERSHIP_ID:
            case -1: {
                throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
            }

            default: {
                count = updateAll(uri, match, values, selection, selectionArgs);
            }
        }

        if (count > 0) {
            mContext.getContentResolver().notifyChange(uri, null);
        }

        return count;
    }

    private int updateAll(Uri uri, final int match, ContentValues values, String selection,
            String[] selectionArgs) {
        Cursor c = query(uri, IdQuery.COLUMNS, selection, selectionArgs, null, null);
        if (c == null) {
            return 0;
        }

        int count = 0;
        try {
            while (c.moveToNext()) {
                long id = c.getLong(IdQuery._ID);
                count += update(match, id, values);
            }
        } finally {
            c.close();
        }

        return count;
    }

    public int update(int match, long id, ContentValues values) {
        int count = 0;
        switch(match) {
            case PEOPLE:
            case PEOPLE_ID: {
                count = updatePeople(id, values);
                break;
            }

            case ORGANIZATIONS:
            case ORGANIZATIONS_ID: {
                count = updateOrganizations(id, values);
                break;
            }

            case PHONES:
            case PHONES_ID: {
                count = updatePhones(id, values);
                break;
            }

            case CONTACTMETHODS:
            case CONTACTMETHODS_ID: {
                count = updateContactMethods(id, values);
                break;
            }

            case EXTENSIONS:
            case EXTENSIONS_ID: {
                count = updateExtensions(id, values);
                break;
            }

            case GROUPS:
            case GROUPS_ID: {
                count = updateGroups(id, values);
                break;
            }

            case PHOTOS:
            case PHOTOS_ID:
                count = updatePhotoByDataId(id, values);
                break;
        }

        return count;
    }

    private int updatePeople(long rawContactId, ContentValues values) {
        parsePeopleValues(values);

        int count = mContactsProvider.update(RawContacts.CONTENT_URI,
                mValues, RawContacts._ID + "=" + rawContactId, null);

        if (count == 0) {
            return 0;
        }

        if (mValues2.size() != 0) {
            Uri dataUri = findFirstDataRow(rawContactId, StructuredName.CONTENT_ITEM_TYPE);
            if (dataUri != null) {
                mContactsProvider.update(dataUri, mValues2, null, null);
            } else {
                mValues2.put(Data.RAW_CONTACT_ID, rawContactId);
                mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues2);
            }
        }

        if (mValues3.size() != 0) {
            Uri dataUri = findFirstDataRow(rawContactId, Note.CONTENT_ITEM_TYPE);
            if (dataUri != null) {
                mContactsProvider.update(dataUri, mValues3, null, null);
            } else {
                mValues3.put(Data.RAW_CONTACT_ID, rawContactId);
                mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues3);
            }
        }

        if (values.containsKey(People.LAST_TIME_CONTACTED) &&
                !values.containsKey(People.TIMES_CONTACTED)) {
            updateContactTime(rawContactId, values);
        }

        return count;
    }

    private int updateOrganizations(long dataId, ContentValues values) {
        parseOrganizationValues(values);

        return mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
                Data._ID + "=" + dataId, null);
    }

    private int updatePhones(long dataId, ContentValues values) {
        parsePhoneValues(values);

        return mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
                Data._ID + "=" + dataId, null);
    }

    private int updateContactMethods(long dataId, ContentValues values) {
        int kind;

        mDataMimetypeQuery.bindLong(1, dataId);
        long mimetype_id;
        try {
            mimetype_id = mDataMimetypeQuery.simpleQueryForLong();
        } catch (SQLiteDoneException e) {
            // Data row not found
            return 0;
        }

        if (mimetype_id == mMimetypeEmail) {
            kind = android.provider.Contacts.KIND_EMAIL;
        } else if (mimetype_id == mMimetypeIm) {
            kind = android.provider.Contacts.KIND_IM;
        } else if (mimetype_id == mMimetypePostal) {
            kind = android.provider.Contacts.KIND_POSTAL;
        } else {

            // Non-legacy kind: return "Not found"
            return 0;
        }

        parseContactMethodValues(kind, values);

        return mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
                Data._ID + "=" + dataId, null);
    }

    private int updateExtensions(long dataId, ContentValues values) {
        parseExtensionValues(values);

        return mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
                Data._ID + "=" + dataId, null);
    }

    private int updateGroups(long groupId, ContentValues values) {
        parseGroupValues(values);

        return mContactsProvider.updateInTransaction(Groups.CONTENT_URI, mValues,
                Groups._ID + "=" + groupId, null);
    }

    private int updateContactTime(Uri uri, ContentValues values) {
        long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
        updateContactTime(rawContactId, values);
        return 1;
    }

    private void updateContactTime(long rawContactId, ContentValues values) {
        long lastTimeContacted;
        if (values.containsKey(People.LAST_TIME_CONTACTED)) {
            lastTimeContacted = values.getAsLong(People.LAST_TIME_CONTACTED);
        } else {
            lastTimeContacted = System.currentTimeMillis();
        }

        // TODO check sanctions
        long contactId = mDbHelper.getContactId(rawContactId);
        SQLiteDatabase mDb = mDbHelper.getWritableDatabase();
        mSelectionArgs2[0] = String.valueOf(lastTimeContacted);
        if (contactId != 0) {
            mSelectionArgs2[1] = String.valueOf(contactId);
            mDb.execSQL(CONTACTS_UPDATE_LASTTIMECONTACTED, mSelectionArgs2);
            // increment times_contacted column
            mSelectionArgs1[0] = String.valueOf(contactId);
            mDb.execSQL(ContactsProvider2.UPDATE_TIMES_CONTACTED_CONTACTS_TABLE, mSelectionArgs1);
        }
        mSelectionArgs2[1] = String.valueOf(rawContactId);
        mDb.execSQL(RAWCONTACTS_UPDATE_LASTTIMECONTACTED, mSelectionArgs2);
        // increment times_contacted column
        mSelectionArgs1[0] = String.valueOf(contactId);
        mDb.execSQL(ContactsProvider2.UPDATE_TIMES_CONTACTED_RAWCONTACTS_TABLE, mSelectionArgs1);
    }

    private int updatePhoto(long rawContactId, ContentValues values) {

        // TODO check sanctions

        int count;

        long dataId = findFirstDataId(rawContactId, Photo.CONTENT_ITEM_TYPE);

        mValues.clear();
        byte[] bytes = values.getAsByteArray(android.provider.Contacts.Photos.DATA);
        mValues.put(Photo.PHOTO, bytes);

        if (dataId == -1) {
            mValues.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
            mValues.put(Data.RAW_CONTACT_ID, rawContactId);
            Uri dataUri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
            dataId = ContentUris.parseId(dataUri);
            count = 1;
        } else {
            Uri dataUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
            count = mContactsProvider.updateInTransaction(dataUri, mValues, null, null);
        }

        updateLegacyPhotoData(rawContactId, dataId, values);

        return count;
    }

    private int updatePhotoByDataId(long dataId, ContentValues values) {

        mDataRawContactIdQuery.bindLong(1, dataId);
        long rawContactId;

        try {
            rawContactId = mDataRawContactIdQuery.simpleQueryForLong();
        } catch (SQLiteDoneException e) {
            // Data row not found
            return 0;
        }

        if (values.containsKey(android.provider.Contacts.Photos.DATA)) {
            byte[] bytes = values.getAsByteArray(android.provider.Contacts.Photos.DATA);
            mValues.clear();
            mValues.put(Photo.PHOTO, bytes);
            mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
                    Data._ID + "=" + dataId, null);
        }

        updateLegacyPhotoData(rawContactId, dataId, values);

        return 1;
    }

    private void updateLegacyPhotoData(long rawContactId, long dataId, ContentValues values) {
        mValues.clear();
        ContactsDatabaseHelper.copyStringValue(mValues, LegacyPhotoData.LOCAL_VERSION,
                values, android.provider.Contacts.Photos.LOCAL_VERSION);
        ContactsDatabaseHelper.copyStringValue(mValues, LegacyPhotoData.DOWNLOAD_REQUIRED,
                values, android.provider.Contacts.Photos.DOWNLOAD_REQUIRED);
        ContactsDatabaseHelper.copyStringValue(mValues, LegacyPhotoData.EXISTS_ON_SERVER,
                values, android.provider.Contacts.Photos.EXISTS_ON_SERVER);
        ContactsDatabaseHelper.copyStringValue(mValues, LegacyPhotoData.SYNC_ERROR,
                values, android.provider.Contacts.Photos.SYNC_ERROR);

        int updated = mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
                Data.MIMETYPE + "='" + LegacyPhotoData.CONTENT_ITEM_TYPE + "'"
                        + " AND " + Data.RAW_CONTACT_ID + "=" + rawContactId
                        + " AND " + LegacyPhotoData.PHOTO_DATA_ID + "=" + dataId, null);
        if (updated == 0) {
            mValues.put(Data.RAW_CONTACT_ID, rawContactId);
            mValues.put(Data.MIMETYPE, LegacyPhotoData.CONTENT_ITEM_TYPE);
            mValues.put(LegacyPhotoData.PHOTO_DATA_ID, dataId);
            mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
        }
    }

    private int updateSettings(ContentValues values) {
        SQLiteDatabase db = mDbHelper.getWritableDatabase();
        String accountName = values.getAsString(android.provider.Contacts.Settings._SYNC_ACCOUNT);
        String accountType =
                values.getAsString(android.provider.Contacts.Settings._SYNC_ACCOUNT_TYPE);
        String key = values.getAsString(android.provider.Contacts.Settings.KEY);
        if (key == null) {
            throw new IllegalArgumentException("you must specify the key when updating settings");
        }
        updateSetting(db, accountName, accountType, values);
        if (key.equals(android.provider.Contacts.Settings.SYNC_EVERYTHING)) {
            mValues.clear();
            mValues.put(Settings.SHOULD_SYNC,
                    values.getAsInteger(android.provider.Contacts.Settings.VALUE));
            String selection;
            String[] selectionArgs;
            if (accountName != null && accountType != null) {
                selectionArgs = new String[]{accountName, accountType};
                selection = Settings.ACCOUNT_NAME + "=?"
                        + " AND " + Settings.ACCOUNT_TYPE + "=?";
            } else {
                selectionArgs = null;
                selection = Settings.ACCOUNT_NAME + " IS NULL"
                        + " AND " + Settings.ACCOUNT_TYPE + " IS NULL";
            }
            int count = mContactsProvider.updateInTransaction(Settings.CONTENT_URI, mValues,
                    selection, selectionArgs);
            if (count == 0) {
                mValues.put(Settings.ACCOUNT_NAME, accountName);
                mValues.put(Settings.ACCOUNT_TYPE, accountType);
                mContactsProvider.insertInTransaction(Settings.CONTENT_URI, mValues);
            }
        }
        return 1;
    }

    private void updateSetting(SQLiteDatabase db, String accountName, String accountType,
            ContentValues values) {
        final String key = values.getAsString(android.provider.Contacts.Settings.KEY);
        if (accountName == null || accountType == null) {
            db.delete(LegacyTables.SETTINGS, "_sync_account IS NULL AND key=?", new String[]{key});
        } else {
            db.delete(LegacyTables.SETTINGS, "_sync_account=? AND _sync_account_type=? AND key=?",
                    new String[]{accountName, accountType, key});
        }
        long rowId = db.insert(LegacyTables.SETTINGS,
                android.provider.Contacts.Settings.KEY, values);
        if (rowId < 0) {
            throw new SQLException("error updating settings with " + values);
        }
    }

    private interface SettingsMatchQuery {
        String SQL =
            "SELECT "
                    + ContactsContract.Settings.ACCOUNT_NAME + ","
                    + ContactsContract.Settings.ACCOUNT_TYPE + ","
                    + ContactsContract.Settings.SHOULD_SYNC +
            " FROM " + Tables.SETTINGS + " LEFT OUTER JOIN " + LegacyTables.SETTINGS +
            " ON (" + ContactsContract.Settings.ACCOUNT_NAME + "="
                              + android.provider.Contacts.Settings._SYNC_ACCOUNT +
                      " AND " + ContactsContract.Settings.ACCOUNT_TYPE + "="
                              + android.provider.Contacts.Settings._SYNC_ACCOUNT_TYPE +
                      " AND " + android.provider.Contacts.Settings.KEY + "='"
                              + android.provider.Contacts.Settings.SYNC_EVERYTHING + "'" +
            ")" +
            " WHERE " + ContactsContract.Settings.SHOULD_SYNC + "<>"
                            + android.provider.Contacts.Settings.VALUE;

        int ACCOUNT_NAME = 0;
        int ACCOUNT_TYPE = 1;
        int SHOULD_SYNC = 2;
    }

    /**
     * Brings legacy settings table in sync with the new settings.
     */
    public void copySettingsToLegacySettings() {
        SQLiteDatabase db = mDbHelper.getWritableDatabase();
        Cursor cursor = db.rawQuery(SettingsMatchQuery.SQL, null);
        try {
            while(cursor.moveToNext()) {
                String accountName = cursor.getString(SettingsMatchQuery.ACCOUNT_NAME);
                String accountType = cursor.getString(SettingsMatchQuery.ACCOUNT_TYPE);
                String value = cursor.getString(SettingsMatchQuery.SHOULD_SYNC);
                mValues.clear();
                mValues.put(android.provider.Contacts.Settings._SYNC_ACCOUNT, accountName);
                mValues.put(android.provider.Contacts.Settings._SYNC_ACCOUNT_TYPE, accountType);
                mValues.put(android.provider.Contacts.Settings.KEY,
                        android.provider.Contacts.Settings.SYNC_EVERYTHING);
                mValues.put(android.provider.Contacts.Settings.VALUE, value);
                updateSetting(db, accountName, accountType, mValues);
            }
        } finally {
            cursor.close();
        }
    }

    private void parsePeopleValues(ContentValues values) {
        mValues.clear();
        mValues2.clear();
        mValues3.clear();

        ContactsDatabaseHelper.copyStringValue(mValues, RawContacts.CUSTOM_RINGTONE,
                values, People.CUSTOM_RINGTONE);
        ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.SEND_TO_VOICEMAIL,
                values, People.SEND_TO_VOICEMAIL);
        ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.LAST_TIME_CONTACTED,
                values, People.LAST_TIME_CONTACTED);
        ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.TIMES_CONTACTED,
                values, People.TIMES_CONTACTED);
        ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.STARRED,
                values, People.STARRED);
        if (mAccount != null) {
            mValues.put(RawContacts.ACCOUNT_NAME, mAccount.name);
            mValues.put(RawContacts.ACCOUNT_TYPE, mAccount.type);
        }

        if (values.containsKey(People.NAME) || values.containsKey(People.PHONETIC_NAME)) {
            mValues2.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
            ContactsDatabaseHelper.copyStringValue(mValues2, StructuredName.DISPLAY_NAME,
                    values, People.NAME);
            if (values.containsKey(People.PHONETIC_NAME)) {
                String phoneticName = values.getAsString(People.PHONETIC_NAME);
                NameSplitter.Name parsedName = new NameSplitter.Name();
                mPhoneticNameSplitter.split(parsedName, phoneticName);
                mValues2.put(StructuredName.PHONETIC_GIVEN_NAME, parsedName.getGivenNames());
                mValues2.put(StructuredName.PHONETIC_MIDDLE_NAME, parsedName.getMiddleName());
                mValues2.put(StructuredName.PHONETIC_FAMILY_NAME, parsedName.getFamilyName());
            }
        }

        if (values.containsKey(People.NOTES)) {
            mValues3.put(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
            ContactsDatabaseHelper.copyStringValue(mValues3, Note.NOTE, values, People.NOTES);
        }
    }

    private void parseOrganizationValues(ContentValues values) {
        mValues.clear();

        mValues.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);

        ContactsDatabaseHelper.copyLongValue(mValues, Data.IS_PRIMARY,
                values, android.provider.Contacts.Organizations.ISPRIMARY);

        ContactsDatabaseHelper.copyStringValue(mValues, Organization.COMPANY,
                values, android.provider.Contacts.Organizations.COMPANY);

        // TYPE values happen to remain the same between V1 and V2 - can just copy the value
        ContactsDatabaseHelper.copyLongValue(mValues, Organization.TYPE,
                values, android.provider.Contacts.Organizations.TYPE);

        ContactsDatabaseHelper.copyStringValue(mValues, Organization.LABEL,
                values, android.provider.Contacts.Organizations.LABEL);
        ContactsDatabaseHelper.copyStringValue(mValues, Organization.TITLE,
                values, android.provider.Contacts.Organizations.TITLE);
    }

    private void parsePhoneValues(ContentValues values) {
        mValues.clear();

        mValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);

        ContactsDatabaseHelper.copyLongValue(mValues, Data.IS_PRIMARY,
                values, android.provider.Contacts.Phones.ISPRIMARY);

        ContactsDatabaseHelper.copyStringValue(mValues, Phone.NUMBER,
                values, android.provider.Contacts.Phones.NUMBER);

        // TYPE values happen to remain the same between V1 and V2 - can just copy the value
        ContactsDatabaseHelper.copyLongValue(mValues, Phone.TYPE,
                values, android.provider.Contacts.Phones.TYPE);

        ContactsDatabaseHelper.copyStringValue(mValues, Phone.LABEL,
                values, android.provider.Contacts.Phones.LABEL);
    }

    private void parseContactMethodValues(int kind, ContentValues values) {
        mValues.clear();

        ContactsDatabaseHelper.copyLongValue(mValues, Data.IS_PRIMARY, values,
                ContactMethods.ISPRIMARY);

        switch (kind) {
            case android.provider.Contacts.KIND_EMAIL: {
                copyCommonFields(values, Email.CONTENT_ITEM_TYPE, Email.TYPE, Email.LABEL,
                        Data.DATA14);
                ContactsDatabaseHelper.copyStringValue(mValues, Email.DATA, values,
                        ContactMethods.DATA);
                break;
            }

            case android.provider.Contacts.KIND_IM: {
                String protocol = values.getAsString(ContactMethods.DATA);
                if (protocol.startsWith("pre:")) {
                    mValues.put(Im.PROTOCOL, Integer.parseInt(protocol.substring(4)));
                } else if (protocol.startsWith("custom:")) {
                    mValues.put(Im.PROTOCOL, Im.PROTOCOL_CUSTOM);
                    mValues.put(Im.CUSTOM_PROTOCOL, protocol.substring(7));
                }

                copyCommonFields(values, Im.CONTENT_ITEM_TYPE, Im.TYPE, Im.LABEL, Data.DATA14);
                break;
            }

            case android.provider.Contacts.KIND_POSTAL: {
                copyCommonFields(values, StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.TYPE,
                        StructuredPostal.LABEL, Data.DATA14);
                ContactsDatabaseHelper.copyStringValue(mValues, StructuredPostal.FORMATTED_ADDRESS,
                        values, ContactMethods.DATA);
                break;
            }
        }
    }

    private void copyCommonFields(ContentValues values, String mimeType, String typeColumn,
            String labelColumn, String auxDataColumn) {
        mValues.put(Data.MIMETYPE, mimeType);
        ContactsDatabaseHelper.copyLongValue(mValues, typeColumn, values,
                ContactMethods.TYPE);
        ContactsDatabaseHelper.copyStringValue(mValues, labelColumn, values,
                ContactMethods.LABEL);
        ContactsDatabaseHelper.copyStringValue(mValues, auxDataColumn, values,
                ContactMethods.AUX_DATA);
    }

    private void parseGroupValues(ContentValues values) {
        mValues.clear();

        ContactsDatabaseHelper.copyStringValue(mValues, Groups.TITLE,
                values, android.provider.Contacts.Groups.NAME);
        ContactsDatabaseHelper.copyStringValue(mValues, Groups.NOTES,
                values, android.provider.Contacts.Groups.NOTES);
        ContactsDatabaseHelper.copyStringValue(mValues, Groups.SYSTEM_ID,
                values, android.provider.Contacts.Groups.SYSTEM_ID);
    }

    private void parseExtensionValues(ContentValues values) {
        ContactsDatabaseHelper.copyStringValue(mValues, ExtensionsColumns.NAME,
                values, android.provider.Contacts.People.Extensions.NAME);
        ContactsDatabaseHelper.copyStringValue(mValues, ExtensionsColumns.VALUE,
                values, android.provider.Contacts.People.Extensions.VALUE);
    }

    private Uri findFirstDataRow(long rawContactId, String contentItemType) {
        long dataId = findFirstDataId(rawContactId, contentItemType);
        if (dataId == -1) {
            return null;
        }

        return ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
    }

    private long findFirstDataId(long rawContactId, String mimeType) {
        long dataId = -1;
        Cursor c = mContactsProvider.query(Data.CONTENT_URI, IdQuery.COLUMNS,
                Data.RAW_CONTACT_ID + "=" + rawContactId + " AND "
                        + Data.MIMETYPE + "='" + mimeType + "'",
                null, null);
        try {
            if (c.moveToFirst()) {
                dataId = c.getLong(IdQuery._ID);
            }
        } finally {
            c.close();
        }
        return dataId;
    }


    public int delete(Uri uri, String selection, String[] selectionArgs) {
        final int match = sUriMatcher.match(uri);
        if (match == -1 || match == SETTINGS) {
            throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
        }

        Cursor c = query(uri, IdQuery.COLUMNS, selection, selectionArgs, null, null);
        if (c == null) {
            return 0;
        }

        int count = 0;
        try {
            while (c.moveToNext()) {
                long id = c.getLong(IdQuery._ID);
                count += delete(uri, match, id);
            }
        } finally {
            c.close();
        }

        return count;
    }

    public int delete(Uri uri, int match, long id) {
        int count = 0;
        switch (match) {
            case PEOPLE:
            case PEOPLE_ID:
                count = mContactsProvider.deleteRawContact(id, mDbHelper.getContactId(id), false);
                break;

            case PEOPLE_PHOTO:
                mValues.clear();
                mValues.putNull(android.provider.Contacts.Photos.DATA);
                updatePhoto(id, mValues);
                break;

            case ORGANIZATIONS:
            case ORGANIZATIONS_ID:
                count = mContactsProvider.deleteData(id, ORGANIZATION_MIME_TYPES);
                break;

            case CONTACTMETHODS:
            case CONTACTMETHODS_ID:
                count = mContactsProvider.deleteData(id, CONTACT_METHOD_MIME_TYPES);
                break;

            case PHONES:
            case PHONES_ID:
                count = mContactsProvider.deleteData(id, PHONE_MIME_TYPES);
                break;

            case EXTENSIONS:
            case EXTENSIONS_ID:
                count = mContactsProvider.deleteData(id, EXTENSION_MIME_TYPES);
                break;

            case PHOTOS:
            case PHOTOS_ID:
                count = mContactsProvider.deleteData(id, PHOTO_MIME_TYPES);
                break;

            case GROUPMEMBERSHIP:
            case GROUPMEMBERSHIP_ID:
                count = mContactsProvider.deleteData(id, GROUP_MEMBERSHIP_MIME_TYPES);
                break;

            case GROUPS:
            case GROUPS_ID:
                count = mContactsProvider.deleteGroup(uri, id, false);
                break;

            default:
                throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
        }

        return count;
    }

    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder, String limit) {
        ensureDefaultAccount();

        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        String groupBy = null;

        final int match = sUriMatcher.match(uri);
        switch (match) {
            case PEOPLE: {
                qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
                qb.setProjectionMap(sPeopleProjectionMap);
                applyRawContactsAccount(qb);
                break;
            }

            case PEOPLE_ID:
                qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
                qb.setProjectionMap(sPeopleProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + People._ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                break;

            case PEOPLE_FILTER: {
                qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
                qb.setProjectionMap(sPeopleProjectionMap);
                applyRawContactsAccount(qb);
                String filterParam = uri.getPathSegments().get(2);
                qb.appendWhere(" AND " + People._ID + " IN "
                        + mContactsProvider.getRawContactsByFilterAsNestedQuery(filterParam));
                break;
            }

            case GROUP_NAME_MEMBERS:
                qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
                qb.setProjectionMap(sPeopleProjectionMap);
                applyRawContactsAccount(qb);
                String group = uri.getPathSegments().get(2);
                qb.appendWhere(" AND " + buildGroupNameMatchWhereClause(group));
                break;

            case GROUP_SYSTEM_ID_MEMBERS:
                qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
                qb.setProjectionMap(sPeopleProjectionMap);
                applyRawContactsAccount(qb);
                String systemId = uri.getPathSegments().get(2);
                qb.appendWhere(" AND " + buildGroupSystemIdMatchWhereClause(systemId));
                break;

            case ORGANIZATIONS:
                qb.setTables(LegacyTables.ORGANIZATIONS + " organizations");
                qb.setProjectionMap(sOrganizationProjectionMap);
                applyRawContactsAccount(qb);
                break;

            case ORGANIZATIONS_ID:
                qb.setTables(LegacyTables.ORGANIZATIONS + " organizations");
                qb.setProjectionMap(sOrganizationProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.Organizations._ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                break;

            case PEOPLE_ORGANIZATIONS:
                qb.setTables(LegacyTables.ORGANIZATIONS + " organizations");
                qb.setProjectionMap(sOrganizationProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.Organizations.PERSON_ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                break;

            case PEOPLE_ORGANIZATIONS_ID:
                qb.setTables(LegacyTables.ORGANIZATIONS + " organizations");
                qb.setProjectionMap(sOrganizationProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.Organizations.PERSON_ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                qb.appendWhere(" AND " + android.provider.Contacts.Organizations._ID + "=");
                qb.appendWhere(uri.getPathSegments().get(3));
                break;

            case CONTACTMETHODS:
                qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
                qb.setProjectionMap(sContactMethodProjectionMap);
                applyRawContactsAccount(qb);
                break;

            case CONTACTMETHODS_ID:
                qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
                qb.setProjectionMap(sContactMethodProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + ContactMethods._ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                break;

            case CONTACTMETHODS_EMAIL:
                qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
                qb.setProjectionMap(sContactMethodProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + ContactMethods.KIND + "="
                        + android.provider.Contacts.KIND_EMAIL);
                break;

            case PEOPLE_CONTACTMETHODS:
                qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
                qb.setProjectionMap(sContactMethodProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + ContactMethods.PERSON_ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                qb.appendWhere(" AND " + ContactMethods.KIND + " IS NOT NULL");
                break;

            case PEOPLE_CONTACTMETHODS_ID:
                qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
                qb.setProjectionMap(sContactMethodProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + ContactMethods.PERSON_ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                qb.appendWhere(" AND " + ContactMethods._ID + "=");
                qb.appendWhere(uri.getPathSegments().get(3));
                qb.appendWhere(" AND " + ContactMethods.KIND + " IS NOT NULL");
                break;

            case PHONES:
                qb.setTables(LegacyTables.PHONES + " phones");
                qb.setProjectionMap(sPhoneProjectionMap);
                applyRawContactsAccount(qb);
                break;

            case PHONES_ID:
                qb.setTables(LegacyTables.PHONES + " phones");
                qb.setProjectionMap(sPhoneProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.Phones._ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                break;

            case PHONES_FILTER:
                qb.setTables(LegacyTables.PHONES + " phones");
                qb.setProjectionMap(sPhoneProjectionMap);
                applyRawContactsAccount(qb);
                if (uri.getPathSegments().size() > 2) {
                    String filterParam = uri.getLastPathSegment();
                    qb.appendWhere(" AND person =");
                    qb.appendWhere(mDbHelper.buildPhoneLookupAsNestedQuery(filterParam));
                }
                break;

            case PEOPLE_PHONES:
                qb.setTables(LegacyTables.PHONES + " phones");
                qb.setProjectionMap(sPhoneProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.Phones.PERSON_ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                break;

            case PEOPLE_PHONES_ID:
                qb.setTables(LegacyTables.PHONES + " phones");
                qb.setProjectionMap(sPhoneProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.Phones.PERSON_ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                qb.appendWhere(" AND " + android.provider.Contacts.Phones._ID + "=");
                qb.appendWhere(uri.getPathSegments().get(3));
                break;

            case EXTENSIONS:
                qb.setTables(LegacyTables.EXTENSIONS + " extensions");
                qb.setProjectionMap(sExtensionProjectionMap);
                applyRawContactsAccount(qb);
                break;

            case EXTENSIONS_ID:
                qb.setTables(LegacyTables.EXTENSIONS + " extensions");
                qb.setProjectionMap(sExtensionProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.Extensions._ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                break;

            case PEOPLE_EXTENSIONS:
                qb.setTables(LegacyTables.EXTENSIONS + " extensions");
                qb.setProjectionMap(sExtensionProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.Extensions.PERSON_ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                break;

            case PEOPLE_EXTENSIONS_ID:
                qb.setTables(LegacyTables.EXTENSIONS + " extensions");
                qb.setProjectionMap(sExtensionProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.Extensions.PERSON_ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                qb.appendWhere(" AND " + android.provider.Contacts.Extensions._ID + "=");
                qb.appendWhere(uri.getPathSegments().get(3));
                break;

            case GROUPS:
                qb.setTables(LegacyTables.GROUPS + " groups");
                qb.setProjectionMap(sGroupProjectionMap);
                applyGroupAccount(qb);
                break;

            case GROUPS_ID:
                qb.setTables(LegacyTables.GROUPS + " groups");
                qb.setProjectionMap(sGroupProjectionMap);
                applyGroupAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.Groups._ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                break;

            case GROUPMEMBERSHIP:
                qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
                qb.setProjectionMap(sGroupMembershipProjectionMap);
                applyRawContactsAccount(qb);
                break;

            case GROUPMEMBERSHIP_ID:
                qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
                qb.setProjectionMap(sGroupMembershipProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership._ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                break;

            case PEOPLE_GROUPMEMBERSHIP:
                qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
                qb.setProjectionMap(sGroupMembershipProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership.PERSON_ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                break;

            case PEOPLE_GROUPMEMBERSHIP_ID:
                qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
                qb.setProjectionMap(sGroupMembershipProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership.PERSON_ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership._ID + "=");
                qb.appendWhere(uri.getPathSegments().get(3));
                break;

            case PEOPLE_PHOTO:
                qb.setTables(LegacyTables.PHOTOS + " photos");
                qb.setProjectionMap(sPhotoProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.Photos.PERSON_ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                limit = "1";
                break;

            case PHOTOS:
                qb.setTables(LegacyTables.PHOTOS + " photos");
                qb.setProjectionMap(sPhotoProjectionMap);
                applyRawContactsAccount(qb);
                break;

            case PHOTOS_ID:
                qb.setTables(LegacyTables.PHOTOS + " photos");
                qb.setProjectionMap(sPhotoProjectionMap);
                applyRawContactsAccount(qb);
                qb.appendWhere(" AND " + android.provider.Contacts.Photos._ID + "=");
                qb.appendWhere(uri.getPathSegments().get(1));
                break;

            case SEARCH_SUGGESTIONS:

                // No legacy compatibility for search suggestions
                return mGlobalSearchSupport.handleSearchSuggestionsQuery(db, uri, limit);

            case SEARCH_SHORTCUT: {
                String lookupKey = uri.getLastPathSegment();
                return mGlobalSearchSupport.handleSearchShortcutRefresh(db, lookupKey, projection);
            }

            case LIVE_FOLDERS_PEOPLE:
                return mContactsProvider.query(LIVE_FOLDERS_CONTACTS_URI,
                        projection, selection, selectionArgs, sortOrder);

            case LIVE_FOLDERS_PEOPLE_WITH_PHONES:
                return mContactsProvider.query(LIVE_FOLDERS_CONTACTS_WITH_PHONES_URI,
                        projection, selection, selectionArgs, sortOrder);

            case LIVE_FOLDERS_PEOPLE_FAVORITES:
                return mContactsProvider.query(LIVE_FOLDERS_CONTACTS_FAVORITES_URI,
                        projection, selection, selectionArgs, sortOrder);

            case LIVE_FOLDERS_PEOPLE_GROUP_NAME:
                return mContactsProvider.query(Uri.withAppendedPath(LIVE_FOLDERS_CONTACTS_URI,
                        Uri.encode(uri.getLastPathSegment())),
                        projection, selection, selectionArgs, sortOrder);

            case DELETED_PEOPLE:
            case DELETED_GROUPS:
                throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));

            case SETTINGS:
                copySettingsToLegacySettings();
                qb.setTables(LegacyTables.SETTINGS);
                break;

            default:
                throw new IllegalArgumentException(mDbHelper.exceptionMessage(uri));
        }

        // Perform the query and set the notification uri
        final Cursor c = qb.query(db, projection, selection, selectionArgs,
                groupBy, null, sortOrder, limit);
        if (c != null) {
            c.setNotificationUri(mContext.getContentResolver(),
                    android.provider.Contacts.CONTENT_URI);
        }
        return c;
    }

    private void applyRawContactsAccount(SQLiteQueryBuilder qb) {
        StringBuilder sb = new StringBuilder();
        appendRawContactsAccount(sb);
        qb.appendWhere(sb.toString());
    }

    private void appendRawContactsAccount(StringBuilder sb) {
        if (mAccount != null) {
            sb.append(RawContacts.ACCOUNT_NAME + "=");
            DatabaseUtils.appendEscapedSQLString(sb, mAccount.name);
            sb.append(" AND " + RawContacts.ACCOUNT_TYPE + "=");
            DatabaseUtils.appendEscapedSQLString(sb, mAccount.type);
        } else {
            sb.append(RawContacts.ACCOUNT_NAME + " IS NULL" +
                    " AND " + RawContacts.ACCOUNT_TYPE + " IS NULL");
        }
    }

    private void applyGroupAccount(SQLiteQueryBuilder qb) {
        StringBuilder sb = new StringBuilder();
        appendGroupAccount(sb);
        qb.appendWhere(sb.toString());
    }

    private void appendGroupAccount(StringBuilder sb) {
        if (mAccount != null) {
            sb.append(Groups.ACCOUNT_NAME + "=");
            DatabaseUtils.appendEscapedSQLString(sb, mAccount.name);
            sb.append(" AND " + Groups.ACCOUNT_TYPE + "=");
            DatabaseUtils.appendEscapedSQLString(sb, mAccount.type);
        } else {
            sb.append(Groups.ACCOUNT_NAME + " IS NULL" +
                    " AND " + Groups.ACCOUNT_TYPE + " IS NULL");
        }
    }

    /**
     * Build a WHERE clause that restricts the query to match people that are a member of
     * a group with a particular name. The projection map of the query must include
     * {@link People#_ID}.
     *
     * @param groupName The name of the group
     * @return The where clause.
     */
    private String buildGroupNameMatchWhereClause(String groupName) {
        return "people._id IN "
                + "(SELECT " + DataColumns.CONCRETE_RAW_CONTACT_ID
                + " FROM " + Tables.DATA_JOIN_MIMETYPES
                + " WHERE " + Data.MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
                        + "' AND " + GroupMembership.GROUP_ROW_ID + "="
                                + "(SELECT " + Tables.GROUPS + "." + Groups._ID
                                + " FROM " + Tables.GROUPS
                                + " WHERE " + Groups.TITLE + "="
                                        + DatabaseUtils.sqlEscapeString(groupName) + "))";
    }

    /**
     * Build a WHERE clause that restricts the query to match people that are a member of
     * a group with a particular system id. The projection map of the query must include
     * {@link People#_ID}.
     *
     * @param groupName The name of the group
     * @return The where clause.
     */
    private String buildGroupSystemIdMatchWhereClause(String systemId) {
        return "people._id IN "
                + "(SELECT " + DataColumns.CONCRETE_RAW_CONTACT_ID
                + " FROM " + Tables.DATA_JOIN_MIMETYPES
                + " WHERE " + Data.MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
                        + "' AND " + GroupMembership.GROUP_ROW_ID + "="
                                + "(SELECT " + Tables.GROUPS + "." + Groups._ID
                                + " FROM " + Tables.GROUPS
                                + " WHERE " + Groups.SYSTEM_ID + "="
                                        + DatabaseUtils.sqlEscapeString(systemId) + "))";
    }

    /**
     * Called when a change has been made.
     *
     * @param uri the uri that the change was made to
     */
    private void onChange(Uri uri) {
        mContext.getContentResolver().notifyChange(android.provider.Contacts.CONTENT_URI, null);
    }

    public String getType(Uri uri) {
        int match = sUriMatcher.match(uri);
        switch (match) {
            case EXTENSIONS:
            case PEOPLE_EXTENSIONS:
                return Extensions.CONTENT_TYPE;
            case EXTENSIONS_ID:
            case PEOPLE_EXTENSIONS_ID:
                return Extensions.CONTENT_ITEM_TYPE;
            case PEOPLE:
                return "vnd.android.cursor.dir/person";
            case PEOPLE_ID:
                return "vnd.android.cursor.item/person";
            case PEOPLE_PHONES:
                return "vnd.android.cursor.dir/phone";
            case PEOPLE_PHONES_ID:
                return "vnd.android.cursor.item/phone";
            case PEOPLE_CONTACTMETHODS:
                return "vnd.android.cursor.dir/contact-methods";
            case PEOPLE_CONTACTMETHODS_ID:
                return getContactMethodType(uri);
            case PHONES:
                return "vnd.android.cursor.dir/phone";
            case PHONES_ID:
                return "vnd.android.cursor.item/phone";
            case PHONES_FILTER:
                return "vnd.android.cursor.dir/phone";
            case PHOTOS_ID:
                return "vnd.android.cursor.item/photo";
            case PHOTOS:
                return "vnd.android.cursor.dir/photo";
            case PEOPLE_PHOTO:
                return "vnd.android.cursor.item/photo";
            case CONTACTMETHODS:
                return "vnd.android.cursor.dir/contact-methods";
            case CONTACTMETHODS_ID:
                return getContactMethodType(uri);
            case ORGANIZATIONS:
                return "vnd.android.cursor.dir/organizations";
            case ORGANIZATIONS_ID:
                return "vnd.android.cursor.item/organization";
            case SEARCH_SUGGESTIONS:
                return SearchManager.SUGGEST_MIME_TYPE;
            case SEARCH_SHORTCUT:
                return SearchManager.SHORTCUT_MIME_TYPE;
            default:
                throw new IllegalArgumentException(mDbHelper.exceptionMessage(uri));
        }
    }

    private String getContactMethodType(Uri url) {
        String mime = null;

        Cursor c = query(url, new String[] {ContactMethods.KIND}, null, null, null, null);
        if (c != null) {
            try {
                if (c.moveToFirst()) {
                    int kind = c.getInt(0);
                    switch (kind) {
                    case android.provider.Contacts.KIND_EMAIL:
                        mime = "vnd.android.cursor.item/email";
                        break;

                    case android.provider.Contacts.KIND_IM:
                        mime = "vnd.android.cursor.item/jabber-im";
                        break;

                    case android.provider.Contacts.KIND_POSTAL:
                        mime = "vnd.android.cursor.item/postal-address";
                        break;
                    }
                }
            } finally {
                c.close();
            }
        }
        return mime;
    }
}