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