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