Home | History | Annotate | Download | only in contacts
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 package com.android.providers.contacts;
     17 
     18 import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
     19 import com.android.providers.contacts.ContactsDatabaseHelper.ExtensionsColumns;
     20 import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
     21 import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
     22 import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns;
     23 import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType;
     24 import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
     25 import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
     26 import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
     27 import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
     28 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
     29 
     30 import android.accounts.Account;
     31 import android.app.SearchManager;
     32 import android.content.ContentUris;
     33 import android.content.ContentValues;
     34 import android.content.Context;
     35 import android.content.UriMatcher;
     36 import android.database.Cursor;
     37 import android.database.DatabaseUtils;
     38 import android.database.SQLException;
     39 import android.database.sqlite.SQLiteDatabase;
     40 import android.database.sqlite.SQLiteDoneException;
     41 import android.database.sqlite.SQLiteQueryBuilder;
     42 import android.database.sqlite.SQLiteStatement;
     43 import android.net.Uri;
     44 import android.provider.BaseColumns;
     45 import android.provider.Contacts.ContactMethods;
     46 import android.provider.Contacts.Extensions;
     47 import android.provider.Contacts.People;
     48 import android.provider.ContactsContract;
     49 import android.provider.ContactsContract.CommonDataKinds.Email;
     50 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
     51 import android.provider.ContactsContract.CommonDataKinds.Im;
     52 import android.provider.ContactsContract.CommonDataKinds.Note;
     53 import android.provider.ContactsContract.CommonDataKinds.Organization;
     54 import android.provider.ContactsContract.CommonDataKinds.Phone;
     55 import android.provider.ContactsContract.CommonDataKinds.Photo;
     56 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
     57 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
     58 import android.provider.ContactsContract.Contacts;
     59 import android.provider.ContactsContract.Data;
     60 import android.provider.ContactsContract.Groups;
     61 import android.provider.ContactsContract.RawContacts;
     62 import android.provider.ContactsContract.Settings;
     63 import android.provider.ContactsContract.StatusUpdates;
     64 import android.text.TextUtils;
     65 import android.util.Log;
     66 
     67 import java.util.HashMap;
     68 import java.util.Locale;
     69 
     70 @SuppressWarnings("deprecation")
     71 public class LegacyApiSupport {
     72 
     73     private static final String TAG = "ContactsProviderV1";
     74 
     75     private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
     76 
     77     private static final int PEOPLE = 1;
     78     private static final int PEOPLE_ID = 2;
     79     private static final int PEOPLE_UPDATE_CONTACT_TIME = 3;
     80     private static final int ORGANIZATIONS = 4;
     81     private static final int ORGANIZATIONS_ID = 5;
     82     private static final int PEOPLE_CONTACTMETHODS = 6;
     83     private static final int PEOPLE_CONTACTMETHODS_ID = 7;
     84     private static final int CONTACTMETHODS = 8;
     85     private static final int CONTACTMETHODS_ID = 9;
     86     private static final int PEOPLE_PHONES = 10;
     87     private static final int PEOPLE_PHONES_ID = 11;
     88     private static final int PHONES = 12;
     89     private static final int PHONES_ID = 13;
     90     private static final int EXTENSIONS = 14;
     91     private static final int EXTENSIONS_ID = 15;
     92     private static final int PEOPLE_EXTENSIONS = 16;
     93     private static final int PEOPLE_EXTENSIONS_ID = 17;
     94     private static final int GROUPS = 18;
     95     private static final int GROUPS_ID = 19;
     96     private static final int GROUPMEMBERSHIP = 20;
     97     private static final int GROUPMEMBERSHIP_ID = 21;
     98     private static final int PEOPLE_GROUPMEMBERSHIP = 22;
     99     private static final int PEOPLE_GROUPMEMBERSHIP_ID = 23;
    100     private static final int PEOPLE_PHOTO = 24;
    101     private static final int PHOTOS = 25;
    102     private static final int PHOTOS_ID = 26;
    103     private static final int PEOPLE_FILTER = 29;
    104     private static final int DELETED_PEOPLE = 30;
    105     private static final int DELETED_GROUPS = 31;
    106     private static final int SEARCH_SUGGESTIONS = 32;
    107     private static final int SEARCH_SHORTCUT = 33;
    108     private static final int PHONES_FILTER = 34;
    109     private static final int LIVE_FOLDERS_PEOPLE = 35;
    110     private static final int LIVE_FOLDERS_PEOPLE_GROUP_NAME = 36;
    111     private static final int LIVE_FOLDERS_PEOPLE_WITH_PHONES = 37;
    112     private static final int LIVE_FOLDERS_PEOPLE_FAVORITES = 38;
    113     private static final int CONTACTMETHODS_EMAIL = 39;
    114     private static final int GROUP_NAME_MEMBERS = 40;
    115     private static final int GROUP_SYSTEM_ID_MEMBERS = 41;
    116     private static final int PEOPLE_ORGANIZATIONS = 42;
    117     private static final int PEOPLE_ORGANIZATIONS_ID = 43;
    118     private static final int SETTINGS = 44;
    119 
    120     private static final String PEOPLE_JOINS =
    121             " LEFT OUTER JOIN data name ON (raw_contacts._id = name.raw_contact_id"
    122             + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = name.mimetype_id)"
    123                     + "='" + StructuredName.CONTENT_ITEM_TYPE + "')"
    124             + " LEFT OUTER JOIN data organization ON (raw_contacts._id = organization.raw_contact_id"
    125             + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = organization.mimetype_id)"
    126                     + "='" + Organization.CONTENT_ITEM_TYPE + "' AND organization.is_primary)"
    127             + " LEFT OUTER JOIN data email ON (raw_contacts._id = email.raw_contact_id"
    128             + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = email.mimetype_id)"
    129                     + "='" + Email.CONTENT_ITEM_TYPE + "' AND email.is_primary)"
    130             + " LEFT OUTER JOIN data note ON (raw_contacts._id = note.raw_contact_id"
    131             + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = note.mimetype_id)"
    132                     + "='" + Note.CONTENT_ITEM_TYPE + "')"
    133             + " LEFT OUTER JOIN data phone ON (raw_contacts._id = phone.raw_contact_id"
    134             + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = phone.mimetype_id)"
    135                     + "='" + Phone.CONTENT_ITEM_TYPE + "' AND phone.is_primary)";
    136 
    137     public static final String DATA_JOINS =
    138             " JOIN mimetypes ON (mimetypes._id = data.mimetype_id)"
    139             + " JOIN raw_contacts ON (raw_contacts._id = data.raw_contact_id)"
    140             + PEOPLE_JOINS;
    141 
    142     public static final String PRESENCE_JOINS =
    143             " LEFT OUTER JOIN " + Tables.PRESENCE +
    144             " ON (" + Tables.PRESENCE + "." + StatusUpdates.DATA_ID + "=" +
    145                     "(SELECT MAX(" + StatusUpdates.DATA_ID + ")" +
    146                     " FROM " + Tables.PRESENCE +
    147                     " WHERE people._id = " + PresenceColumns.RAW_CONTACT_ID + ")" +
    148             " )";
    149 
    150     private static final String PHONETIC_NAME_SQL = "trim(trim("
    151             + "ifnull(name." + StructuredName.PHONETIC_GIVEN_NAME + ",' ')||' '||"
    152             + "ifnull(name." + StructuredName.PHONETIC_MIDDLE_NAME + ",' '))||' '||"
    153             + "ifnull(name." + StructuredName.PHONETIC_FAMILY_NAME + ",' ')) ";
    154 
    155     private static final String CONTACT_METHOD_KIND_SQL =
    156             "CAST ((CASE WHEN mimetype='" + Email.CONTENT_ITEM_TYPE + "'"
    157                 + " THEN " + android.provider.Contacts.KIND_EMAIL
    158                 + " ELSE"
    159                     + " (CASE WHEN mimetype='" + Im.CONTENT_ITEM_TYPE +"'"
    160                         + " THEN " + android.provider.Contacts.KIND_IM
    161                         + " ELSE"
    162                         + " (CASE WHEN mimetype='" + StructuredPostal.CONTENT_ITEM_TYPE + "'"
    163                             + " THEN "  + android.provider.Contacts.KIND_POSTAL
    164                             + " ELSE"
    165                                 + " NULL"
    166                             + " END)"
    167                         + " END)"
    168                 + " END) AS INTEGER)";
    169 
    170     private static final String IM_PROTOCOL_SQL =
    171             "(CASE WHEN " + StatusUpdates.PROTOCOL + "=" + Im.PROTOCOL_CUSTOM
    172                 + " THEN 'custom:'||" + StatusUpdates.CUSTOM_PROTOCOL
    173                 + " ELSE 'pre:'||" + StatusUpdates.PROTOCOL
    174                 + " END)";
    175 
    176     private static String CONTACT_METHOD_DATA_SQL =
    177             "(CASE WHEN " + Data.MIMETYPE + "='" + Im.CONTENT_ITEM_TYPE + "'"
    178                 + " THEN (CASE WHEN " + Tables.DATA + "." + Im.PROTOCOL + "=" + Im.PROTOCOL_CUSTOM
    179                     + " THEN 'custom:'||" + Tables.DATA + "." + Im.CUSTOM_PROTOCOL
    180                     + " ELSE 'pre:'||" + Tables.DATA + "." + Im.PROTOCOL
    181                     + " END)"
    182                 + " ELSE " + Tables.DATA + "." + Email.DATA
    183                 + " END)";
    184 
    185     private static final Uri LIVE_FOLDERS_CONTACTS_URI = Uri.withAppendedPath(
    186             ContactsContract.AUTHORITY_URI, "live_folders/contacts");
    187 
    188     private static final Uri LIVE_FOLDERS_CONTACTS_WITH_PHONES_URI = Uri.withAppendedPath(
    189             ContactsContract.AUTHORITY_URI, "live_folders/contacts_with_phones");
    190 
    191     private static final Uri LIVE_FOLDERS_CONTACTS_FAVORITES_URI = Uri.withAppendedPath(
    192             ContactsContract.AUTHORITY_URI, "live_folders/favorites");
    193 
    194     private static final String CONTACTS_UPDATE_LASTTIMECONTACTED =
    195             "UPDATE " + Tables.CONTACTS +
    196             " SET " + Contacts.LAST_TIME_CONTACTED + "=? " +
    197             "WHERE " + Contacts._ID + "=?";
    198     private static final String RAWCONTACTS_UPDATE_LASTTIMECONTACTED =
    199             "UPDATE " + Tables.RAW_CONTACTS + " SET "
    200             + RawContacts.LAST_TIME_CONTACTED + "=? WHERE "
    201             + RawContacts._ID + "=?";
    202 
    203     private String[] mSelectionArgs1 = new String[1];
    204     private String[] mSelectionArgs2 = new String[2];
    205 
    206     public interface LegacyTables {
    207         public static final String PEOPLE = "view_v1_people";
    208         public static final String PEOPLE_JOIN_PRESENCE = "view_v1_people people " + PRESENCE_JOINS;
    209         public static final String GROUPS = "view_v1_groups";
    210         public static final String ORGANIZATIONS = "view_v1_organizations";
    211         public static final String CONTACT_METHODS = "view_v1_contact_methods";
    212         public static final String PHONES = "view_v1_phones";
    213         public static final String EXTENSIONS = "view_v1_extensions";
    214         public static final String GROUP_MEMBERSHIP = "view_v1_group_membership";
    215         public static final String PHOTOS = "view_v1_photos";
    216         public static final String SETTINGS = "v1_settings";
    217     }
    218 
    219     private static final String[] ORGANIZATION_MIME_TYPES = new String[] {
    220         Organization.CONTENT_ITEM_TYPE
    221     };
    222 
    223     private static final String[] CONTACT_METHOD_MIME_TYPES = new String[] {
    224         Email.CONTENT_ITEM_TYPE,
    225         Im.CONTENT_ITEM_TYPE,
    226         StructuredPostal.CONTENT_ITEM_TYPE,
    227     };
    228 
    229     private static final String[] PHONE_MIME_TYPES = new String[] {
    230         Phone.CONTENT_ITEM_TYPE
    231     };
    232 
    233     private static final String[] PHOTO_MIME_TYPES = new String[] {
    234         Photo.CONTENT_ITEM_TYPE
    235     };
    236 
    237     private static final String[] GROUP_MEMBERSHIP_MIME_TYPES = new String[] {
    238         GroupMembership.CONTENT_ITEM_TYPE
    239     };
    240 
    241     private static final String[] EXTENSION_MIME_TYPES = new String[] {
    242         android.provider.Contacts.Extensions.CONTENT_ITEM_TYPE
    243     };
    244 
    245     private interface IdQuery {
    246         String[] COLUMNS = { BaseColumns._ID };
    247 
    248         int _ID = 0;
    249     }
    250 
    251     /**
    252      * A custom data row that is used to store legacy photo data fields no
    253      * longer directly supported by the API.
    254      */
    255     private interface LegacyPhotoData {
    256         public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/photo_v1_extras";
    257 
    258         public static final String PHOTO_DATA_ID = Data.DATA1;
    259         public static final String LOCAL_VERSION = Data.DATA2;
    260         public static final String DOWNLOAD_REQUIRED = Data.DATA3;
    261         public static final String EXISTS_ON_SERVER = Data.DATA4;
    262         public static final String SYNC_ERROR = Data.DATA5;
    263     }
    264 
    265     public static final String LEGACY_PHOTO_JOIN =
    266             " LEFT OUTER JOIN data legacy_photo ON (raw_contacts._id = legacy_photo.raw_contact_id"
    267             + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = legacy_photo.mimetype_id)"
    268                 + "='" + LegacyPhotoData.CONTENT_ITEM_TYPE + "'"
    269             + " AND " + DataColumns.CONCRETE_ID + " = legacy_photo." + LegacyPhotoData.PHOTO_DATA_ID
    270             + ")";
    271 
    272     private static final HashMap<String, String> sPeopleProjectionMap;
    273     private static final HashMap<String, String> sOrganizationProjectionMap;
    274     private static final HashMap<String, String> sContactMethodProjectionMap;
    275     private static final HashMap<String, String> sPhoneProjectionMap;
    276     private static final HashMap<String, String> sExtensionProjectionMap;
    277     private static final HashMap<String, String> sGroupProjectionMap;
    278     private static final HashMap<String, String> sGroupMembershipProjectionMap;
    279     private static final HashMap<String, String> sPhotoProjectionMap;
    280 
    281     static {
    282 
    283         // Contacts URI matching table
    284         UriMatcher matcher = sUriMatcher;
    285 
    286         String authority = android.provider.Contacts.AUTHORITY;
    287         matcher.addURI(authority, "extensions", EXTENSIONS);
    288         matcher.addURI(authority, "extensions/#", EXTENSIONS_ID);
    289         matcher.addURI(authority, "groups", GROUPS);
    290         matcher.addURI(authority, "groups/#", GROUPS_ID);
    291         matcher.addURI(authority, "groups/name/*/members", GROUP_NAME_MEMBERS);
    292 //        matcher.addURI(authority, "groups/name/*/members/filter/*",
    293 //                GROUP_NAME_MEMBERS_FILTER);
    294         matcher.addURI(authority, "groups/system_id/*/members", GROUP_SYSTEM_ID_MEMBERS);
    295 //        matcher.addURI(authority, "groups/system_id/*/members/filter/*",
    296 //                GROUP_SYSTEM_ID_MEMBERS_FILTER);
    297         matcher.addURI(authority, "groupmembership", GROUPMEMBERSHIP);
    298         matcher.addURI(authority, "groupmembership/#", GROUPMEMBERSHIP_ID);
    299 //        matcher.addURI(authority, "groupmembershipraw", GROUPMEMBERSHIP_RAW);
    300         matcher.addURI(authority, "people", PEOPLE);
    301 //        matcher.addURI(authority, "people/strequent", PEOPLE_STREQUENT);
    302 //        matcher.addURI(authority, "people/strequent/filter/*", PEOPLE_STREQUENT_FILTER);
    303         matcher.addURI(authority, "people/filter/*", PEOPLE_FILTER);
    304 //        matcher.addURI(authority, "people/with_phones_filter/*",
    305 //                PEOPLE_WITH_PHONES_FILTER);
    306 //        matcher.addURI(authority, "people/with_email_or_im_filter/*",
    307 //                PEOPLE_WITH_EMAIL_OR_IM_FILTER);
    308         matcher.addURI(authority, "people/#", PEOPLE_ID);
    309         matcher.addURI(authority, "people/#/extensions", PEOPLE_EXTENSIONS);
    310         matcher.addURI(authority, "people/#/extensions/#", PEOPLE_EXTENSIONS_ID);
    311         matcher.addURI(authority, "people/#/phones", PEOPLE_PHONES);
    312         matcher.addURI(authority, "people/#/phones/#", PEOPLE_PHONES_ID);
    313 //        matcher.addURI(authority, "people/#/phones_with_presence",
    314 //                PEOPLE_PHONES_WITH_PRESENCE);
    315         matcher.addURI(authority, "people/#/photo", PEOPLE_PHOTO);
    316 //        matcher.addURI(authority, "people/#/photo/data", PEOPLE_PHOTO_DATA);
    317         matcher.addURI(authority, "people/#/contact_methods", PEOPLE_CONTACTMETHODS);
    318 //        matcher.addURI(authority, "people/#/contact_methods_with_presence",
    319 //                PEOPLE_CONTACTMETHODS_WITH_PRESENCE);
    320         matcher.addURI(authority, "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID);
    321         matcher.addURI(authority, "people/#/organizations", PEOPLE_ORGANIZATIONS);
    322         matcher.addURI(authority, "people/#/organizations/#", PEOPLE_ORGANIZATIONS_ID);
    323         matcher.addURI(authority, "people/#/groupmembership", PEOPLE_GROUPMEMBERSHIP);
    324         matcher.addURI(authority, "people/#/groupmembership/#", PEOPLE_GROUPMEMBERSHIP_ID);
    325 //        matcher.addURI(authority, "people/raw", PEOPLE_RAW);
    326 //        matcher.addURI(authority, "people/owner", PEOPLE_OWNER);
    327         matcher.addURI(authority, "people/#/update_contact_time",
    328                 PEOPLE_UPDATE_CONTACT_TIME);
    329         matcher.addURI(authority, "deleted_people", DELETED_PEOPLE);
    330         matcher.addURI(authority, "deleted_groups", DELETED_GROUPS);
    331         matcher.addURI(authority, "phones", PHONES);
    332 //        matcher.addURI(authority, "phones_with_presence", PHONES_WITH_PRESENCE);
    333         matcher.addURI(authority, "phones/filter/*", PHONES_FILTER);
    334 //        matcher.addURI(authority, "phones/filter_name/*", PHONES_FILTER_NAME);
    335 //        matcher.addURI(authority, "phones/mobile_filter_name/*",
    336 //                PHONES_MOBILE_FILTER_NAME);
    337         matcher.addURI(authority, "phones/#", PHONES_ID);
    338         matcher.addURI(authority, "photos", PHOTOS);
    339         matcher.addURI(authority, "photos/#", PHOTOS_ID);
    340         matcher.addURI(authority, "contact_methods", CONTACTMETHODS);
    341         matcher.addURI(authority, "contact_methods/email", CONTACTMETHODS_EMAIL);
    342 //        matcher.addURI(authority, "contact_methods/email/*", CONTACTMETHODS_EMAIL_FILTER);
    343         matcher.addURI(authority, "contact_methods/#", CONTACTMETHODS_ID);
    344 //        matcher.addURI(authority, "contact_methods/with_presence",
    345 //                CONTACTMETHODS_WITH_PRESENCE);
    346         matcher.addURI(authority, "organizations", ORGANIZATIONS);
    347         matcher.addURI(authority, "organizations/#", ORGANIZATIONS_ID);
    348 //        matcher.addURI(authority, "voice_dialer_timestamp", VOICE_DIALER_TIMESTAMP);
    349         matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY,
    350                 SEARCH_SUGGESTIONS);
    351         matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY + "/*",
    352                 SEARCH_SUGGESTIONS);
    353         matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*",
    354                 SEARCH_SHORTCUT);
    355         matcher.addURI(authority, "settings", SETTINGS);
    356 
    357         matcher.addURI(authority, "live_folders/people", LIVE_FOLDERS_PEOPLE);
    358         matcher.addURI(authority, "live_folders/people/*",
    359                 LIVE_FOLDERS_PEOPLE_GROUP_NAME);
    360         matcher.addURI(authority, "live_folders/people_with_phones",
    361                 LIVE_FOLDERS_PEOPLE_WITH_PHONES);
    362         matcher.addURI(authority, "live_folders/favorites",
    363                 LIVE_FOLDERS_PEOPLE_FAVORITES);
    364 
    365 
    366         HashMap<String, String> peopleProjectionMap = new HashMap<String, String>();
    367         peopleProjectionMap.put(People.NAME, People.NAME);
    368         peopleProjectionMap.put(People.DISPLAY_NAME, People.DISPLAY_NAME);
    369         peopleProjectionMap.put(People.PHONETIC_NAME, People.PHONETIC_NAME);
    370         peopleProjectionMap.put(People.NOTES, People.NOTES);
    371         peopleProjectionMap.put(People.TIMES_CONTACTED, People.TIMES_CONTACTED);
    372         peopleProjectionMap.put(People.LAST_TIME_CONTACTED, People.LAST_TIME_CONTACTED);
    373         peopleProjectionMap.put(People.CUSTOM_RINGTONE, People.CUSTOM_RINGTONE);
    374         peopleProjectionMap.put(People.SEND_TO_VOICEMAIL, People.SEND_TO_VOICEMAIL);
    375         peopleProjectionMap.put(People.STARRED, People.STARRED);
    376         peopleProjectionMap.put(People.PRIMARY_ORGANIZATION_ID, People.PRIMARY_ORGANIZATION_ID);
    377         peopleProjectionMap.put(People.PRIMARY_EMAIL_ID, People.PRIMARY_EMAIL_ID);
    378         peopleProjectionMap.put(People.PRIMARY_PHONE_ID, People.PRIMARY_PHONE_ID);
    379 
    380         sPeopleProjectionMap = new HashMap<String, String>(peopleProjectionMap);
    381         sPeopleProjectionMap.put(People._ID, People._ID);
    382         sPeopleProjectionMap.put(People.NUMBER, People.NUMBER);
    383         sPeopleProjectionMap.put(People.TYPE, People.TYPE);
    384         sPeopleProjectionMap.put(People.LABEL, People.LABEL);
    385         sPeopleProjectionMap.put(People.NUMBER_KEY, People.NUMBER_KEY);
    386         sPeopleProjectionMap.put(People.IM_PROTOCOL, IM_PROTOCOL_SQL + " AS " + People.IM_PROTOCOL);
    387         sPeopleProjectionMap.put(People.IM_HANDLE, People.IM_HANDLE);
    388         sPeopleProjectionMap.put(People.IM_ACCOUNT, People.IM_ACCOUNT);
    389         sPeopleProjectionMap.put(People.PRESENCE_STATUS, People.PRESENCE_STATUS);
    390         sPeopleProjectionMap.put(People.PRESENCE_CUSTOM_STATUS,
    391                 "(SELECT " + StatusUpdates.STATUS +
    392                 " FROM " + Tables.STATUS_UPDATES +
    393                 " JOIN " + Tables.DATA +
    394                 "   ON(" + StatusUpdatesColumns.DATA_ID + "=" + DataColumns.CONCRETE_ID + ")" +
    395                 " WHERE " + DataColumns.CONCRETE_RAW_CONTACT_ID + "=people." + People._ID +
    396                 " ORDER BY " + StatusUpdates.STATUS_TIMESTAMP + " DESC " +
    397                 " LIMIT 1" +
    398                 ") AS " + People.PRESENCE_CUSTOM_STATUS);
    399 
    400         sOrganizationProjectionMap = new HashMap<String, String>();
    401         sOrganizationProjectionMap.put(android.provider.Contacts.Organizations._ID,
    402                 android.provider.Contacts.Organizations._ID);
    403         sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.PERSON_ID,
    404                 android.provider.Contacts.Organizations.PERSON_ID);
    405         sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.ISPRIMARY,
    406                 android.provider.Contacts.Organizations.ISPRIMARY);
    407         sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.COMPANY,
    408                 android.provider.Contacts.Organizations.COMPANY);
    409         sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.TYPE,
    410                 android.provider.Contacts.Organizations.TYPE);
    411         sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.LABEL,
    412                 android.provider.Contacts.Organizations.LABEL);
    413         sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.TITLE,
    414                 android.provider.Contacts.Organizations.TITLE);
    415 
    416         sContactMethodProjectionMap = new HashMap<String, String>(peopleProjectionMap);
    417         sContactMethodProjectionMap.put(ContactMethods._ID, ContactMethods._ID);
    418         sContactMethodProjectionMap.put(ContactMethods.PERSON_ID, ContactMethods.PERSON_ID);
    419         sContactMethodProjectionMap.put(ContactMethods.KIND, ContactMethods.KIND);
    420         sContactMethodProjectionMap.put(ContactMethods.ISPRIMARY, ContactMethods.ISPRIMARY);
    421         sContactMethodProjectionMap.put(ContactMethods.TYPE, ContactMethods.TYPE);
    422         sContactMethodProjectionMap.put(ContactMethods.DATA, ContactMethods.DATA);
    423         sContactMethodProjectionMap.put(ContactMethods.LABEL, ContactMethods.LABEL);
    424         sContactMethodProjectionMap.put(ContactMethods.AUX_DATA, ContactMethods.AUX_DATA);
    425 
    426         sPhoneProjectionMap = new HashMap<String, String>(peopleProjectionMap);
    427         sPhoneProjectionMap.put(android.provider.Contacts.Phones._ID,
    428                 android.provider.Contacts.Phones._ID);
    429         sPhoneProjectionMap.put(android.provider.Contacts.Phones.PERSON_ID,
    430                 android.provider.Contacts.Phones.PERSON_ID);
    431         sPhoneProjectionMap.put(android.provider.Contacts.Phones.ISPRIMARY,
    432                 android.provider.Contacts.Phones.ISPRIMARY);
    433         sPhoneProjectionMap.put(android.provider.Contacts.Phones.NUMBER,
    434                 android.provider.Contacts.Phones.NUMBER);
    435         sPhoneProjectionMap.put(android.provider.Contacts.Phones.TYPE,
    436                 android.provider.Contacts.Phones.TYPE);
    437         sPhoneProjectionMap.put(android.provider.Contacts.Phones.LABEL,
    438                 android.provider.Contacts.Phones.LABEL);
    439         sPhoneProjectionMap.put(android.provider.Contacts.Phones.NUMBER_KEY,
    440                 android.provider.Contacts.Phones.NUMBER_KEY);
    441 
    442         sExtensionProjectionMap = new HashMap<String, String>();
    443         sExtensionProjectionMap.put(android.provider.Contacts.Extensions._ID,
    444                 android.provider.Contacts.Extensions._ID);
    445         sExtensionProjectionMap.put(android.provider.Contacts.Extensions.PERSON_ID,
    446                 android.provider.Contacts.Extensions.PERSON_ID);
    447         sExtensionProjectionMap.put(android.provider.Contacts.Extensions.NAME,
    448                 android.provider.Contacts.Extensions.NAME);
    449         sExtensionProjectionMap.put(android.provider.Contacts.Extensions.VALUE,
    450                 android.provider.Contacts.Extensions.VALUE);
    451 
    452         sGroupProjectionMap = new HashMap<String, String>();
    453         sGroupProjectionMap.put(android.provider.Contacts.Groups._ID,
    454                 android.provider.Contacts.Groups._ID);
    455         sGroupProjectionMap.put(android.provider.Contacts.Groups.NAME,
    456                 android.provider.Contacts.Groups.NAME);
    457         sGroupProjectionMap.put(android.provider.Contacts.Groups.NOTES,
    458                 android.provider.Contacts.Groups.NOTES);
    459         sGroupProjectionMap.put(android.provider.Contacts.Groups.SYSTEM_ID,
    460                 android.provider.Contacts.Groups.SYSTEM_ID);
    461 
    462         sGroupMembershipProjectionMap = new HashMap<String, String>(sGroupProjectionMap);
    463         sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership._ID,
    464                 android.provider.Contacts.GroupMembership._ID);
    465         sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership.PERSON_ID,
    466                 android.provider.Contacts.GroupMembership.PERSON_ID);
    467         sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership.GROUP_ID,
    468                 android.provider.Contacts.GroupMembership.GROUP_ID);
    469         sGroupMembershipProjectionMap.put(
    470                 android.provider.Contacts.GroupMembership.GROUP_SYNC_ID,
    471                 android.provider.Contacts.GroupMembership.GROUP_SYNC_ID);
    472         sGroupMembershipProjectionMap.put(
    473                 android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT,
    474                 android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT);
    475         sGroupMembershipProjectionMap.put(
    476                 android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT_TYPE,
    477                 android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT_TYPE);
    478 
    479         sPhotoProjectionMap = new HashMap<String, String>();
    480         sPhotoProjectionMap.put(android.provider.Contacts.Photos._ID,
    481                 android.provider.Contacts.Photos._ID);
    482         sPhotoProjectionMap.put(android.provider.Contacts.Photos.PERSON_ID,
    483                 android.provider.Contacts.Photos.PERSON_ID);
    484         sPhotoProjectionMap.put(android.provider.Contacts.Photos.DATA,
    485                 android.provider.Contacts.Photos.DATA);
    486         sPhotoProjectionMap.put(android.provider.Contacts.Photos.LOCAL_VERSION,
    487                 android.provider.Contacts.Photos.LOCAL_VERSION);
    488         sPhotoProjectionMap.put(android.provider.Contacts.Photos.DOWNLOAD_REQUIRED,
    489                 android.provider.Contacts.Photos.DOWNLOAD_REQUIRED);
    490         sPhotoProjectionMap.put(android.provider.Contacts.Photos.EXISTS_ON_SERVER,
    491                 android.provider.Contacts.Photos.EXISTS_ON_SERVER);
    492         sPhotoProjectionMap.put(android.provider.Contacts.Photos.SYNC_ERROR,
    493                 android.provider.Contacts.Photos.SYNC_ERROR);
    494     }
    495 
    496     private final Context mContext;
    497     private final ContactsDatabaseHelper mDbHelper;
    498     private final ContactsProvider2 mContactsProvider;
    499     private final NameSplitter mPhoneticNameSplitter;
    500     private final GlobalSearchSupport mGlobalSearchSupport;
    501 
    502     private final SQLiteStatement mDataMimetypeQuery;
    503     private final SQLiteStatement mDataRawContactIdQuery;
    504 
    505     private final ContentValues mValues = new ContentValues();
    506     private final ContentValues mValues2 = new ContentValues();
    507     private final ContentValues mValues3 = new ContentValues();
    508     private boolean mDefaultAccountKnown;
    509     private Account mAccount;
    510 
    511     private long mMimetypeEmail;
    512     private long mMimetypeIm;
    513     private long mMimetypePostal;
    514 
    515 
    516     public LegacyApiSupport(Context context, ContactsDatabaseHelper contactsDatabaseHelper,
    517             ContactsProvider2 contactsProvider, GlobalSearchSupport globalSearchSupport) {
    518         mContext = context;
    519         mContactsProvider = contactsProvider;
    520         mDbHelper = contactsDatabaseHelper;
    521         mGlobalSearchSupport = globalSearchSupport;
    522 
    523         mPhoneticNameSplitter = new NameSplitter("", "", "", context
    524                 .getString(com.android.internal.R.string.common_name_conjunctions), Locale
    525                 .getDefault());
    526 
    527         SQLiteDatabase db = mDbHelper.getReadableDatabase();
    528         mDataMimetypeQuery = db.compileStatement(
    529                 "SELECT " + DataColumns.MIMETYPE_ID +
    530                 " FROM " + Tables.DATA +
    531                 " WHERE " + Data._ID + "=?");
    532 
    533         mDataRawContactIdQuery = db.compileStatement(
    534                 "SELECT " + Data.RAW_CONTACT_ID +
    535                 " FROM " + Tables.DATA +
    536                 " WHERE " + Data._ID + "=?");
    537 
    538         mMimetypeEmail = mDbHelper.getMimeTypeId(Email.CONTENT_ITEM_TYPE);
    539         mMimetypeIm = mDbHelper.getMimeTypeId(Im.CONTENT_ITEM_TYPE);
    540         mMimetypePostal = mDbHelper.getMimeTypeId(StructuredPostal.CONTENT_ITEM_TYPE);
    541     }
    542 
    543     private void ensureDefaultAccount() {
    544         if (!mDefaultAccountKnown) {
    545             mAccount = mContactsProvider.getDefaultAccount();
    546             mDefaultAccountKnown = true;
    547         }
    548     }
    549 
    550     public static void createDatabase(SQLiteDatabase db) {
    551         Log.i(TAG, "Bootstrapping database legacy support");
    552         createViews(db);
    553         createSettingsTable(db);
    554     }
    555 
    556     public static void createViews(SQLiteDatabase db) {
    557 
    558         String peopleColumns = "name." + StructuredName.DISPLAY_NAME
    559                         + " AS " + People.NAME + ", " +
    560                 Tables.RAW_CONTACTS + "." + RawContactsColumns.DISPLAY_NAME
    561                         + " AS " + People.DISPLAY_NAME + ", " +
    562                 PHONETIC_NAME_SQL
    563                         + " AS " + People.PHONETIC_NAME + " , " +
    564                 "note." + Note.NOTE
    565                         + " AS " + People.NOTES + ", " +
    566                 RawContacts.ACCOUNT_NAME + ", " +
    567                 RawContacts.ACCOUNT_TYPE + ", " +
    568                 Tables.RAW_CONTACTS + "." + RawContacts.TIMES_CONTACTED
    569                         + " AS " + People.TIMES_CONTACTED + ", " +
    570                 Tables.RAW_CONTACTS + "." + RawContacts.LAST_TIME_CONTACTED
    571                         + " AS " + People.LAST_TIME_CONTACTED + ", " +
    572                 Tables.RAW_CONTACTS + "." + RawContacts.CUSTOM_RINGTONE
    573                         + " AS " + People.CUSTOM_RINGTONE + ", " +
    574                 Tables.RAW_CONTACTS + "." + RawContacts.SEND_TO_VOICEMAIL
    575                         + " AS " + People.SEND_TO_VOICEMAIL + ", " +
    576                 Tables.RAW_CONTACTS + "." + RawContacts.STARRED
    577                         + " AS " + People.STARRED + ", " +
    578                 "organization." + Data._ID
    579                         + " AS " + People.PRIMARY_ORGANIZATION_ID + ", " +
    580                 "email." + Data._ID
    581                         + " AS " + People.PRIMARY_EMAIL_ID + ", " +
    582                 "phone." + Data._ID
    583                         + " AS " + People.PRIMARY_PHONE_ID + ", " +
    584                 "phone." + Phone.NUMBER
    585                         + " AS " + People.NUMBER + ", " +
    586                 "phone." + Phone.TYPE
    587                         + " AS " + People.TYPE + ", " +
    588                 "phone." + Phone.LABEL
    589                         + " AS " + People.LABEL + ", " +
    590                 "_PHONE_NUMBER_STRIPPED_REVERSED(phone." + Phone.NUMBER + ")"
    591                         + " AS " + People.NUMBER_KEY;
    592 
    593         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.PEOPLE + ";");
    594         db.execSQL("CREATE VIEW " + LegacyTables.PEOPLE + " AS SELECT " +
    595                 RawContactsColumns.CONCRETE_ID
    596                         + " AS " + android.provider.Contacts.People._ID + ", " +
    597                 peopleColumns +
    598                 " FROM " + Tables.RAW_CONTACTS + PEOPLE_JOINS +
    599                 " WHERE " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0;");
    600 
    601         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.ORGANIZATIONS + ";");
    602         db.execSQL("CREATE VIEW " + LegacyTables.ORGANIZATIONS + " AS SELECT " +
    603                 DataColumns.CONCRETE_ID
    604                         + " AS " + android.provider.Contacts.Organizations._ID + ", " +
    605                 Data.RAW_CONTACT_ID
    606                         + " AS " + android.provider.Contacts.Organizations.PERSON_ID + ", " +
    607                 Data.IS_PRIMARY
    608                         + " AS " + android.provider.Contacts.Organizations.ISPRIMARY + ", " +
    609                 RawContacts.ACCOUNT_NAME + ", " +
    610                 RawContacts.ACCOUNT_TYPE + ", " +
    611                 Organization.COMPANY
    612                         + " AS " + android.provider.Contacts.Organizations.COMPANY + ", " +
    613                 Organization.TYPE
    614                         + " AS " + android.provider.Contacts.Organizations.TYPE + ", " +
    615                 Organization.LABEL
    616                         + " AS " + android.provider.Contacts.Organizations.LABEL + ", " +
    617                 Organization.TITLE
    618                         + " AS " + android.provider.Contacts.Organizations.TITLE +
    619                 " FROM " + Tables.DATA_JOIN_MIMETYPE_RAW_CONTACTS +
    620                 " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
    621                         + Organization.CONTENT_ITEM_TYPE + "'"
    622                         + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0" +
    623         ";");
    624 
    625         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.CONTACT_METHODS + ";");
    626         db.execSQL("CREATE VIEW " + LegacyTables.CONTACT_METHODS + " AS SELECT " +
    627                 DataColumns.CONCRETE_ID
    628                         + " AS " + ContactMethods._ID + ", " +
    629                 DataColumns.CONCRETE_RAW_CONTACT_ID
    630                         + " AS " + ContactMethods.PERSON_ID + ", " +
    631                 CONTACT_METHOD_KIND_SQL
    632                         + " AS " + ContactMethods.KIND + ", " +
    633                 DataColumns.CONCRETE_IS_PRIMARY
    634                         + " AS " + ContactMethods.ISPRIMARY + ", " +
    635                 Tables.DATA + "." + Email.TYPE
    636                         + " AS " + ContactMethods.TYPE + ", " +
    637                 CONTACT_METHOD_DATA_SQL
    638                         + " AS " + ContactMethods.DATA + ", " +
    639                 Tables.DATA + "." + Email.LABEL
    640                         + " AS " + ContactMethods.LABEL + ", " +
    641                 DataColumns.CONCRETE_DATA14
    642                         + " AS " + ContactMethods.AUX_DATA + ", " +
    643                 peopleColumns +
    644                 " FROM " + Tables.DATA + DATA_JOINS +
    645                 " WHERE " + ContactMethods.KIND + " IS NOT NULL"
    646                     + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0" +
    647         ";");
    648 
    649 
    650         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.PHONES + ";");
    651         db.execSQL("CREATE VIEW " + LegacyTables.PHONES + " AS SELECT DISTINCT " +
    652                 DataColumns.CONCRETE_ID
    653                         + " AS " + android.provider.Contacts.Phones._ID + ", " +
    654                 DataColumns.CONCRETE_RAW_CONTACT_ID
    655                         + " AS " + android.provider.Contacts.Phones.PERSON_ID + ", " +
    656                 DataColumns.CONCRETE_IS_PRIMARY
    657                         + " AS " + android.provider.Contacts.Phones.ISPRIMARY + ", " +
    658                 Tables.DATA + "." + Phone.NUMBER
    659                         + " AS " + android.provider.Contacts.Phones.NUMBER + ", " +
    660                 Tables.DATA + "." + Phone.TYPE
    661                         + " AS " + android.provider.Contacts.Phones.TYPE + ", " +
    662                 Tables.DATA + "." + Phone.LABEL
    663                         + " AS " + android.provider.Contacts.Phones.LABEL + ", " +
    664                 "_PHONE_NUMBER_STRIPPED_REVERSED(" + Tables.DATA + "." + Phone.NUMBER + ")"
    665                         + " AS " + android.provider.Contacts.Phones.NUMBER_KEY + ", " +
    666                 peopleColumns +
    667                 " FROM " + Tables.DATA
    668                         + " JOIN " + Tables.PHONE_LOOKUP
    669                         + " ON (" + Tables.DATA + "._id = "
    670                                 + Tables.PHONE_LOOKUP + "." + PhoneLookupColumns.DATA_ID + ")"
    671                         + DATA_JOINS +
    672                 " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
    673                         + Phone.CONTENT_ITEM_TYPE + "'"
    674                         + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0" +
    675         ";");
    676 
    677         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.EXTENSIONS + ";");
    678         db.execSQL("CREATE VIEW " + LegacyTables.EXTENSIONS + " AS SELECT " +
    679                 DataColumns.CONCRETE_ID
    680                         + " AS " + android.provider.Contacts.Extensions._ID + ", " +
    681                 DataColumns.CONCRETE_RAW_CONTACT_ID
    682                         + " AS " + android.provider.Contacts.Extensions.PERSON_ID + ", " +
    683                 RawContacts.ACCOUNT_NAME + ", " +
    684                 RawContacts.ACCOUNT_TYPE + ", " +
    685                 ExtensionsColumns.NAME
    686                         + " AS " + android.provider.Contacts.Extensions.NAME + ", " +
    687                 ExtensionsColumns.VALUE
    688                         + " AS " + android.provider.Contacts.Extensions.VALUE +
    689                 " FROM " + Tables.DATA_JOIN_MIMETYPE_RAW_CONTACTS +
    690                 " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
    691                         + android.provider.Contacts.Extensions.CONTENT_ITEM_TYPE + "'"
    692                         + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0" +
    693         ";");
    694 
    695         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.GROUPS + ";");
    696         db.execSQL("CREATE VIEW " + LegacyTables.GROUPS + " AS SELECT " +
    697                 GroupsColumns.CONCRETE_ID + " AS " + android.provider.Contacts.Groups._ID + ", " +
    698                 Groups.ACCOUNT_NAME + ", " +
    699                 Groups.ACCOUNT_TYPE + ", " +
    700                 Groups.TITLE + " AS " + android.provider.Contacts.Groups.NAME + ", " +
    701                 Groups.NOTES + " AS " + android.provider.Contacts.Groups.NOTES + " , " +
    702                 Groups.SYSTEM_ID + " AS " + android.provider.Contacts.Groups.SYSTEM_ID +
    703                 " FROM " + Tables.GROUPS +
    704         ";");
    705 
    706         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.GROUP_MEMBERSHIP + ";");
    707         db.execSQL("CREATE VIEW " + LegacyTables.GROUP_MEMBERSHIP + " AS SELECT " +
    708                 DataColumns.CONCRETE_ID
    709                         + " AS " + android.provider.Contacts.GroupMembership._ID + ", " +
    710                 DataColumns.CONCRETE_RAW_CONTACT_ID
    711                         + " AS " + android.provider.Contacts.GroupMembership.PERSON_ID + ", " +
    712                 Tables.RAW_CONTACTS + "." + RawContacts.ACCOUNT_NAME
    713                         + " AS " +  RawContacts.ACCOUNT_NAME + ", " +
    714                 Tables.RAW_CONTACTS + "." + RawContacts.ACCOUNT_TYPE
    715                         + " AS " +  RawContacts.ACCOUNT_TYPE + ", " +
    716                 GroupMembership.GROUP_ROW_ID
    717                         + " AS " + android.provider.Contacts.GroupMembership.GROUP_ID + ", " +
    718                 Groups.TITLE
    719                         + " AS " + android.provider.Contacts.GroupMembership.NAME + ", " +
    720                 Groups.NOTES
    721                         + " AS " + android.provider.Contacts.GroupMembership.NOTES + ", " +
    722                 Groups.SYSTEM_ID
    723                         + " AS " + android.provider.Contacts.GroupMembership.SYSTEM_ID + ", " +
    724                 GroupsColumns.CONCRETE_SOURCE_ID
    725                         + " AS "
    726                         + android.provider.Contacts.GroupMembership.GROUP_SYNC_ID + ", " +
    727                 GroupsColumns.CONCRETE_ACCOUNT_NAME
    728                         + " AS "
    729                         + android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT + ", " +
    730                 GroupsColumns.CONCRETE_ACCOUNT_TYPE
    731                         + " AS "
    732                         + android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT_TYPE +
    733                 " FROM " + Tables.DATA_JOIN_PACKAGES_MIMETYPES_RAW_CONTACTS_GROUPS +
    734                 " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
    735                         + GroupMembership.CONTENT_ITEM_TYPE + "'"
    736                         + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0" +
    737         ";");
    738 
    739         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.PHOTOS + ";");
    740         db.execSQL("CREATE VIEW " + LegacyTables.PHOTOS + " AS SELECT " +
    741                 DataColumns.CONCRETE_ID
    742                         + " AS " + android.provider.Contacts.Photos._ID + ", " +
    743                 DataColumns.CONCRETE_RAW_CONTACT_ID
    744                         + " AS " + android.provider.Contacts.Photos.PERSON_ID + ", " +
    745                 RawContacts.ACCOUNT_NAME + ", " +
    746                 RawContacts.ACCOUNT_TYPE + ", " +
    747                 Tables.DATA + "." + Photo.PHOTO
    748                         + " AS " + android.provider.Contacts.Photos.DATA + ", " +
    749                 "legacy_photo." + LegacyPhotoData.EXISTS_ON_SERVER
    750                         + " AS " + android.provider.Contacts.Photos.EXISTS_ON_SERVER + ", " +
    751                 "legacy_photo." + LegacyPhotoData.DOWNLOAD_REQUIRED
    752                         + " AS " + android.provider.Contacts.Photos.DOWNLOAD_REQUIRED + ", " +
    753                 "legacy_photo." + LegacyPhotoData.LOCAL_VERSION
    754                         + " AS " + android.provider.Contacts.Photos.LOCAL_VERSION + ", " +
    755                 "legacy_photo." + LegacyPhotoData.SYNC_ERROR
    756                         + " AS " + android.provider.Contacts.Photos.SYNC_ERROR +
    757                 " FROM " + Tables.DATA + DATA_JOINS + LEGACY_PHOTO_JOIN +
    758                 " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
    759                         + Photo.CONTENT_ITEM_TYPE + "'"
    760                         + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0" +
    761         ";");
    762 
    763     }
    764 
    765     public static void createSettingsTable(SQLiteDatabase db) {
    766         db.execSQL("DROP TABLE IF EXISTS " + LegacyTables.SETTINGS + ";");
    767         db.execSQL("CREATE TABLE " + LegacyTables.SETTINGS + " (" +
    768                 android.provider.Contacts.Settings._ID + " INTEGER PRIMARY KEY," +
    769                 android.provider.Contacts.Settings._SYNC_ACCOUNT + " TEXT," +
    770                 android.provider.Contacts.Settings._SYNC_ACCOUNT_TYPE + " TEXT," +
    771                 android.provider.Contacts.Settings.KEY + " STRING NOT NULL," +
    772                 android.provider.Contacts.Settings.VALUE + " STRING " +
    773         ");");
    774     }
    775 
    776     public Uri insert(Uri uri, ContentValues values) {
    777         ensureDefaultAccount();
    778         final int match = sUriMatcher.match(uri);
    779         long id = 0;
    780         switch (match) {
    781             case PEOPLE:
    782                 id = insertPeople(values);
    783                 break;
    784 
    785             case ORGANIZATIONS:
    786                 id = insertOrganization(values);
    787                 break;
    788 
    789             case PEOPLE_CONTACTMETHODS: {
    790                 long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
    791                 id = insertContactMethod(rawContactId, values);
    792                 break;
    793             }
    794 
    795             case CONTACTMETHODS: {
    796                 long rawContactId = getRequiredValue(values, ContactMethods.PERSON_ID);
    797                 id = insertContactMethod(rawContactId, values);
    798                 break;
    799             }
    800 
    801             case PHONES: {
    802                 long rawContactId = getRequiredValue(values,
    803                         android.provider.Contacts.Phones.PERSON_ID);
    804                 id = insertPhone(rawContactId, values);
    805                 break;
    806             }
    807 
    808             case PEOPLE_PHONES: {
    809                 long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
    810                 id = insertPhone(rawContactId, values);
    811                 break;
    812             }
    813 
    814             case EXTENSIONS: {
    815                 long rawContactId = getRequiredValue(values,
    816                         android.provider.Contacts.Extensions.PERSON_ID);
    817                 id = insertExtension(rawContactId, values);
    818                 break;
    819             }
    820 
    821             case GROUPS:
    822                 id = insertGroup(values);
    823                 break;
    824 
    825             case GROUPMEMBERSHIP: {
    826                 long rawContactId = getRequiredValue(values,
    827                         android.provider.Contacts.GroupMembership.PERSON_ID);
    828                 long groupId = getRequiredValue(values,
    829                         android.provider.Contacts.GroupMembership.GROUP_ID);
    830                 id = insertGroupMembership(rawContactId, groupId);
    831                 break;
    832             }
    833 
    834             default:
    835                 throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
    836         }
    837 
    838         if (id < 0) {
    839             return null;
    840         }
    841 
    842         final Uri result = ContentUris.withAppendedId(uri, id);
    843         onChange(result);
    844         return result;
    845     }
    846 
    847     private long getRequiredValue(ContentValues values, String column) {
    848         if (!values.containsKey(column)) {
    849             throw new RuntimeException("Required value: " + column);
    850         }
    851 
    852         return values.getAsLong(column);
    853     }
    854 
    855     private long insertPeople(ContentValues values) {
    856         parsePeopleValues(values);
    857 
    858         Uri contactUri = mContactsProvider.insertInTransaction(RawContacts.CONTENT_URI, mValues);
    859         long rawContactId = ContentUris.parseId(contactUri);
    860 
    861         if (mValues2.size() != 0) {
    862             mValues2.put(Data.RAW_CONTACT_ID, rawContactId);
    863             mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues2);
    864         }
    865         if (mValues3.size() != 0) {
    866             mValues3.put(Data.RAW_CONTACT_ID, rawContactId);
    867             mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues3);
    868         }
    869 
    870         return rawContactId;
    871     }
    872 
    873     private long insertOrganization(ContentValues values) {
    874         parseOrganizationValues(values);
    875         ContactsDatabaseHelper.copyLongValue(mValues, Data.RAW_CONTACT_ID,
    876                 values, android.provider.Contacts.Organizations.PERSON_ID);
    877 
    878         Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
    879 
    880         return ContentUris.parseId(uri);
    881     }
    882 
    883     private long insertPhone(long rawContactId, ContentValues values) {
    884         parsePhoneValues(values);
    885         mValues.put(Data.RAW_CONTACT_ID, rawContactId);
    886 
    887         Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
    888 
    889         return ContentUris.parseId(uri);
    890     }
    891 
    892     private long insertContactMethod(long rawContactId, ContentValues values) {
    893         Integer kind = values.getAsInteger(ContactMethods.KIND);
    894         if (kind == null) {
    895             throw new RuntimeException("Required value: " + ContactMethods.KIND);
    896         }
    897 
    898         parseContactMethodValues(kind, values);
    899 
    900         mValues.put(Data.RAW_CONTACT_ID, rawContactId);
    901         Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
    902         return ContentUris.parseId(uri);
    903     }
    904 
    905     private long insertExtension(long rawContactId, ContentValues values) {
    906         mValues.clear();
    907 
    908         mValues.put(Data.RAW_CONTACT_ID, rawContactId);
    909         mValues.put(Data.MIMETYPE, android.provider.Contacts.Extensions.CONTENT_ITEM_TYPE);
    910 
    911         parseExtensionValues(values);
    912 
    913         Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
    914         return ContentUris.parseId(uri);
    915     }
    916 
    917     private long insertGroup(ContentValues values) {
    918         parseGroupValues(values);
    919 
    920         if (mAccount != null) {
    921             mValues.put(Groups.ACCOUNT_NAME, mAccount.name);
    922             mValues.put(Groups.ACCOUNT_TYPE, mAccount.type);
    923         }
    924 
    925         Uri uri = mContactsProvider.insertInTransaction(Groups.CONTENT_URI, mValues);
    926         return ContentUris.parseId(uri);
    927     }
    928 
    929     private long insertGroupMembership(long rawContactId, long groupId) {
    930         mValues.clear();
    931 
    932         mValues.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
    933         mValues.put(GroupMembership.RAW_CONTACT_ID, rawContactId);
    934         mValues.put(GroupMembership.GROUP_ROW_ID, groupId);
    935 
    936         Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
    937         return ContentUris.parseId(uri);
    938     }
    939 
    940     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    941         ensureDefaultAccount();
    942 
    943         int match = sUriMatcher.match(uri);
    944         int count = 0;
    945         switch(match) {
    946             case PEOPLE_UPDATE_CONTACT_TIME: {
    947                 count = updateContactTime(uri, values);
    948                 break;
    949             }
    950 
    951             case PEOPLE_PHOTO: {
    952                 long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
    953                 return updatePhoto(rawContactId, values);
    954             }
    955 
    956             case SETTINGS: {
    957                 return updateSettings(values);
    958             }
    959 
    960             case GROUPMEMBERSHIP:
    961             case GROUPMEMBERSHIP_ID:
    962             case -1: {
    963                 throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
    964             }
    965 
    966             default: {
    967                 count = updateAll(uri, match, values, selection, selectionArgs);
    968             }
    969         }
    970 
    971         if (count > 0) {
    972             mContext.getContentResolver().notifyChange(uri, null);
    973         }
    974 
    975         return count;
    976     }
    977 
    978     private int updateAll(Uri uri, final int match, ContentValues values, String selection,
    979             String[] selectionArgs) {
    980         Cursor c = query(uri, IdQuery.COLUMNS, selection, selectionArgs, null, null);
    981         if (c == null) {
    982             return 0;
    983         }
    984 
    985         int count = 0;
    986         try {
    987             while (c.moveToNext()) {
    988                 long id = c.getLong(IdQuery._ID);
    989                 count += update(match, id, values);
    990             }
    991         } finally {
    992             c.close();
    993         }
    994 
    995         return count;
    996     }
    997 
    998     public int update(int match, long id, ContentValues values) {
    999         int count = 0;
   1000         switch(match) {
   1001             case PEOPLE:
   1002             case PEOPLE_ID: {
   1003                 count = updatePeople(id, values);
   1004                 break;
   1005             }
   1006 
   1007             case ORGANIZATIONS:
   1008             case ORGANIZATIONS_ID: {
   1009                 count = updateOrganizations(id, values);
   1010                 break;
   1011             }
   1012 
   1013             case PHONES:
   1014             case PHONES_ID: {
   1015                 count = updatePhones(id, values);
   1016                 break;
   1017             }
   1018 
   1019             case CONTACTMETHODS:
   1020             case CONTACTMETHODS_ID: {
   1021                 count = updateContactMethods(id, values);
   1022                 break;
   1023             }
   1024 
   1025             case EXTENSIONS:
   1026             case EXTENSIONS_ID: {
   1027                 count = updateExtensions(id, values);
   1028                 break;
   1029             }
   1030 
   1031             case GROUPS:
   1032             case GROUPS_ID: {
   1033                 count = updateGroups(id, values);
   1034                 break;
   1035             }
   1036 
   1037             case PHOTOS:
   1038             case PHOTOS_ID:
   1039                 count = updatePhotoByDataId(id, values);
   1040                 break;
   1041         }
   1042 
   1043         return count;
   1044     }
   1045 
   1046     private int updatePeople(long rawContactId, ContentValues values) {
   1047         parsePeopleValues(values);
   1048 
   1049         int count = mContactsProvider.updateInTransaction(RawContacts.CONTENT_URI,
   1050                 mValues, RawContacts._ID + "=" + rawContactId, null);
   1051 
   1052         if (count == 0) {
   1053             return 0;
   1054         }
   1055 
   1056         if (mValues2.size() != 0) {
   1057             Uri dataUri = findFirstDataRow(rawContactId, StructuredName.CONTENT_ITEM_TYPE);
   1058             if (dataUri != null) {
   1059                 mContactsProvider.updateInTransaction(dataUri, mValues2, null, null);
   1060             } else {
   1061                 mValues2.put(Data.RAW_CONTACT_ID, rawContactId);
   1062                 mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues2);
   1063             }
   1064         }
   1065 
   1066         if (mValues3.size() != 0) {
   1067             Uri dataUri = findFirstDataRow(rawContactId, Note.CONTENT_ITEM_TYPE);
   1068             if (dataUri != null) {
   1069                 mContactsProvider.updateInTransaction(dataUri, mValues3, null, null);
   1070             } else {
   1071                 mValues3.put(Data.RAW_CONTACT_ID, rawContactId);
   1072                 mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues3);
   1073             }
   1074         }
   1075 
   1076         if (values.containsKey(People.LAST_TIME_CONTACTED) &&
   1077                 !values.containsKey(People.TIMES_CONTACTED)) {
   1078             updateContactTime(rawContactId, values);
   1079         }
   1080 
   1081         return count;
   1082     }
   1083 
   1084     private int updateOrganizations(long dataId, ContentValues values) {
   1085         parseOrganizationValues(values);
   1086 
   1087         return mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
   1088                 Data._ID + "=" + dataId, null);
   1089     }
   1090 
   1091     private int updatePhones(long dataId, ContentValues values) {
   1092         parsePhoneValues(values);
   1093 
   1094         return mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
   1095                 Data._ID + "=" + dataId, null);
   1096     }
   1097 
   1098     private int updateContactMethods(long dataId, ContentValues values) {
   1099         int kind;
   1100 
   1101         mDataMimetypeQuery.bindLong(1, dataId);
   1102         long mimetype_id;
   1103         try {
   1104             mimetype_id = mDataMimetypeQuery.simpleQueryForLong();
   1105         } catch (SQLiteDoneException e) {
   1106             // Data row not found
   1107             return 0;
   1108         }
   1109 
   1110         if (mimetype_id == mMimetypeEmail) {
   1111             kind = android.provider.Contacts.KIND_EMAIL;
   1112         } else if (mimetype_id == mMimetypeIm) {
   1113             kind = android.provider.Contacts.KIND_IM;
   1114         } else if (mimetype_id == mMimetypePostal) {
   1115             kind = android.provider.Contacts.KIND_POSTAL;
   1116         } else {
   1117 
   1118             // Non-legacy kind: return "Not found"
   1119             return 0;
   1120         }
   1121 
   1122         parseContactMethodValues(kind, values);
   1123 
   1124         return mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
   1125                 Data._ID + "=" + dataId, null);
   1126     }
   1127 
   1128     private int updateExtensions(long dataId, ContentValues values) {
   1129         parseExtensionValues(values);
   1130 
   1131         return mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
   1132                 Data._ID + "=" + dataId, null);
   1133     }
   1134 
   1135     private int updateGroups(long groupId, ContentValues values) {
   1136         parseGroupValues(values);
   1137 
   1138         return mContactsProvider.updateInTransaction(Groups.CONTENT_URI, mValues,
   1139                 Groups._ID + "=" + groupId, null);
   1140     }
   1141 
   1142     private int updateContactTime(Uri uri, ContentValues values) {
   1143         long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
   1144         updateContactTime(rawContactId, values);
   1145         return 1;
   1146     }
   1147 
   1148     private void updateContactTime(long rawContactId, ContentValues values) {
   1149         long lastTimeContacted;
   1150         if (values.containsKey(People.LAST_TIME_CONTACTED)) {
   1151             lastTimeContacted = values.getAsLong(People.LAST_TIME_CONTACTED);
   1152         } else {
   1153             lastTimeContacted = System.currentTimeMillis();
   1154         }
   1155 
   1156         // TODO check sanctions
   1157         long contactId = mDbHelper.getContactId(rawContactId);
   1158         SQLiteDatabase mDb = mDbHelper.getWritableDatabase();
   1159         mSelectionArgs2[0] = String.valueOf(lastTimeContacted);
   1160         if (contactId != 0) {
   1161             mSelectionArgs2[1] = String.valueOf(contactId);
   1162             mDb.execSQL(CONTACTS_UPDATE_LASTTIMECONTACTED, mSelectionArgs2);
   1163             // increment times_contacted column
   1164             mSelectionArgs1[0] = String.valueOf(contactId);
   1165             mDb.execSQL(ContactsProvider2.UPDATE_TIMES_CONTACTED_CONTACTS_TABLE, mSelectionArgs1);
   1166         }
   1167         mSelectionArgs2[1] = String.valueOf(rawContactId);
   1168         mDb.execSQL(RAWCONTACTS_UPDATE_LASTTIMECONTACTED, mSelectionArgs2);
   1169         // increment times_contacted column
   1170         mSelectionArgs1[0] = String.valueOf(contactId);
   1171         mDb.execSQL(ContactsProvider2.UPDATE_TIMES_CONTACTED_RAWCONTACTS_TABLE, mSelectionArgs1);
   1172     }
   1173 
   1174     private int updatePhoto(long rawContactId, ContentValues values) {
   1175 
   1176         // TODO check sanctions
   1177 
   1178         int count;
   1179 
   1180         long dataId = findFirstDataId(rawContactId, Photo.CONTENT_ITEM_TYPE);
   1181 
   1182         mValues.clear();
   1183         byte[] bytes = values.getAsByteArray(android.provider.Contacts.Photos.DATA);
   1184         mValues.put(Photo.PHOTO, bytes);
   1185 
   1186         if (dataId == -1) {
   1187             mValues.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
   1188             mValues.put(Data.RAW_CONTACT_ID, rawContactId);
   1189             Uri dataUri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
   1190             dataId = ContentUris.parseId(dataUri);
   1191             count = 1;
   1192         } else {
   1193             Uri dataUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
   1194             count = mContactsProvider.updateInTransaction(dataUri, mValues, null, null);
   1195         }
   1196 
   1197         updateLegacyPhotoData(rawContactId, dataId, values);
   1198 
   1199         return count;
   1200     }
   1201 
   1202     private int updatePhotoByDataId(long dataId, ContentValues values) {
   1203 
   1204         mDataRawContactIdQuery.bindLong(1, dataId);
   1205         long rawContactId;
   1206 
   1207         try {
   1208             rawContactId = mDataRawContactIdQuery.simpleQueryForLong();
   1209         } catch (SQLiteDoneException e) {
   1210             // Data row not found
   1211             return 0;
   1212         }
   1213 
   1214         if (values.containsKey(android.provider.Contacts.Photos.DATA)) {
   1215             byte[] bytes = values.getAsByteArray(android.provider.Contacts.Photos.DATA);
   1216             mValues.clear();
   1217             mValues.put(Photo.PHOTO, bytes);
   1218             mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
   1219                     Data._ID + "=" + dataId, null);
   1220         }
   1221 
   1222         updateLegacyPhotoData(rawContactId, dataId, values);
   1223 
   1224         return 1;
   1225     }
   1226 
   1227     private void updateLegacyPhotoData(long rawContactId, long dataId, ContentValues values) {
   1228         mValues.clear();
   1229         ContactsDatabaseHelper.copyStringValue(mValues, LegacyPhotoData.LOCAL_VERSION,
   1230                 values, android.provider.Contacts.Photos.LOCAL_VERSION);
   1231         ContactsDatabaseHelper.copyStringValue(mValues, LegacyPhotoData.DOWNLOAD_REQUIRED,
   1232                 values, android.provider.Contacts.Photos.DOWNLOAD_REQUIRED);
   1233         ContactsDatabaseHelper.copyStringValue(mValues, LegacyPhotoData.EXISTS_ON_SERVER,
   1234                 values, android.provider.Contacts.Photos.EXISTS_ON_SERVER);
   1235         ContactsDatabaseHelper.copyStringValue(mValues, LegacyPhotoData.SYNC_ERROR,
   1236                 values, android.provider.Contacts.Photos.SYNC_ERROR);
   1237 
   1238         int updated = mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
   1239                 Data.MIMETYPE + "='" + LegacyPhotoData.CONTENT_ITEM_TYPE + "'"
   1240                         + " AND " + Data.RAW_CONTACT_ID + "=" + rawContactId
   1241                         + " AND " + LegacyPhotoData.PHOTO_DATA_ID + "=" + dataId, null);
   1242         if (updated == 0) {
   1243             mValues.put(Data.RAW_CONTACT_ID, rawContactId);
   1244             mValues.put(Data.MIMETYPE, LegacyPhotoData.CONTENT_ITEM_TYPE);
   1245             mValues.put(LegacyPhotoData.PHOTO_DATA_ID, dataId);
   1246             mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
   1247         }
   1248     }
   1249 
   1250     private int updateSettings(ContentValues values) {
   1251         SQLiteDatabase db = mDbHelper.getWritableDatabase();
   1252         String accountName = values.getAsString(android.provider.Contacts.Settings._SYNC_ACCOUNT);
   1253         String accountType =
   1254                 values.getAsString(android.provider.Contacts.Settings._SYNC_ACCOUNT_TYPE);
   1255         String key = values.getAsString(android.provider.Contacts.Settings.KEY);
   1256         if (key == null) {
   1257             throw new IllegalArgumentException("you must specify the key when updating settings");
   1258         }
   1259         updateSetting(db, accountName, accountType, values);
   1260         if (key.equals(android.provider.Contacts.Settings.SYNC_EVERYTHING)) {
   1261             mValues.clear();
   1262             mValues.put(Settings.SHOULD_SYNC,
   1263                     values.getAsInteger(android.provider.Contacts.Settings.VALUE));
   1264             String selection;
   1265             String[] selectionArgs;
   1266             if (accountName != null && accountType != null) {
   1267 
   1268                 selectionArgs = new String[]{accountName, accountType};
   1269                 selection = Settings.ACCOUNT_NAME + "=?"
   1270                         + " AND " + Settings.ACCOUNT_TYPE + "=?"
   1271                         + " AND " + Settings.DATA_SET + " IS NULL";
   1272             } else {
   1273                 selectionArgs = null;
   1274                 selection = Settings.ACCOUNT_NAME + " IS NULL"
   1275                         + " AND " + Settings.ACCOUNT_TYPE + " IS NULL"
   1276                         + " AND " + Settings.DATA_SET + " IS NULL";
   1277             }
   1278             int count = mContactsProvider.updateInTransaction(Settings.CONTENT_URI, mValues,
   1279                     selection, selectionArgs);
   1280             if (count == 0) {
   1281                 mValues.put(Settings.ACCOUNT_NAME, accountName);
   1282                 mValues.put(Settings.ACCOUNT_TYPE, accountType);
   1283                 mContactsProvider.insertInTransaction(Settings.CONTENT_URI, mValues);
   1284             }
   1285         }
   1286         return 1;
   1287     }
   1288 
   1289     private void updateSetting(SQLiteDatabase db, String accountName, String accountType,
   1290             ContentValues values) {
   1291         final String key = values.getAsString(android.provider.Contacts.Settings.KEY);
   1292         if (accountName == null || accountType == null) {
   1293             db.delete(LegacyTables.SETTINGS, "_sync_account IS NULL AND key=?", new String[]{key});
   1294         } else {
   1295             db.delete(LegacyTables.SETTINGS, "_sync_account=? AND _sync_account_type=? AND key=?",
   1296                     new String[]{accountName, accountType, key});
   1297         }
   1298         long rowId = db.insert(LegacyTables.SETTINGS,
   1299                 android.provider.Contacts.Settings.KEY, values);
   1300         if (rowId < 0) {
   1301             throw new SQLException("error updating settings with " + values);
   1302         }
   1303     }
   1304 
   1305     private interface SettingsMatchQuery {
   1306         String SQL =
   1307             "SELECT "
   1308                     + ContactsContract.Settings.ACCOUNT_NAME + ","
   1309                     + ContactsContract.Settings.ACCOUNT_TYPE + ","
   1310                     + ContactsContract.Settings.SHOULD_SYNC +
   1311             " FROM " + Tables.SETTINGS + " LEFT OUTER JOIN " + LegacyTables.SETTINGS +
   1312             " ON (" + ContactsContract.Settings.ACCOUNT_NAME + "="
   1313                               + android.provider.Contacts.Settings._SYNC_ACCOUNT +
   1314                       " AND " + ContactsContract.Settings.ACCOUNT_TYPE + "="
   1315                               + android.provider.Contacts.Settings._SYNC_ACCOUNT_TYPE +
   1316                       " AND " + ContactsContract.Settings.DATA_SET + " IS NULL" +
   1317                       " AND " + android.provider.Contacts.Settings.KEY + "='"
   1318                               + android.provider.Contacts.Settings.SYNC_EVERYTHING + "'" +
   1319             ")" +
   1320             " WHERE " + ContactsContract.Settings.SHOULD_SYNC + "<>"
   1321                             + android.provider.Contacts.Settings.VALUE;
   1322 
   1323         int ACCOUNT_NAME = 0;
   1324         int ACCOUNT_TYPE = 1;
   1325         int SHOULD_SYNC = 2;
   1326     }
   1327 
   1328     /**
   1329      * Brings legacy settings table in sync with the new settings.
   1330      */
   1331     public void copySettingsToLegacySettings() {
   1332         SQLiteDatabase db = mDbHelper.getWritableDatabase();
   1333         Cursor cursor = db.rawQuery(SettingsMatchQuery.SQL, null);
   1334         try {
   1335             while(cursor.moveToNext()) {
   1336                 String accountName = cursor.getString(SettingsMatchQuery.ACCOUNT_NAME);
   1337                 String accountType = cursor.getString(SettingsMatchQuery.ACCOUNT_TYPE);
   1338                 String value = cursor.getString(SettingsMatchQuery.SHOULD_SYNC);
   1339                 mValues.clear();
   1340                 mValues.put(android.provider.Contacts.Settings._SYNC_ACCOUNT, accountName);
   1341                 mValues.put(android.provider.Contacts.Settings._SYNC_ACCOUNT_TYPE, accountType);
   1342                 mValues.put(android.provider.Contacts.Settings.KEY,
   1343                         android.provider.Contacts.Settings.SYNC_EVERYTHING);
   1344                 mValues.put(android.provider.Contacts.Settings.VALUE, value);
   1345                 updateSetting(db, accountName, accountType, mValues);
   1346             }
   1347         } finally {
   1348             cursor.close();
   1349         }
   1350     }
   1351 
   1352     private void parsePeopleValues(ContentValues values) {
   1353         mValues.clear();
   1354         mValues2.clear();
   1355         mValues3.clear();
   1356 
   1357         ContactsDatabaseHelper.copyStringValue(mValues, RawContacts.CUSTOM_RINGTONE,
   1358                 values, People.CUSTOM_RINGTONE);
   1359         ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.SEND_TO_VOICEMAIL,
   1360                 values, People.SEND_TO_VOICEMAIL);
   1361         ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.LAST_TIME_CONTACTED,
   1362                 values, People.LAST_TIME_CONTACTED);
   1363         ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.TIMES_CONTACTED,
   1364                 values, People.TIMES_CONTACTED);
   1365         ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.STARRED,
   1366                 values, People.STARRED);
   1367         if (mAccount != null) {
   1368             mValues.put(RawContacts.ACCOUNT_NAME, mAccount.name);
   1369             mValues.put(RawContacts.ACCOUNT_TYPE, mAccount.type);
   1370         }
   1371 
   1372         if (values.containsKey(People.NAME) || values.containsKey(People.PHONETIC_NAME)) {
   1373             mValues2.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
   1374             ContactsDatabaseHelper.copyStringValue(mValues2, StructuredName.DISPLAY_NAME,
   1375                     values, People.NAME);
   1376             if (values.containsKey(People.PHONETIC_NAME)) {
   1377                 String phoneticName = values.getAsString(People.PHONETIC_NAME);
   1378                 NameSplitter.Name parsedName = new NameSplitter.Name();
   1379                 mPhoneticNameSplitter.split(parsedName, phoneticName);
   1380                 mValues2.put(StructuredName.PHONETIC_GIVEN_NAME, parsedName.getGivenNames());
   1381                 mValues2.put(StructuredName.PHONETIC_MIDDLE_NAME, parsedName.getMiddleName());
   1382                 mValues2.put(StructuredName.PHONETIC_FAMILY_NAME, parsedName.getFamilyName());
   1383             }
   1384         }
   1385 
   1386         if (values.containsKey(People.NOTES)) {
   1387             mValues3.put(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
   1388             ContactsDatabaseHelper.copyStringValue(mValues3, Note.NOTE, values, People.NOTES);
   1389         }
   1390     }
   1391 
   1392     private void parseOrganizationValues(ContentValues values) {
   1393         mValues.clear();
   1394 
   1395         mValues.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
   1396 
   1397         ContactsDatabaseHelper.copyLongValue(mValues, Data.IS_PRIMARY,
   1398                 values, android.provider.Contacts.Organizations.ISPRIMARY);
   1399 
   1400         ContactsDatabaseHelper.copyStringValue(mValues, Organization.COMPANY,
   1401                 values, android.provider.Contacts.Organizations.COMPANY);
   1402 
   1403         // TYPE values happen to remain the same between V1 and V2 - can just copy the value
   1404         ContactsDatabaseHelper.copyLongValue(mValues, Organization.TYPE,
   1405                 values, android.provider.Contacts.Organizations.TYPE);
   1406 
   1407         ContactsDatabaseHelper.copyStringValue(mValues, Organization.LABEL,
   1408                 values, android.provider.Contacts.Organizations.LABEL);
   1409         ContactsDatabaseHelper.copyStringValue(mValues, Organization.TITLE,
   1410                 values, android.provider.Contacts.Organizations.TITLE);
   1411     }
   1412 
   1413     private void parsePhoneValues(ContentValues values) {
   1414         mValues.clear();
   1415 
   1416         mValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
   1417 
   1418         ContactsDatabaseHelper.copyLongValue(mValues, Data.IS_PRIMARY,
   1419                 values, android.provider.Contacts.Phones.ISPRIMARY);
   1420 
   1421         ContactsDatabaseHelper.copyStringValue(mValues, Phone.NUMBER,
   1422                 values, android.provider.Contacts.Phones.NUMBER);
   1423 
   1424         // TYPE values happen to remain the same between V1 and V2 - can just copy the value
   1425         ContactsDatabaseHelper.copyLongValue(mValues, Phone.TYPE,
   1426                 values, android.provider.Contacts.Phones.TYPE);
   1427 
   1428         ContactsDatabaseHelper.copyStringValue(mValues, Phone.LABEL,
   1429                 values, android.provider.Contacts.Phones.LABEL);
   1430     }
   1431 
   1432     private void parseContactMethodValues(int kind, ContentValues values) {
   1433         mValues.clear();
   1434 
   1435         ContactsDatabaseHelper.copyLongValue(mValues, Data.IS_PRIMARY, values,
   1436                 ContactMethods.ISPRIMARY);
   1437 
   1438         switch (kind) {
   1439             case android.provider.Contacts.KIND_EMAIL: {
   1440                 copyCommonFields(values, Email.CONTENT_ITEM_TYPE, Email.TYPE, Email.LABEL,
   1441                         Data.DATA14);
   1442                 ContactsDatabaseHelper.copyStringValue(mValues, Email.DATA, values,
   1443                         ContactMethods.DATA);
   1444                 break;
   1445             }
   1446 
   1447             case android.provider.Contacts.KIND_IM: {
   1448                 String protocol = values.getAsString(ContactMethods.DATA);
   1449                 if (protocol.startsWith("pre:")) {
   1450                     mValues.put(Im.PROTOCOL, Integer.parseInt(protocol.substring(4)));
   1451                 } else if (protocol.startsWith("custom:")) {
   1452                     mValues.put(Im.PROTOCOL, Im.PROTOCOL_CUSTOM);
   1453                     mValues.put(Im.CUSTOM_PROTOCOL, protocol.substring(7));
   1454                 }
   1455 
   1456                 copyCommonFields(values, Im.CONTENT_ITEM_TYPE, Im.TYPE, Im.LABEL, Data.DATA14);
   1457                 break;
   1458             }
   1459 
   1460             case android.provider.Contacts.KIND_POSTAL: {
   1461                 copyCommonFields(values, StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.TYPE,
   1462                         StructuredPostal.LABEL, Data.DATA14);
   1463                 ContactsDatabaseHelper.copyStringValue(mValues, StructuredPostal.FORMATTED_ADDRESS,
   1464                         values, ContactMethods.DATA);
   1465                 break;
   1466             }
   1467         }
   1468     }
   1469 
   1470     private void copyCommonFields(ContentValues values, String mimeType, String typeColumn,
   1471             String labelColumn, String auxDataColumn) {
   1472         mValues.put(Data.MIMETYPE, mimeType);
   1473         ContactsDatabaseHelper.copyLongValue(mValues, typeColumn, values,
   1474                 ContactMethods.TYPE);
   1475         ContactsDatabaseHelper.copyStringValue(mValues, labelColumn, values,
   1476                 ContactMethods.LABEL);
   1477         ContactsDatabaseHelper.copyStringValue(mValues, auxDataColumn, values,
   1478                 ContactMethods.AUX_DATA);
   1479     }
   1480 
   1481     private void parseGroupValues(ContentValues values) {
   1482         mValues.clear();
   1483 
   1484         ContactsDatabaseHelper.copyStringValue(mValues, Groups.TITLE,
   1485                 values, android.provider.Contacts.Groups.NAME);
   1486         ContactsDatabaseHelper.copyStringValue(mValues, Groups.NOTES,
   1487                 values, android.provider.Contacts.Groups.NOTES);
   1488         ContactsDatabaseHelper.copyStringValue(mValues, Groups.SYSTEM_ID,
   1489                 values, android.provider.Contacts.Groups.SYSTEM_ID);
   1490     }
   1491 
   1492     private void parseExtensionValues(ContentValues values) {
   1493         ContactsDatabaseHelper.copyStringValue(mValues, ExtensionsColumns.NAME,
   1494                 values, android.provider.Contacts.People.Extensions.NAME);
   1495         ContactsDatabaseHelper.copyStringValue(mValues, ExtensionsColumns.VALUE,
   1496                 values, android.provider.Contacts.People.Extensions.VALUE);
   1497     }
   1498 
   1499     private Uri findFirstDataRow(long rawContactId, String contentItemType) {
   1500         long dataId = findFirstDataId(rawContactId, contentItemType);
   1501         if (dataId == -1) {
   1502             return null;
   1503         }
   1504 
   1505         return ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
   1506     }
   1507 
   1508     private long findFirstDataId(long rawContactId, String mimeType) {
   1509         long dataId = -1;
   1510         Cursor c = mContactsProvider.query(Data.CONTENT_URI, IdQuery.COLUMNS,
   1511                 Data.RAW_CONTACT_ID + "=" + rawContactId + " AND "
   1512                         + Data.MIMETYPE + "='" + mimeType + "'",
   1513                 null, null);
   1514         try {
   1515             if (c.moveToFirst()) {
   1516                 dataId = c.getLong(IdQuery._ID);
   1517             }
   1518         } finally {
   1519             c.close();
   1520         }
   1521         return dataId;
   1522     }
   1523 
   1524 
   1525     public int delete(Uri uri, String selection, String[] selectionArgs) {
   1526         final int match = sUriMatcher.match(uri);
   1527         if (match == -1 || match == SETTINGS) {
   1528             throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
   1529         }
   1530 
   1531         Cursor c = query(uri, IdQuery.COLUMNS, selection, selectionArgs, null, null);
   1532         if (c == null) {
   1533             return 0;
   1534         }
   1535 
   1536         int count = 0;
   1537         try {
   1538             while (c.moveToNext()) {
   1539                 long id = c.getLong(IdQuery._ID);
   1540                 count += delete(uri, match, id);
   1541             }
   1542         } finally {
   1543             c.close();
   1544         }
   1545 
   1546         return count;
   1547     }
   1548 
   1549     public int delete(Uri uri, int match, long id) {
   1550         int count = 0;
   1551         switch (match) {
   1552             case PEOPLE:
   1553             case PEOPLE_ID:
   1554                 count = mContactsProvider.deleteRawContact(id, mDbHelper.getContactId(id), false);
   1555                 break;
   1556 
   1557             case PEOPLE_PHOTO:
   1558                 mValues.clear();
   1559                 mValues.putNull(android.provider.Contacts.Photos.DATA);
   1560                 updatePhoto(id, mValues);
   1561                 break;
   1562 
   1563             case ORGANIZATIONS:
   1564             case ORGANIZATIONS_ID:
   1565                 count = mContactsProvider.deleteData(id, ORGANIZATION_MIME_TYPES);
   1566                 break;
   1567 
   1568             case CONTACTMETHODS:
   1569             case CONTACTMETHODS_ID:
   1570                 count = mContactsProvider.deleteData(id, CONTACT_METHOD_MIME_TYPES);
   1571                 break;
   1572 
   1573             case PHONES:
   1574             case PHONES_ID:
   1575                 count = mContactsProvider.deleteData(id, PHONE_MIME_TYPES);
   1576                 break;
   1577 
   1578             case EXTENSIONS:
   1579             case EXTENSIONS_ID:
   1580                 count = mContactsProvider.deleteData(id, EXTENSION_MIME_TYPES);
   1581                 break;
   1582 
   1583             case PHOTOS:
   1584             case PHOTOS_ID:
   1585                 count = mContactsProvider.deleteData(id, PHOTO_MIME_TYPES);
   1586                 break;
   1587 
   1588             case GROUPMEMBERSHIP:
   1589             case GROUPMEMBERSHIP_ID:
   1590                 count = mContactsProvider.deleteData(id, GROUP_MEMBERSHIP_MIME_TYPES);
   1591                 break;
   1592 
   1593             case GROUPS:
   1594             case GROUPS_ID:
   1595                 count = mContactsProvider.deleteGroup(uri, id, false);
   1596                 break;
   1597 
   1598             default:
   1599                 throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
   1600         }
   1601 
   1602         return count;
   1603     }
   1604 
   1605     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
   1606             String sortOrder, String limit) {
   1607         ensureDefaultAccount();
   1608 
   1609         final SQLiteDatabase db = mDbHelper.getReadableDatabase();
   1610         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
   1611         String groupBy = null;
   1612 
   1613         final int match = sUriMatcher.match(uri);
   1614         switch (match) {
   1615             case PEOPLE: {
   1616                 qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
   1617                 qb.setProjectionMap(sPeopleProjectionMap);
   1618                 applyRawContactsAccount(qb);
   1619                 break;
   1620             }
   1621 
   1622             case PEOPLE_ID:
   1623                 qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
   1624                 qb.setProjectionMap(sPeopleProjectionMap);
   1625                 applyRawContactsAccount(qb);
   1626                 qb.appendWhere(" AND " + People._ID + "=");
   1627                 qb.appendWhere(uri.getPathSegments().get(1));
   1628                 break;
   1629 
   1630             case PEOPLE_FILTER: {
   1631                 qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
   1632                 qb.setProjectionMap(sPeopleProjectionMap);
   1633                 applyRawContactsAccount(qb);
   1634                 String filterParam = uri.getPathSegments().get(2);
   1635                 qb.appendWhere(" AND " + People._ID + " IN "
   1636                         + getRawContactsByFilterAsNestedQuery(filterParam));
   1637                 break;
   1638             }
   1639 
   1640             case GROUP_NAME_MEMBERS:
   1641                 qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
   1642                 qb.setProjectionMap(sPeopleProjectionMap);
   1643                 applyRawContactsAccount(qb);
   1644                 String group = uri.getPathSegments().get(2);
   1645                 qb.appendWhere(" AND " + buildGroupNameMatchWhereClause(group));
   1646                 break;
   1647 
   1648             case GROUP_SYSTEM_ID_MEMBERS:
   1649                 qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
   1650                 qb.setProjectionMap(sPeopleProjectionMap);
   1651                 applyRawContactsAccount(qb);
   1652                 String systemId = uri.getPathSegments().get(2);
   1653                 qb.appendWhere(" AND " + buildGroupSystemIdMatchWhereClause(systemId));
   1654                 break;
   1655 
   1656             case ORGANIZATIONS:
   1657                 qb.setTables(LegacyTables.ORGANIZATIONS + " organizations");
   1658                 qb.setProjectionMap(sOrganizationProjectionMap);
   1659                 applyRawContactsAccount(qb);
   1660                 break;
   1661 
   1662             case ORGANIZATIONS_ID:
   1663                 qb.setTables(LegacyTables.ORGANIZATIONS + " organizations");
   1664                 qb.setProjectionMap(sOrganizationProjectionMap);
   1665                 applyRawContactsAccount(qb);
   1666                 qb.appendWhere(" AND " + android.provider.Contacts.Organizations._ID + "=");
   1667                 qb.appendWhere(uri.getPathSegments().get(1));
   1668                 break;
   1669 
   1670             case PEOPLE_ORGANIZATIONS:
   1671                 qb.setTables(LegacyTables.ORGANIZATIONS + " organizations");
   1672                 qb.setProjectionMap(sOrganizationProjectionMap);
   1673                 applyRawContactsAccount(qb);
   1674                 qb.appendWhere(" AND " + android.provider.Contacts.Organizations.PERSON_ID + "=");
   1675                 qb.appendWhere(uri.getPathSegments().get(1));
   1676                 break;
   1677 
   1678             case PEOPLE_ORGANIZATIONS_ID:
   1679                 qb.setTables(LegacyTables.ORGANIZATIONS + " organizations");
   1680                 qb.setProjectionMap(sOrganizationProjectionMap);
   1681                 applyRawContactsAccount(qb);
   1682                 qb.appendWhere(" AND " + android.provider.Contacts.Organizations.PERSON_ID + "=");
   1683                 qb.appendWhere(uri.getPathSegments().get(1));
   1684                 qb.appendWhere(" AND " + android.provider.Contacts.Organizations._ID + "=");
   1685                 qb.appendWhere(uri.getPathSegments().get(3));
   1686                 break;
   1687 
   1688             case CONTACTMETHODS:
   1689                 qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
   1690                 qb.setProjectionMap(sContactMethodProjectionMap);
   1691                 applyRawContactsAccount(qb);
   1692                 break;
   1693 
   1694             case CONTACTMETHODS_ID:
   1695                 qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
   1696                 qb.setProjectionMap(sContactMethodProjectionMap);
   1697                 applyRawContactsAccount(qb);
   1698                 qb.appendWhere(" AND " + ContactMethods._ID + "=");
   1699                 qb.appendWhere(uri.getPathSegments().get(1));
   1700                 break;
   1701 
   1702             case CONTACTMETHODS_EMAIL:
   1703                 qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
   1704                 qb.setProjectionMap(sContactMethodProjectionMap);
   1705                 applyRawContactsAccount(qb);
   1706                 qb.appendWhere(" AND " + ContactMethods.KIND + "="
   1707                         + android.provider.Contacts.KIND_EMAIL);
   1708                 break;
   1709 
   1710             case PEOPLE_CONTACTMETHODS:
   1711                 qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
   1712                 qb.setProjectionMap(sContactMethodProjectionMap);
   1713                 applyRawContactsAccount(qb);
   1714                 qb.appendWhere(" AND " + ContactMethods.PERSON_ID + "=");
   1715                 qb.appendWhere(uri.getPathSegments().get(1));
   1716                 qb.appendWhere(" AND " + ContactMethods.KIND + " IS NOT NULL");
   1717                 break;
   1718 
   1719             case PEOPLE_CONTACTMETHODS_ID:
   1720                 qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
   1721                 qb.setProjectionMap(sContactMethodProjectionMap);
   1722                 applyRawContactsAccount(qb);
   1723                 qb.appendWhere(" AND " + ContactMethods.PERSON_ID + "=");
   1724                 qb.appendWhere(uri.getPathSegments().get(1));
   1725                 qb.appendWhere(" AND " + ContactMethods._ID + "=");
   1726                 qb.appendWhere(uri.getPathSegments().get(3));
   1727                 qb.appendWhere(" AND " + ContactMethods.KIND + " IS NOT NULL");
   1728                 break;
   1729 
   1730             case PHONES:
   1731                 qb.setTables(LegacyTables.PHONES + " phones");
   1732                 qb.setProjectionMap(sPhoneProjectionMap);
   1733                 applyRawContactsAccount(qb);
   1734                 break;
   1735 
   1736             case PHONES_ID:
   1737                 qb.setTables(LegacyTables.PHONES + " phones");
   1738                 qb.setProjectionMap(sPhoneProjectionMap);
   1739                 applyRawContactsAccount(qb);
   1740                 qb.appendWhere(" AND " + android.provider.Contacts.Phones._ID + "=");
   1741                 qb.appendWhere(uri.getPathSegments().get(1));
   1742                 break;
   1743 
   1744             case PHONES_FILTER:
   1745                 qb.setTables(LegacyTables.PHONES + " phones");
   1746                 qb.setProjectionMap(sPhoneProjectionMap);
   1747                 applyRawContactsAccount(qb);
   1748                 if (uri.getPathSegments().size() > 2) {
   1749                     String filterParam = uri.getLastPathSegment();
   1750                     qb.appendWhere(" AND person =");
   1751                     qb.appendWhere(mDbHelper.buildPhoneLookupAsNestedQuery(filterParam));
   1752                     qb.setDistinct(true);
   1753                 }
   1754                 break;
   1755 
   1756             case PEOPLE_PHONES:
   1757                 qb.setTables(LegacyTables.PHONES + " phones");
   1758                 qb.setProjectionMap(sPhoneProjectionMap);
   1759                 applyRawContactsAccount(qb);
   1760                 qb.appendWhere(" AND " + android.provider.Contacts.Phones.PERSON_ID + "=");
   1761                 qb.appendWhere(uri.getPathSegments().get(1));
   1762                 break;
   1763 
   1764             case PEOPLE_PHONES_ID:
   1765                 qb.setTables(LegacyTables.PHONES + " phones");
   1766                 qb.setProjectionMap(sPhoneProjectionMap);
   1767                 applyRawContactsAccount(qb);
   1768                 qb.appendWhere(" AND " + android.provider.Contacts.Phones.PERSON_ID + "=");
   1769                 qb.appendWhere(uri.getPathSegments().get(1));
   1770                 qb.appendWhere(" AND " + android.provider.Contacts.Phones._ID + "=");
   1771                 qb.appendWhere(uri.getPathSegments().get(3));
   1772                 break;
   1773 
   1774             case EXTENSIONS:
   1775                 qb.setTables(LegacyTables.EXTENSIONS + " extensions");
   1776                 qb.setProjectionMap(sExtensionProjectionMap);
   1777                 applyRawContactsAccount(qb);
   1778                 break;
   1779 
   1780             case EXTENSIONS_ID:
   1781                 qb.setTables(LegacyTables.EXTENSIONS + " extensions");
   1782                 qb.setProjectionMap(sExtensionProjectionMap);
   1783                 applyRawContactsAccount(qb);
   1784                 qb.appendWhere(" AND " + android.provider.Contacts.Extensions._ID + "=");
   1785                 qb.appendWhere(uri.getPathSegments().get(1));
   1786                 break;
   1787 
   1788             case PEOPLE_EXTENSIONS:
   1789                 qb.setTables(LegacyTables.EXTENSIONS + " extensions");
   1790                 qb.setProjectionMap(sExtensionProjectionMap);
   1791                 applyRawContactsAccount(qb);
   1792                 qb.appendWhere(" AND " + android.provider.Contacts.Extensions.PERSON_ID + "=");
   1793                 qb.appendWhere(uri.getPathSegments().get(1));
   1794                 break;
   1795 
   1796             case PEOPLE_EXTENSIONS_ID:
   1797                 qb.setTables(LegacyTables.EXTENSIONS + " extensions");
   1798                 qb.setProjectionMap(sExtensionProjectionMap);
   1799                 applyRawContactsAccount(qb);
   1800                 qb.appendWhere(" AND " + android.provider.Contacts.Extensions.PERSON_ID + "=");
   1801                 qb.appendWhere(uri.getPathSegments().get(1));
   1802                 qb.appendWhere(" AND " + android.provider.Contacts.Extensions._ID + "=");
   1803                 qb.appendWhere(uri.getPathSegments().get(3));
   1804                 break;
   1805 
   1806             case GROUPS:
   1807                 qb.setTables(LegacyTables.GROUPS + " groups");
   1808                 qb.setProjectionMap(sGroupProjectionMap);
   1809                 applyGroupAccount(qb);
   1810                 break;
   1811 
   1812             case GROUPS_ID:
   1813                 qb.setTables(LegacyTables.GROUPS + " groups");
   1814                 qb.setProjectionMap(sGroupProjectionMap);
   1815                 applyGroupAccount(qb);
   1816                 qb.appendWhere(" AND " + android.provider.Contacts.Groups._ID + "=");
   1817                 qb.appendWhere(uri.getPathSegments().get(1));
   1818                 break;
   1819 
   1820             case GROUPMEMBERSHIP:
   1821                 qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
   1822                 qb.setProjectionMap(sGroupMembershipProjectionMap);
   1823                 applyRawContactsAccount(qb);
   1824                 break;
   1825 
   1826             case GROUPMEMBERSHIP_ID:
   1827                 qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
   1828                 qb.setProjectionMap(sGroupMembershipProjectionMap);
   1829                 applyRawContactsAccount(qb);
   1830                 qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership._ID + "=");
   1831                 qb.appendWhere(uri.getPathSegments().get(1));
   1832                 break;
   1833 
   1834             case PEOPLE_GROUPMEMBERSHIP:
   1835                 qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
   1836                 qb.setProjectionMap(sGroupMembershipProjectionMap);
   1837                 applyRawContactsAccount(qb);
   1838                 qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership.PERSON_ID + "=");
   1839                 qb.appendWhere(uri.getPathSegments().get(1));
   1840                 break;
   1841 
   1842             case PEOPLE_GROUPMEMBERSHIP_ID:
   1843                 qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
   1844                 qb.setProjectionMap(sGroupMembershipProjectionMap);
   1845                 applyRawContactsAccount(qb);
   1846                 qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership.PERSON_ID + "=");
   1847                 qb.appendWhere(uri.getPathSegments().get(1));
   1848                 qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership._ID + "=");
   1849                 qb.appendWhere(uri.getPathSegments().get(3));
   1850                 break;
   1851 
   1852             case PEOPLE_PHOTO:
   1853                 qb.setTables(LegacyTables.PHOTOS + " photos");
   1854                 qb.setProjectionMap(sPhotoProjectionMap);
   1855                 applyRawContactsAccount(qb);
   1856                 qb.appendWhere(" AND " + android.provider.Contacts.Photos.PERSON_ID + "=");
   1857                 qb.appendWhere(uri.getPathSegments().get(1));
   1858                 limit = "1";
   1859                 break;
   1860 
   1861             case PHOTOS:
   1862                 qb.setTables(LegacyTables.PHOTOS + " photos");
   1863                 qb.setProjectionMap(sPhotoProjectionMap);
   1864                 applyRawContactsAccount(qb);
   1865                 break;
   1866 
   1867             case PHOTOS_ID:
   1868                 qb.setTables(LegacyTables.PHOTOS + " photos");
   1869                 qb.setProjectionMap(sPhotoProjectionMap);
   1870                 applyRawContactsAccount(qb);
   1871                 qb.appendWhere(" AND " + android.provider.Contacts.Photos._ID + "=");
   1872                 qb.appendWhere(uri.getPathSegments().get(1));
   1873                 break;
   1874 
   1875             case SEARCH_SUGGESTIONS:
   1876                 return mGlobalSearchSupport.handleSearchSuggestionsQuery(
   1877                         db, uri, projection, limit);
   1878 
   1879             case SEARCH_SHORTCUT: {
   1880                 String lookupKey = uri.getLastPathSegment();
   1881                 String filter = ContactsProvider2.getQueryParameter(uri, "filter");
   1882                 return mGlobalSearchSupport.handleSearchShortcutRefresh(
   1883                         db, projection, lookupKey, filter);
   1884             }
   1885 
   1886             case LIVE_FOLDERS_PEOPLE:
   1887                 return mContactsProvider.query(LIVE_FOLDERS_CONTACTS_URI,
   1888                         projection, selection, selectionArgs, sortOrder);
   1889 
   1890             case LIVE_FOLDERS_PEOPLE_WITH_PHONES:
   1891                 return mContactsProvider.query(LIVE_FOLDERS_CONTACTS_WITH_PHONES_URI,
   1892                         projection, selection, selectionArgs, sortOrder);
   1893 
   1894             case LIVE_FOLDERS_PEOPLE_FAVORITES:
   1895                 return mContactsProvider.query(LIVE_FOLDERS_CONTACTS_FAVORITES_URI,
   1896                         projection, selection, selectionArgs, sortOrder);
   1897 
   1898             case LIVE_FOLDERS_PEOPLE_GROUP_NAME:
   1899                 return mContactsProvider.query(Uri.withAppendedPath(LIVE_FOLDERS_CONTACTS_URI,
   1900                         Uri.encode(uri.getLastPathSegment())),
   1901                         projection, selection, selectionArgs, sortOrder);
   1902 
   1903             case DELETED_PEOPLE:
   1904             case DELETED_GROUPS:
   1905                 throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
   1906 
   1907             case SETTINGS:
   1908                 copySettingsToLegacySettings();
   1909                 qb.setTables(LegacyTables.SETTINGS);
   1910                 break;
   1911 
   1912             default:
   1913                 throw new IllegalArgumentException(mDbHelper.exceptionMessage(uri));
   1914         }
   1915 
   1916         // Perform the query and set the notification uri
   1917         final Cursor c = qb.query(db, projection, selection, selectionArgs,
   1918                 groupBy, null, sortOrder, limit);
   1919         if (c != null) {
   1920             c.setNotificationUri(mContext.getContentResolver(),
   1921                     android.provider.Contacts.CONTENT_URI);
   1922         }
   1923         return c;
   1924     }
   1925 
   1926     private void applyRawContactsAccount(SQLiteQueryBuilder qb) {
   1927         StringBuilder sb = new StringBuilder();
   1928         appendRawContactsAccount(sb);
   1929         qb.appendWhere(sb.toString());
   1930     }
   1931 
   1932     private void appendRawContactsAccount(StringBuilder sb) {
   1933         if (mAccount != null) {
   1934             sb.append(RawContacts.ACCOUNT_NAME + "=");
   1935             DatabaseUtils.appendEscapedSQLString(sb, mAccount.name);
   1936             sb.append(" AND " + RawContacts.ACCOUNT_TYPE + "=");
   1937             DatabaseUtils.appendEscapedSQLString(sb, mAccount.type);
   1938         } else {
   1939             sb.append(RawContacts.ACCOUNT_NAME + " IS NULL" +
   1940                     " AND " + RawContacts.ACCOUNT_TYPE + " IS NULL");
   1941         }
   1942     }
   1943 
   1944     private void applyGroupAccount(SQLiteQueryBuilder qb) {
   1945         StringBuilder sb = new StringBuilder();
   1946         appendGroupAccount(sb);
   1947         qb.appendWhere(sb.toString());
   1948     }
   1949 
   1950     private void appendGroupAccount(StringBuilder sb) {
   1951         if (mAccount != null) {
   1952             sb.append(Groups.ACCOUNT_NAME + "=");
   1953             DatabaseUtils.appendEscapedSQLString(sb, mAccount.name);
   1954             sb.append(" AND " + Groups.ACCOUNT_TYPE + "=");
   1955             DatabaseUtils.appendEscapedSQLString(sb, mAccount.type);
   1956         } else {
   1957             sb.append(Groups.ACCOUNT_NAME + " IS NULL" +
   1958                     " AND " + Groups.ACCOUNT_TYPE + " IS NULL");
   1959         }
   1960     }
   1961 
   1962     /**
   1963      * Build a WHERE clause that restricts the query to match people that are a member of
   1964      * a group with a particular name. The projection map of the query must include
   1965      * {@link People#_ID}.
   1966      *
   1967      * @param groupName The name of the group
   1968      * @return The where clause.
   1969      */
   1970     private String buildGroupNameMatchWhereClause(String groupName) {
   1971         return "people._id IN "
   1972                 + "(SELECT " + DataColumns.CONCRETE_RAW_CONTACT_ID
   1973                 + " FROM " + Tables.DATA_JOIN_MIMETYPES
   1974                 + " WHERE " + Data.MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
   1975                         + "' AND " + GroupMembership.GROUP_ROW_ID + "="
   1976                                 + "(SELECT " + Tables.GROUPS + "." + Groups._ID
   1977                                 + " FROM " + Tables.GROUPS
   1978                                 + " WHERE " + Groups.TITLE + "="
   1979                                         + DatabaseUtils.sqlEscapeString(groupName) + "))";
   1980     }
   1981 
   1982     /**
   1983      * Build a WHERE clause that restricts the query to match people that are a member of
   1984      * a group with a particular system id. The projection map of the query must include
   1985      * {@link People#_ID}.
   1986      *
   1987      * @param groupName The name of the group
   1988      * @return The where clause.
   1989      */
   1990     private String buildGroupSystemIdMatchWhereClause(String systemId) {
   1991         return "people._id IN "
   1992                 + "(SELECT " + DataColumns.CONCRETE_RAW_CONTACT_ID
   1993                 + " FROM " + Tables.DATA_JOIN_MIMETYPES
   1994                 + " WHERE " + Data.MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
   1995                         + "' AND " + GroupMembership.GROUP_ROW_ID + "="
   1996                                 + "(SELECT " + Tables.GROUPS + "." + Groups._ID
   1997                                 + " FROM " + Tables.GROUPS
   1998                                 + " WHERE " + Groups.SYSTEM_ID + "="
   1999                                         + DatabaseUtils.sqlEscapeString(systemId) + "))";
   2000     }
   2001 
   2002     private String getRawContactsByFilterAsNestedQuery(String filterParam) {
   2003         StringBuilder sb = new StringBuilder();
   2004         String normalizedName = NameNormalizer.normalize(filterParam);
   2005         if (TextUtils.isEmpty(normalizedName)) {
   2006             // Effectively an empty IN clause - SQL syntax does not allow an actual empty list here
   2007             sb.append("(0)");
   2008         } else {
   2009             sb.append("(" +
   2010                     "SELECT " + NameLookupColumns.RAW_CONTACT_ID +
   2011                     " FROM " + Tables.NAME_LOOKUP +
   2012                     " WHERE " + NameLookupColumns.NORMALIZED_NAME +
   2013                     " GLOB '");
   2014             // Should not use a "?" argument placeholder here, because
   2015             // that would prevent the SQL optimizer from using the index on NORMALIZED_NAME.
   2016             sb.append(normalizedName);
   2017             sb.append("*' AND " + NameLookupColumns.NAME_TYPE + " IN ("
   2018                     + NameLookupType.NAME_COLLATION_KEY + ","
   2019                     + NameLookupType.NICKNAME);
   2020             if (true) {
   2021                 sb.append("," + NameLookupType.EMAIL_BASED_NICKNAME);
   2022             }
   2023             sb.append("))");
   2024         }
   2025         return sb.toString();
   2026     }
   2027 
   2028     /**
   2029      * Called when a change has been made.
   2030      *
   2031      * @param uri the uri that the change was made to
   2032      */
   2033     private void onChange(Uri uri) {
   2034         mContext.getContentResolver().notifyChange(android.provider.Contacts.CONTENT_URI, null);
   2035     }
   2036 
   2037     public String getType(Uri uri) {
   2038         int match = sUriMatcher.match(uri);
   2039         switch (match) {
   2040             case EXTENSIONS:
   2041             case PEOPLE_EXTENSIONS:
   2042                 return Extensions.CONTENT_TYPE;
   2043             case EXTENSIONS_ID:
   2044             case PEOPLE_EXTENSIONS_ID:
   2045                 return Extensions.CONTENT_ITEM_TYPE;
   2046             case PEOPLE:
   2047                 return "vnd.android.cursor.dir/person";
   2048             case PEOPLE_ID:
   2049                 return "vnd.android.cursor.item/person";
   2050             case PEOPLE_PHONES:
   2051                 return "vnd.android.cursor.dir/phone";
   2052             case PEOPLE_PHONES_ID:
   2053                 return "vnd.android.cursor.item/phone";
   2054             case PEOPLE_CONTACTMETHODS:
   2055                 return "vnd.android.cursor.dir/contact-methods";
   2056             case PEOPLE_CONTACTMETHODS_ID:
   2057                 return getContactMethodType(uri);
   2058             case PHONES:
   2059                 return "vnd.android.cursor.dir/phone";
   2060             case PHONES_ID:
   2061                 return "vnd.android.cursor.item/phone";
   2062             case PHONES_FILTER:
   2063                 return "vnd.android.cursor.dir/phone";
   2064             case PHOTOS_ID:
   2065                 return "vnd.android.cursor.item/photo";
   2066             case PHOTOS:
   2067                 return "vnd.android.cursor.dir/photo";
   2068             case PEOPLE_PHOTO:
   2069                 return "vnd.android.cursor.item/photo";
   2070             case CONTACTMETHODS:
   2071                 return "vnd.android.cursor.dir/contact-methods";
   2072             case CONTACTMETHODS_ID:
   2073                 return getContactMethodType(uri);
   2074             case ORGANIZATIONS:
   2075                 return "vnd.android.cursor.dir/organizations";
   2076             case ORGANIZATIONS_ID:
   2077                 return "vnd.android.cursor.item/organization";
   2078             case SEARCH_SUGGESTIONS:
   2079                 return SearchManager.SUGGEST_MIME_TYPE;
   2080             case SEARCH_SHORTCUT:
   2081                 return SearchManager.SHORTCUT_MIME_TYPE;
   2082             default:
   2083                 throw new IllegalArgumentException(mDbHelper.exceptionMessage(uri));
   2084         }
   2085     }
   2086 
   2087     private String getContactMethodType(Uri url) {
   2088         String mime = null;
   2089 
   2090         Cursor c = query(url, new String[] {ContactMethods.KIND}, null, null, null, null);
   2091         if (c != null) {
   2092             try {
   2093                 if (c.moveToFirst()) {
   2094                     int kind = c.getInt(0);
   2095                     switch (kind) {
   2096                     case android.provider.Contacts.KIND_EMAIL:
   2097                         mime = "vnd.android.cursor.item/email";
   2098                         break;
   2099 
   2100                     case android.provider.Contacts.KIND_IM:
   2101                         mime = "vnd.android.cursor.item/jabber-im";
   2102                         break;
   2103 
   2104                     case android.provider.Contacts.KIND_POSTAL:
   2105                         mime = "vnd.android.cursor.item/postal-address";
   2106                         break;
   2107                     }
   2108                 }
   2109             } finally {
   2110                 c.close();
   2111             }
   2112         }
   2113         return mime;
   2114     }
   2115 }
   2116