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