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