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