LegacyApiSupport.java revision 0cc388933e698760d4985a7d7be1325f9c8305e9
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.OpenHelper.DataColumns;
19import com.android.providers.contacts.OpenHelper.ExtensionsColumns;
20import com.android.providers.contacts.OpenHelper.GroupsColumns;
21import com.android.providers.contacts.OpenHelper.MimetypesColumns;
22import com.android.providers.contacts.OpenHelper.PhoneColumns;
23import com.android.providers.contacts.OpenHelper.RawContactsColumns;
24import com.android.providers.contacts.OpenHelper.StatusUpdatesColumns;
25import com.android.providers.contacts.OpenHelper.Tables;
26
27import android.accounts.Account;
28import android.app.SearchManager;
29import android.content.ContentUris;
30import android.content.ContentValues;
31import android.content.Context;
32import android.content.UriMatcher;
33import android.database.Cursor;
34import android.database.DatabaseUtils;
35import android.database.sqlite.SQLiteDatabase;
36import android.database.sqlite.SQLiteDoneException;
37import android.database.sqlite.SQLiteQueryBuilder;
38import android.database.sqlite.SQLiteStatement;
39import android.net.Uri;
40import android.provider.BaseColumns;
41import android.provider.ContactsContract;
42import android.provider.Contacts.ContactMethods;
43import android.provider.Contacts.Extensions;
44import android.provider.Contacts.People;
45import android.provider.ContactsContract.Data;
46import android.provider.ContactsContract.Groups;
47import android.provider.ContactsContract.RawContacts;
48import android.provider.ContactsContract.StatusUpdates;
49import android.provider.ContactsContract.CommonDataKinds.Email;
50import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
51import android.provider.ContactsContract.CommonDataKinds.Im;
52import android.provider.ContactsContract.CommonDataKinds.Note;
53import android.provider.ContactsContract.CommonDataKinds.Organization;
54import android.provider.ContactsContract.CommonDataKinds.Phone;
55import android.provider.ContactsContract.CommonDataKinds.Photo;
56import android.provider.ContactsContract.CommonDataKinds.StructuredName;
57import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
58import android.util.Log;
59
60import java.util.HashMap;
61import java.util.Locale;
62
63public class LegacyApiSupport {
64
65    private static final String TAG = "ContactsProviderV1";
66
67    private static final String NON_EXISTENT_ACCOUNT_TYPE = "android.INVALID_ACCOUNT_TYPE";
68    private static final String NON_EXISTENT_ACCOUNT_NAME = "invalid";
69
70    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
71
72    private static final int PEOPLE = 1;
73    private static final int PEOPLE_ID = 2;
74    private static final int PEOPLE_UPDATE_CONTACT_TIME = 3;
75    private static final int ORGANIZATIONS = 4;
76    private static final int ORGANIZATIONS_ID = 5;
77    private static final int PEOPLE_CONTACTMETHODS = 6;
78    private static final int PEOPLE_CONTACTMETHODS_ID = 7;
79    private static final int CONTACTMETHODS = 8;
80    private static final int CONTACTMETHODS_ID = 9;
81    private static final int PEOPLE_PHONES = 10;
82    private static final int PEOPLE_PHONES_ID = 11;
83    private static final int PHONES = 12;
84    private static final int PHONES_ID = 13;
85    private static final int EXTENSIONS = 14;
86    private static final int EXTENSIONS_ID = 15;
87    private static final int PEOPLE_EXTENSIONS = 16;
88    private static final int PEOPLE_EXTENSIONS_ID = 17;
89    private static final int GROUPS = 18;
90    private static final int GROUPS_ID = 19;
91    private static final int GROUPMEMBERSHIP = 20;
92    private static final int GROUPMEMBERSHIP_ID = 21;
93    private static final int PEOPLE_GROUPMEMBERSHIP = 22;
94    private static final int PEOPLE_GROUPMEMBERSHIP_ID = 23;
95    private static final int PEOPLE_PHOTO = 24;
96    private static final int PHOTOS = 25;
97    private static final int PHOTOS_ID = 26;
98    private static final int PEOPLE_FILTER = 29;
99    private static final int DELETED_PEOPLE = 30;
100    private static final int DELETED_GROUPS = 31;
101    private static final int SEARCH_SUGGESTIONS = 32;
102    private static final int SEARCH_SHORTCUT = 33;
103    private static final int PHONES_FILTER = 34;
104    private static final int LIVE_FOLDERS_PEOPLE = 35;
105    private static final int LIVE_FOLDERS_PEOPLE_GROUP_NAME = 36;
106    private static final int LIVE_FOLDERS_PEOPLE_WITH_PHONES = 37;
107    private static final int LIVE_FOLDERS_PEOPLE_FAVORITES = 38;
108    private static final int CONTACTMETHODS_EMAIL = 39;
109    private static final int GROUP_NAME_MEMBERS = 40;
110
111    private static final String PEOPLE_JOINS =
112            " LEFT OUTER JOIN data name ON (raw_contacts._id = name.raw_contact_id"
113            + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = name.mimetype_id)"
114                    + "='" + StructuredName.CONTENT_ITEM_TYPE + "')"
115            + " LEFT OUTER JOIN data organization ON (raw_contacts._id = organization.raw_contact_id"
116            + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = organization.mimetype_id)"
117                    + "='" + Organization.CONTENT_ITEM_TYPE + "' AND organization.is_primary)"
118            + " LEFT OUTER JOIN data email ON (raw_contacts._id = email.raw_contact_id"
119            + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = email.mimetype_id)"
120                    + "='" + Email.CONTENT_ITEM_TYPE + "' AND email.is_primary)"
121            + " LEFT OUTER JOIN data note ON (raw_contacts._id = note.raw_contact_id"
122            + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = note.mimetype_id)"
123                    + "='" + Note.CONTENT_ITEM_TYPE + "')"
124            + " LEFT OUTER JOIN data phone ON (raw_contacts._id = phone.raw_contact_id"
125            + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = phone.mimetype_id)"
126                    + "='" + Phone.CONTENT_ITEM_TYPE + "' AND phone.is_primary)";
127
128    public static final String DATA_JOINS =
129            " JOIN mimetypes ON (mimetypes._id = data.mimetype_id)"
130            + " JOIN raw_contacts ON (raw_contacts._id = data.raw_contact_id)"
131            + PEOPLE_JOINS;
132
133    public static final String PRESENCE_JOINS =
134            " LEFT OUTER JOIN presence" +
135            " ON (" + " presence." + StatusUpdates.DATA_ID
136                    + " = (SELECT max(" + StatusUpdates.DATA_ID + ")" +
137            " FROM presence" +
138            " WHERE people._id = presence_raw_contact_id))";
139
140    private static final String PHONETIC_NAME_SQL = "trim(trim("
141            + "ifnull(name." + StructuredName.PHONETIC_GIVEN_NAME + ",' ')||' '||"
142            + "ifnull(name." + StructuredName.PHONETIC_MIDDLE_NAME + ",' '))||' '||"
143            + "ifnull(name." + StructuredName.PHONETIC_FAMILY_NAME + ",' ')) ";
144
145    private static final String CONTACT_METHOD_KIND_SQL =
146            "CAST ((CASE WHEN mimetype='" + Email.CONTENT_ITEM_TYPE + "'"
147                + " THEN " + android.provider.Contacts.KIND_EMAIL
148                + " ELSE"
149                    + " (CASE WHEN mimetype='" + Im.CONTENT_ITEM_TYPE +"'"
150                        + " THEN " + android.provider.Contacts.KIND_IM
151                        + " ELSE"
152                        + " (CASE WHEN mimetype='" + StructuredPostal.CONTENT_ITEM_TYPE + "'"
153                            + " THEN "  + android.provider.Contacts.KIND_POSTAL
154                            + " ELSE"
155                                + " NULL"
156                            + " END)"
157                        + " END)"
158                + " END) AS INTEGER)";
159
160    private static final String IM_PROTOCOL_SQL =
161            "(CASE WHEN " + StatusUpdates.PROTOCOL + "=" + Im.PROTOCOL_CUSTOM
162                + " THEN 'custom:'||" + StatusUpdates.CUSTOM_PROTOCOL
163                + " ELSE 'pre:'||" + StatusUpdates.PROTOCOL
164                + " END)";
165
166    private static String CONTACT_METHOD_DATA_SQL =
167            "(CASE WHEN " + Data.MIMETYPE + "='" + Im.CONTENT_ITEM_TYPE + "'"
168                + " THEN (CASE WHEN " + Tables.DATA + "." + Im.PROTOCOL + "=" + Im.PROTOCOL_CUSTOM
169                    + " THEN 'custom:'||" + Tables.DATA + "." + Im.CUSTOM_PROTOCOL
170                    + " ELSE 'pre:'||" + Tables.DATA + "." + Im.PROTOCOL
171                    + " END)"
172                + " ELSE " + Tables.DATA + "." + Email.DATA
173                + " END)";
174
175    private static final Uri LIVE_FOLDERS_CONTACTS_URI = Uri.withAppendedPath(
176            ContactsContract.AUTHORITY_URI, "live_folders/contacts");
177
178    private static final Uri LIVE_FOLDERS_CONTACTS_WITH_PHONES_URI = Uri.withAppendedPath(
179            ContactsContract.AUTHORITY_URI, "live_folders/contacts_with_phones");
180
181    private static final Uri LIVE_FOLDERS_CONTACTS_FAVORITES_URI = Uri.withAppendedPath(
182            ContactsContract.AUTHORITY_URI, "live_folders/favorites");
183
184    public interface LegacyTables {
185        public static final String PEOPLE = "view_v1_people";
186        public static final String PEOPLE_JOIN_PRESENCE = "view_v1_people people " + PRESENCE_JOINS;
187        public static final String GROUPS = "view_v1_groups";
188        public static final String ORGANIZATIONS = "view_v1_organizations";
189        public static final String CONTACT_METHODS = "view_v1_contact_methods";
190        public static final String PHONES = "view_v1_phones";
191        public static final String EXTENSIONS = "view_v1_extensions";
192        public static final String GROUP_MEMBERSHIP = "view_v1_group_membership";
193        public static final String PHOTOS = "view_v1_photos";
194    }
195
196    private static final String[] ORGANIZATION_MIME_TYPES = new String[] {
197        Organization.CONTENT_ITEM_TYPE
198    };
199
200    private static final String[] CONTACT_METHOD_MIME_TYPES = new String[] {
201        Email.CONTENT_ITEM_TYPE,
202        Im.CONTENT_ITEM_TYPE,
203        StructuredPostal.CONTENT_ITEM_TYPE,
204    };
205
206    private static final String[] PHONE_MIME_TYPES = new String[] {
207        Phone.CONTENT_ITEM_TYPE
208    };
209
210    private static final String[] PHOTO_MIME_TYPES = new String[] {
211        Photo.CONTENT_ITEM_TYPE
212    };
213
214    private static final String[] EXTENSION_MIME_TYPES = new String[] {
215        android.provider.Contacts.Extensions.CONTENT_ITEM_TYPE
216    };
217
218    private interface IdQuery {
219        String[] COLUMNS = { BaseColumns._ID };
220
221        int _ID = 0;
222    }
223
224    /**
225     * A custom data row that is used to store legacy photo data fields no
226     * longer directly supported by the API.
227     */
228    private interface LegacyPhotoData {
229        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/photo_v1_extras";
230
231        public static final String PHOTO_DATA_ID = Data.DATA1;
232        public static final String LOCAL_VERSION = Data.DATA2;
233        public static final String DOWNLOAD_REQUIRED = Data.DATA3;
234        public static final String EXISTS_ON_SERVER = Data.DATA4;
235        public static final String SYNC_ERROR = Data.DATA5;
236    }
237
238    public static final String LEGACY_PHOTO_JOIN =
239            " LEFT OUTER JOIN data legacy_photo ON (raw_contacts._id = legacy_photo.raw_contact_id"
240            + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = legacy_photo.mimetype_id)"
241                + "='" + LegacyPhotoData.CONTENT_ITEM_TYPE + "'"
242            + " AND " + DataColumns.CONCRETE_ID + " = legacy_photo." + LegacyPhotoData.PHOTO_DATA_ID
243            + ")";
244
245    private static final HashMap<String, String> sPeopleProjectionMap;
246    private static final HashMap<String, String> sOrganizationProjectionMap;
247    private static final HashMap<String, String> sContactMethodProjectionMap;
248    private static final HashMap<String, String> sPhoneProjectionMap;
249    private static final HashMap<String, String> sExtensionProjectionMap;
250    private static final HashMap<String, String> sGroupProjectionMap;
251    private static final HashMap<String, String> sGroupMembershipProjectionMap;
252    private static final HashMap<String, String> sPhotoProjectionMap;
253
254    static {
255
256        // Contacts URI matching table
257        UriMatcher matcher = sUriMatcher;
258
259        String authority = android.provider.Contacts.AUTHORITY;
260        matcher.addURI(authority, "extensions", EXTENSIONS);
261        matcher.addURI(authority, "extensions/#", EXTENSIONS_ID);
262        matcher.addURI(authority, "groups", GROUPS);
263        matcher.addURI(authority, "groups/#", GROUPS_ID);
264        matcher.addURI(authority, "groups/name/*/members", GROUP_NAME_MEMBERS);
265//        matcher.addURI(authority, "groups/name/*/members/filter/*",
266//                GROUP_NAME_MEMBERS_FILTER);
267//        matcher.addURI(authority, "groups/system_id/*/members", GROUP_SYSTEM_ID_MEMBERS);
268//        matcher.addURI(authority, "groups/system_id/*/members/filter/*",
269//                GROUP_SYSTEM_ID_MEMBERS_FILTER);
270        matcher.addURI(authority, "groupmembership", GROUPMEMBERSHIP);
271        matcher.addURI(authority, "groupmembership/#", GROUPMEMBERSHIP_ID);
272//        matcher.addURI(authority, "groupmembershipraw", GROUPMEMBERSHIP_RAW);
273        matcher.addURI(authority, "people", PEOPLE);
274//        matcher.addURI(authority, "people/strequent", PEOPLE_STREQUENT);
275//        matcher.addURI(authority, "people/strequent/filter/*", PEOPLE_STREQUENT_FILTER);
276        matcher.addURI(authority, "people/filter/*", PEOPLE_FILTER);
277//        matcher.addURI(authority, "people/with_phones_filter/*",
278//                PEOPLE_WITH_PHONES_FILTER);
279//        matcher.addURI(authority, "people/with_email_or_im_filter/*",
280//                PEOPLE_WITH_EMAIL_OR_IM_FILTER);
281        matcher.addURI(authority, "people/#", PEOPLE_ID);
282        matcher.addURI(authority, "people/#/extensions", PEOPLE_EXTENSIONS);
283        matcher.addURI(authority, "people/#/extensions/#", PEOPLE_EXTENSIONS_ID);
284        matcher.addURI(authority, "people/#/phones", PEOPLE_PHONES);
285        matcher.addURI(authority, "people/#/phones/#", PEOPLE_PHONES_ID);
286//        matcher.addURI(authority, "people/#/phones_with_presence",
287//                PEOPLE_PHONES_WITH_PRESENCE);
288        matcher.addURI(authority, "people/#/photo", PEOPLE_PHOTO);
289//        matcher.addURI(authority, "people/#/photo/data", PEOPLE_PHOTO_DATA);
290        matcher.addURI(authority, "people/#/contact_methods", PEOPLE_CONTACTMETHODS);
291//        matcher.addURI(authority, "people/#/contact_methods_with_presence",
292//                PEOPLE_CONTACTMETHODS_WITH_PRESENCE);
293        matcher.addURI(authority, "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID);
294//        matcher.addURI(authority, "people/#/organizations", PEOPLE_ORGANIZATIONS);
295//        matcher.addURI(authority, "people/#/organizations/#", PEOPLE_ORGANIZATIONS_ID);
296        matcher.addURI(authority, "people/#/groupmembership", PEOPLE_GROUPMEMBERSHIP);
297        matcher.addURI(authority, "people/#/groupmembership/#", PEOPLE_GROUPMEMBERSHIP_ID);
298//        matcher.addURI(authority, "people/raw", PEOPLE_RAW);
299//        matcher.addURI(authority, "people/owner", PEOPLE_OWNER);
300        matcher.addURI(authority, "people/#/update_contact_time",
301                PEOPLE_UPDATE_CONTACT_TIME);
302        matcher.addURI(authority, "deleted_people", DELETED_PEOPLE);
303        matcher.addURI(authority, "deleted_groups", DELETED_GROUPS);
304        matcher.addURI(authority, "phones", PHONES);
305//        matcher.addURI(authority, "phones_with_presence", PHONES_WITH_PRESENCE);
306        matcher.addURI(authority, "phones/filter/*", PHONES_FILTER);
307//        matcher.addURI(authority, "phones/filter_name/*", PHONES_FILTER_NAME);
308//        matcher.addURI(authority, "phones/mobile_filter_name/*",
309//                PHONES_MOBILE_FILTER_NAME);
310        matcher.addURI(authority, "phones/#", PHONES_ID);
311        matcher.addURI(authority, "photos", PHOTOS);
312        matcher.addURI(authority, "photos/#", PHOTOS_ID);
313        matcher.addURI(authority, "contact_methods", CONTACTMETHODS);
314        matcher.addURI(authority, "contact_methods/email", CONTACTMETHODS_EMAIL);
315//        matcher.addURI(authority, "contact_methods/email/*", CONTACTMETHODS_EMAIL_FILTER);
316        matcher.addURI(authority, "contact_methods/#", CONTACTMETHODS_ID);
317//        matcher.addURI(authority, "contact_methods/with_presence",
318//                CONTACTMETHODS_WITH_PRESENCE);
319        matcher.addURI(authority, "organizations", ORGANIZATIONS);
320        matcher.addURI(authority, "organizations/#", ORGANIZATIONS_ID);
321//        matcher.addURI(authority, "voice_dialer_timestamp", VOICE_DIALER_TIMESTAMP);
322        matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY,
323                SEARCH_SUGGESTIONS);
324        matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY + "/*",
325                SEARCH_SUGGESTIONS);
326        matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/#",
327                SEARCH_SHORTCUT);
328//        matcher.addURI(authority, "settings", SETTINGS);
329//
330        matcher.addURI(authority, "live_folders/people", LIVE_FOLDERS_PEOPLE);
331        matcher.addURI(authority, "live_folders/people/*",
332                LIVE_FOLDERS_PEOPLE_GROUP_NAME);
333        matcher.addURI(authority, "live_folders/people_with_phones",
334                LIVE_FOLDERS_PEOPLE_WITH_PHONES);
335        matcher.addURI(authority, "live_folders/favorites",
336                LIVE_FOLDERS_PEOPLE_FAVORITES);
337
338
339        HashMap<String, String> peopleProjectionMap = new HashMap<String, String>();
340        peopleProjectionMap.put(People.NAME, People.NAME);
341        peopleProjectionMap.put(People.DISPLAY_NAME, People.DISPLAY_NAME);
342        peopleProjectionMap.put(People.PHONETIC_NAME, People.PHONETIC_NAME);
343        peopleProjectionMap.put(People.NOTES, People.NOTES);
344        peopleProjectionMap.put(People.TIMES_CONTACTED, People.TIMES_CONTACTED);
345        peopleProjectionMap.put(People.LAST_TIME_CONTACTED, People.LAST_TIME_CONTACTED);
346        peopleProjectionMap.put(People.CUSTOM_RINGTONE, People.CUSTOM_RINGTONE);
347        peopleProjectionMap.put(People.SEND_TO_VOICEMAIL, People.SEND_TO_VOICEMAIL);
348        peopleProjectionMap.put(People.STARRED, People.STARRED);
349        peopleProjectionMap.put(People.PRIMARY_ORGANIZATION_ID, People.PRIMARY_ORGANIZATION_ID);
350        peopleProjectionMap.put(People.PRIMARY_EMAIL_ID, People.PRIMARY_EMAIL_ID);
351        peopleProjectionMap.put(People.PRIMARY_PHONE_ID, People.PRIMARY_PHONE_ID);
352
353        sPeopleProjectionMap = new HashMap<String, String>(peopleProjectionMap);
354        sPeopleProjectionMap.put(People._ID, People._ID);
355        sPeopleProjectionMap.put(People.NUMBER, People.NUMBER);
356        sPeopleProjectionMap.put(People.TYPE, People.TYPE);
357        sPeopleProjectionMap.put(People.LABEL, People.LABEL);
358        sPeopleProjectionMap.put(People.NUMBER_KEY, People.NUMBER_KEY);
359        sPeopleProjectionMap.put(People.IM_PROTOCOL, IM_PROTOCOL_SQL + " AS " + People.IM_PROTOCOL);
360        sPeopleProjectionMap.put(People.IM_HANDLE, People.IM_HANDLE);
361        sPeopleProjectionMap.put(People.IM_ACCOUNT, People.IM_ACCOUNT);
362        sPeopleProjectionMap.put(People.PRESENCE_STATUS, People.PRESENCE_STATUS);
363        sPeopleProjectionMap.put(People.PRESENCE_CUSTOM_STATUS,
364                "(SELECT " + StatusUpdates.STATUS +
365                " FROM " + Tables.STATUS_UPDATES +
366                " JOIN " + Tables.DATA +
367                "   ON(" + StatusUpdatesColumns.DATA_ID + "=" + DataColumns.CONCRETE_ID + ")" +
368                " WHERE " + DataColumns.CONCRETE_RAW_CONTACT_ID + "=people." + People._ID +
369                " ORDER BY " + StatusUpdates.STATUS_TIMESTAMP + " DESC " +
370                " LIMIT 1" +
371                ") AS " + People.PRESENCE_CUSTOM_STATUS);
372
373        sOrganizationProjectionMap = new HashMap<String, String>();
374        sOrganizationProjectionMap.put(android.provider.Contacts.Organizations._ID,
375                android.provider.Contacts.Organizations._ID);
376        sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.PERSON_ID,
377                android.provider.Contacts.Organizations.PERSON_ID);
378        sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.ISPRIMARY,
379                android.provider.Contacts.Organizations.ISPRIMARY);
380        sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.COMPANY,
381                android.provider.Contacts.Organizations.COMPANY);
382        sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.TYPE,
383                android.provider.Contacts.Organizations.TYPE);
384        sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.LABEL,
385                android.provider.Contacts.Organizations.LABEL);
386        sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.TITLE,
387                android.provider.Contacts.Organizations.TITLE);
388
389        sContactMethodProjectionMap = new HashMap<String, String>(peopleProjectionMap);
390        sContactMethodProjectionMap.put(ContactMethods._ID, ContactMethods._ID);
391        sContactMethodProjectionMap.put(ContactMethods.PERSON_ID, ContactMethods.PERSON_ID);
392        sContactMethodProjectionMap.put(ContactMethods.KIND, ContactMethods.KIND);
393        sContactMethodProjectionMap.put(ContactMethods.ISPRIMARY, ContactMethods.ISPRIMARY);
394        sContactMethodProjectionMap.put(ContactMethods.TYPE, ContactMethods.TYPE);
395        sContactMethodProjectionMap.put(ContactMethods.DATA, ContactMethods.DATA);
396        sContactMethodProjectionMap.put(ContactMethods.LABEL, ContactMethods.LABEL);
397        sContactMethodProjectionMap.put(ContactMethods.AUX_DATA, ContactMethods.AUX_DATA);
398
399        sPhoneProjectionMap = new HashMap<String, String>(peopleProjectionMap);
400        sPhoneProjectionMap.put(android.provider.Contacts.Phones._ID,
401                android.provider.Contacts.Phones._ID);
402        sPhoneProjectionMap.put(android.provider.Contacts.Phones.PERSON_ID,
403                android.provider.Contacts.Phones.PERSON_ID);
404        sPhoneProjectionMap.put(android.provider.Contacts.Phones.ISPRIMARY,
405                android.provider.Contacts.Phones.ISPRIMARY);
406        sPhoneProjectionMap.put(android.provider.Contacts.Phones.NUMBER,
407                android.provider.Contacts.Phones.NUMBER);
408        sPhoneProjectionMap.put(android.provider.Contacts.Phones.TYPE,
409                android.provider.Contacts.Phones.TYPE);
410        sPhoneProjectionMap.put(android.provider.Contacts.Phones.LABEL,
411                android.provider.Contacts.Phones.LABEL);
412        sPhoneProjectionMap.put(android.provider.Contacts.Phones.NUMBER_KEY,
413                android.provider.Contacts.Phones.NUMBER_KEY);
414
415        sExtensionProjectionMap = new HashMap<String, String>();
416        sExtensionProjectionMap.put(android.provider.Contacts.Extensions._ID,
417                android.provider.Contacts.Extensions._ID);
418        sExtensionProjectionMap.put(android.provider.Contacts.Extensions.PERSON_ID,
419                android.provider.Contacts.Extensions.PERSON_ID);
420        sExtensionProjectionMap.put(android.provider.Contacts.Extensions.NAME,
421                android.provider.Contacts.Extensions.NAME);
422        sExtensionProjectionMap.put(android.provider.Contacts.Extensions.VALUE,
423                android.provider.Contacts.Extensions.VALUE);
424
425        sGroupProjectionMap = new HashMap<String, String>();
426        sGroupProjectionMap.put(android.provider.Contacts.Groups._ID,
427                android.provider.Contacts.Groups._ID);
428        sGroupProjectionMap.put(android.provider.Contacts.Groups.NAME,
429                android.provider.Contacts.Groups.NAME);
430        sGroupProjectionMap.put(android.provider.Contacts.Groups.NOTES,
431                android.provider.Contacts.Groups.NOTES);
432        sGroupProjectionMap.put(android.provider.Contacts.Groups.SYSTEM_ID,
433                android.provider.Contacts.Groups.SYSTEM_ID);
434
435        sGroupMembershipProjectionMap = new HashMap<String, String>(sGroupProjectionMap);
436        sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership._ID,
437                android.provider.Contacts.GroupMembership._ID);
438        sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership.PERSON_ID,
439                android.provider.Contacts.GroupMembership.PERSON_ID);
440        sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership.GROUP_ID,
441                android.provider.Contacts.GroupMembership.GROUP_ID);
442
443        sPhotoProjectionMap = new HashMap<String, String>();
444        sPhotoProjectionMap.put(android.provider.Contacts.Photos._ID,
445                android.provider.Contacts.Photos._ID);
446        sPhotoProjectionMap.put(android.provider.Contacts.Photos.PERSON_ID,
447                android.provider.Contacts.Photos.PERSON_ID);
448        sPhotoProjectionMap.put(android.provider.Contacts.Photos.DATA,
449                android.provider.Contacts.Photos.DATA);
450        sPhotoProjectionMap.put(android.provider.Contacts.Photos.LOCAL_VERSION,
451                android.provider.Contacts.Photos.LOCAL_VERSION);
452        sPhotoProjectionMap.put(android.provider.Contacts.Photos.DOWNLOAD_REQUIRED,
453                android.provider.Contacts.Photos.DOWNLOAD_REQUIRED);
454        sPhotoProjectionMap.put(android.provider.Contacts.Photos.EXISTS_ON_SERVER,
455                android.provider.Contacts.Photos.EXISTS_ON_SERVER);
456        sPhotoProjectionMap.put(android.provider.Contacts.Photos.SYNC_ERROR,
457                android.provider.Contacts.Photos.SYNC_ERROR);
458    }
459
460    private final Context mContext;
461    private final OpenHelper mOpenHelper;
462    private final ContactsProvider2 mContactsProvider;
463    private final NameSplitter mPhoneticNameSplitter;
464    private final GlobalSearchSupport mGlobalSearchSupport;
465
466    /** Precompiled sql statement for incrementing times contacted for a contact */
467    private final SQLiteStatement mLastTimeContactedUpdate;
468    private final SQLiteStatement mDataMimetypeQuery;
469    private final SQLiteStatement mDataRawContactIdQuery;
470
471    private final ContentValues mValues = new ContentValues();
472    private final ContentValues mValues2 = new ContentValues();
473    private final ContentValues mValues3 = new ContentValues();
474    private Account mAccount;
475
476    private long mMimetypeEmail;
477    private long mMimetypeIm;
478    private long mMimetypePostal;
479
480    public LegacyApiSupport(Context context, OpenHelper openHelper,
481            ContactsProvider2 contactsProvider, GlobalSearchSupport globalSearchSupport) {
482        mContext = context;
483        mContactsProvider = contactsProvider;
484        mOpenHelper = openHelper;
485        mGlobalSearchSupport = globalSearchSupport;
486
487        mPhoneticNameSplitter = new NameSplitter("", "", "", context
488                .getString(com.android.internal.R.string.common_name_conjunctions), Locale
489                .getDefault());
490
491        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
492        mLastTimeContactedUpdate = db.compileStatement("UPDATE " + Tables.RAW_CONTACTS + " SET "
493                + RawContacts.TIMES_CONTACTED + "="
494                + RawContacts.TIMES_CONTACTED + "+1,"
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 = mOpenHelper.getMimeTypeId(Email.CONTENT_ITEM_TYPE);
509        mMimetypeIm = mOpenHelper.getMimeTypeId(Im.CONTENT_ITEM_TYPE);
510        mMimetypePostal = mOpenHelper.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        OpenHelper.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 = mOpenHelper.getContactId(rawContactId);
1093        if (contactId != 0) {
1094            mContactsProvider.updateContactTime(contactId, lastTimeContacted);
1095        } else {
1096            mLastTimeContactedUpdate.bindLong(1, lastTimeContacted);
1097            mLastTimeContactedUpdate.bindLong(2, rawContactId);
1098            mLastTimeContactedUpdate.execute();
1099        }
1100        return 1;
1101    }
1102
1103    private int updatePhoto(long rawContactId, ContentValues values) {
1104
1105        // TODO check sanctions
1106
1107        int count;
1108
1109        long dataId = findFirstDataId(rawContactId, Photo.CONTENT_ITEM_TYPE);
1110
1111        mValues.clear();
1112        byte[] bytes = values.getAsByteArray(android.provider.Contacts.Photos.DATA);
1113        mValues.put(Photo.PHOTO, bytes);
1114
1115        if (dataId == -1) {
1116            mValues.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
1117            mValues.put(Data.RAW_CONTACT_ID, rawContactId);
1118            Uri dataUri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
1119            dataId = ContentUris.parseId(dataUri);
1120            count = 1;
1121        } else {
1122            Uri dataUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
1123            count = mContactsProvider.updateInTransaction(dataUri, mValues, null, null);
1124        }
1125
1126        updateLegacyPhotoData(rawContactId, dataId, values);
1127
1128        return count;
1129    }
1130
1131    private int updatePhotoByDataId(long dataId, ContentValues values) {
1132
1133        mDataRawContactIdQuery.bindLong(1, dataId);
1134        long rawContactId;
1135
1136        try {
1137            rawContactId = mDataRawContactIdQuery.simpleQueryForLong();
1138        } catch (SQLiteDoneException e) {
1139            // Data row not found
1140            return 0;
1141        }
1142
1143        if (values.containsKey(android.provider.Contacts.Photos.DATA)) {
1144            byte[] bytes = values.getAsByteArray(android.provider.Contacts.Photos.DATA);
1145            mValues.clear();
1146            mValues.put(Photo.PHOTO, bytes);
1147            mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
1148                    Data._ID + "=" + dataId, null);
1149        }
1150
1151        updateLegacyPhotoData(rawContactId, dataId, values);
1152
1153        return 1;
1154    }
1155
1156    private void updateLegacyPhotoData(long rawContactId, long dataId, ContentValues values) {
1157        mValues.clear();
1158        OpenHelper.copyStringValue(mValues, LegacyPhotoData.LOCAL_VERSION,
1159                values, android.provider.Contacts.Photos.LOCAL_VERSION);
1160        OpenHelper.copyStringValue(mValues, LegacyPhotoData.DOWNLOAD_REQUIRED,
1161                values, android.provider.Contacts.Photos.DOWNLOAD_REQUIRED);
1162        OpenHelper.copyStringValue(mValues, LegacyPhotoData.EXISTS_ON_SERVER,
1163                values, android.provider.Contacts.Photos.EXISTS_ON_SERVER);
1164        OpenHelper.copyStringValue(mValues, LegacyPhotoData.SYNC_ERROR,
1165                values, android.provider.Contacts.Photos.SYNC_ERROR);
1166
1167        int updated = mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
1168                Data.MIMETYPE + "='" + LegacyPhotoData.CONTENT_ITEM_TYPE + "'"
1169                        + " AND " + Data.RAW_CONTACT_ID + "=" + rawContactId
1170                        + " AND " + LegacyPhotoData.PHOTO_DATA_ID + "=" + dataId, null);
1171        if (updated == 0) {
1172            mValues.put(Data.RAW_CONTACT_ID, rawContactId);
1173            mValues.put(Data.MIMETYPE, LegacyPhotoData.CONTENT_ITEM_TYPE);
1174            mValues.put(LegacyPhotoData.PHOTO_DATA_ID, dataId);
1175            mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
1176        }
1177    }
1178
1179    private void parsePeopleValues(ContentValues values) {
1180        mValues.clear();
1181        mValues2.clear();
1182        mValues3.clear();
1183
1184        OpenHelper.copyStringValue(mValues, RawContacts.CUSTOM_RINGTONE,
1185                values, People.CUSTOM_RINGTONE);
1186        OpenHelper.copyLongValue(mValues, RawContacts.SEND_TO_VOICEMAIL,
1187                values, People.SEND_TO_VOICEMAIL);
1188        OpenHelper.copyLongValue(mValues, RawContacts.LAST_TIME_CONTACTED,
1189                values, People.LAST_TIME_CONTACTED);
1190        OpenHelper.copyLongValue(mValues, RawContacts.TIMES_CONTACTED,
1191                values, People.TIMES_CONTACTED);
1192        OpenHelper.copyLongValue(mValues, RawContacts.STARRED,
1193                values, People.STARRED);
1194        mValues.put(RawContacts.ACCOUNT_NAME, mAccount.name);
1195        mValues.put(RawContacts.ACCOUNT_TYPE, mAccount.type);
1196
1197        if (values.containsKey(People.NAME) || values.containsKey(People.PHONETIC_NAME)) {
1198            mValues2.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
1199            OpenHelper.copyStringValue(mValues2, StructuredName.DISPLAY_NAME,
1200                    values, People.NAME);
1201            if (values.containsKey(People.PHONETIC_NAME)) {
1202                String phoneticName = values.getAsString(People.PHONETIC_NAME);
1203                NameSplitter.Name parsedName = new NameSplitter.Name();
1204                mPhoneticNameSplitter.split(parsedName, phoneticName);
1205                mValues2.put(StructuredName.PHONETIC_GIVEN_NAME, parsedName.getGivenNames());
1206                mValues2.put(StructuredName.PHONETIC_MIDDLE_NAME, parsedName.getMiddleName());
1207                mValues2.put(StructuredName.PHONETIC_FAMILY_NAME, parsedName.getFamilyName());
1208            }
1209        }
1210
1211        if (values.containsKey(People.NOTES)) {
1212            mValues3.put(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
1213            OpenHelper.copyStringValue(mValues3, Note.NOTE, values, People.NOTES);
1214        }
1215    }
1216
1217    private void parseOrganizationValues(ContentValues values) {
1218        mValues.clear();
1219
1220        mValues.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
1221
1222        OpenHelper.copyLongValue(mValues, Data.IS_PRIMARY,
1223                values, android.provider.Contacts.Organizations.ISPRIMARY);
1224
1225        OpenHelper.copyStringValue(mValues, Organization.COMPANY,
1226                values, android.provider.Contacts.Organizations.COMPANY);
1227
1228        // TYPE values happen to remain the same between V1 and V2 - can just copy the value
1229        OpenHelper.copyLongValue(mValues, Organization.TYPE,
1230                values, android.provider.Contacts.Organizations.TYPE);
1231
1232        OpenHelper.copyStringValue(mValues, Organization.LABEL,
1233                values, android.provider.Contacts.Organizations.LABEL);
1234        OpenHelper.copyStringValue(mValues, Organization.TITLE,
1235                values, android.provider.Contacts.Organizations.TITLE);
1236    }
1237
1238    private void parsePhoneValues(ContentValues values) {
1239        mValues.clear();
1240
1241        mValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
1242
1243        OpenHelper.copyLongValue(mValues, Data.IS_PRIMARY,
1244                values, android.provider.Contacts.Phones.ISPRIMARY);
1245
1246        OpenHelper.copyStringValue(mValues, Phone.NUMBER,
1247                values, android.provider.Contacts.Phones.NUMBER);
1248
1249        // TYPE values happen to remain the same between V1 and V2 - can just copy the value
1250        OpenHelper.copyLongValue(mValues, Phone.TYPE,
1251                values, android.provider.Contacts.Phones.TYPE);
1252
1253        OpenHelper.copyStringValue(mValues, Phone.LABEL,
1254                values, android.provider.Contacts.Phones.LABEL);
1255    }
1256
1257    private void parseContactMethodValues(int kind, ContentValues values) {
1258        mValues.clear();
1259
1260        OpenHelper.copyLongValue(mValues, Data.IS_PRIMARY, values, ContactMethods.ISPRIMARY);
1261
1262        switch (kind) {
1263            case android.provider.Contacts.KIND_EMAIL: {
1264                copyCommonFields(values, Email.CONTENT_ITEM_TYPE, Email.TYPE, Email.LABEL,
1265                        Data.DATA14);
1266                OpenHelper.copyStringValue(mValues, Email.DATA, values, ContactMethods.DATA);
1267                break;
1268            }
1269
1270            case android.provider.Contacts.KIND_IM: {
1271                String protocol = values.getAsString(ContactMethods.DATA);
1272                if (protocol.startsWith("pre:")) {
1273                    mValues.put(Im.PROTOCOL, Integer.parseInt(protocol.substring(4)));
1274                } else if (protocol.startsWith("custom:")) {
1275                    mValues.put(Im.PROTOCOL, Im.PROTOCOL_CUSTOM);
1276                    mValues.put(Im.CUSTOM_PROTOCOL, protocol.substring(7));
1277                }
1278
1279                copyCommonFields(values, Im.CONTENT_ITEM_TYPE, Im.TYPE, Im.LABEL, Data.DATA14);
1280                break;
1281            }
1282
1283            case android.provider.Contacts.KIND_POSTAL: {
1284                copyCommonFields(values, StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.TYPE,
1285                        StructuredPostal.LABEL, Data.DATA14);
1286                OpenHelper.copyStringValue(mValues, StructuredPostal.FORMATTED_ADDRESS, values,
1287                        ContactMethods.DATA);
1288                break;
1289            }
1290        }
1291    }
1292
1293    private void copyCommonFields(ContentValues values, String mimeType, String typeColumn,
1294            String labelColumn, String auxDataColumn) {
1295        mValues.put(Data.MIMETYPE, mimeType);
1296        OpenHelper.copyLongValue(mValues, typeColumn, values, ContactMethods.TYPE);
1297        OpenHelper.copyStringValue(mValues, labelColumn, values, ContactMethods.LABEL);
1298        OpenHelper.copyStringValue(mValues, auxDataColumn, values, ContactMethods.AUX_DATA);
1299    }
1300
1301    private void parseGroupValues(ContentValues values) {
1302        mValues.clear();
1303
1304        OpenHelper.copyStringValue(mValues, Groups.TITLE,
1305                values, android.provider.Contacts.Groups.NAME);
1306        OpenHelper.copyStringValue(mValues, Groups.NOTES,
1307                values, android.provider.Contacts.Groups.NOTES);
1308        OpenHelper.copyStringValue(mValues, Groups.SYSTEM_ID,
1309                values, android.provider.Contacts.Groups.SYSTEM_ID);
1310    }
1311
1312    private void parseExtensionValues(ContentValues values) {
1313        OpenHelper.copyStringValue(mValues, ExtensionsColumns.NAME,
1314                values, android.provider.Contacts.People.Extensions.NAME);
1315        OpenHelper.copyStringValue(mValues, ExtensionsColumns.VALUE,
1316                values, android.provider.Contacts.People.Extensions.VALUE);
1317    }
1318
1319    private Uri findFirstDataRow(long rawContactId, String contentItemType) {
1320        long dataId = findFirstDataId(rawContactId, contentItemType);
1321        if (dataId == -1) {
1322            return null;
1323        }
1324
1325        return ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
1326    }
1327
1328    private long findFirstDataId(long rawContactId, String mimeType) {
1329        long dataId = -1;
1330        Cursor c = mContactsProvider.query(Data.CONTENT_URI, IdQuery.COLUMNS,
1331                Data.RAW_CONTACT_ID + "=" + rawContactId + " AND "
1332                        + Data.MIMETYPE + "='" + mimeType + "'",
1333                null, null);
1334        try {
1335            if (c.moveToFirst()) {
1336                dataId = c.getLong(IdQuery._ID);
1337            }
1338        } finally {
1339            c.close();
1340        }
1341        return dataId;
1342    }
1343
1344
1345    public int delete(Uri uri, String selection, String[] selectionArgs) {
1346        Cursor c = query(uri, IdQuery.COLUMNS, selection, selectionArgs, null, null);
1347        if (c == null) {
1348            return 0;
1349        }
1350
1351        int count = 0;
1352        try {
1353            while (c.moveToNext()) {
1354                long id = c.getLong(IdQuery._ID);
1355                count += delete(uri, id);
1356            }
1357        } finally {
1358            c.close();
1359        }
1360
1361        return count;
1362    }
1363
1364    public int delete(Uri uri, long id) {
1365        final int match = sUriMatcher.match(uri);
1366        int count = 0;
1367        switch (match) {
1368            case PEOPLE:
1369            case PEOPLE_ID:
1370                count = mContactsProvider.deleteRawContact(ContentUris.parseId(uri), false);
1371                break;
1372
1373            case PEOPLE_PHOTO:
1374                mValues.clear();
1375                mValues.putNull(android.provider.Contacts.Photos.DATA);
1376                updatePhoto(id, mValues);
1377                break;
1378
1379            case ORGANIZATIONS:
1380            case ORGANIZATIONS_ID:
1381                count = mContactsProvider.deleteData(ContentUris.parseId(uri),
1382                        ORGANIZATION_MIME_TYPES);
1383                break;
1384
1385            case CONTACTMETHODS:
1386            case CONTACTMETHODS_ID:
1387                count = mContactsProvider.deleteData(ContentUris.parseId(uri),
1388                        CONTACT_METHOD_MIME_TYPES);
1389                break;
1390
1391
1392            case PHONES:
1393            case PHONES_ID:
1394                count = mContactsProvider.deleteData(ContentUris.parseId(uri),
1395                        PHONE_MIME_TYPES);
1396                break;
1397
1398            case EXTENSIONS:
1399            case EXTENSIONS_ID:
1400                count = mContactsProvider.deleteData(ContentUris.parseId(uri),
1401                        EXTENSION_MIME_TYPES);
1402                break;
1403
1404            case PHOTOS:
1405            case PHOTOS_ID:
1406                count = mContactsProvider.deleteData(ContentUris.parseId(uri),
1407                        PHOTO_MIME_TYPES);
1408
1409                break;
1410
1411            default:
1412                throw new UnsupportedOperationException("Unknown uri: " + uri);
1413        }
1414
1415        return count;
1416    }
1417
1418    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
1419            String sortOrder, String limit) {
1420        ensureDefaultAccount();
1421
1422        final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
1423        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
1424        String groupBy = null;
1425
1426        final int match = sUriMatcher.match(uri);
1427        switch (match) {
1428            case PEOPLE: {
1429                qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
1430                qb.setProjectionMap(sPeopleProjectionMap);
1431                applyRawContactsAccount(qb);
1432                break;
1433            }
1434
1435            case PEOPLE_ID:
1436                qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
1437                qb.setProjectionMap(sPeopleProjectionMap);
1438                applyRawContactsAccount(qb);
1439                qb.appendWhere(" AND " + People._ID + "=");
1440                qb.appendWhere(uri.getPathSegments().get(1));
1441                break;
1442
1443            case PEOPLE_FILTER: {
1444                qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
1445                qb.setProjectionMap(sPeopleProjectionMap);
1446                applyRawContactsAccount(qb);
1447                String filterParam = uri.getPathSegments().get(2);
1448                qb.appendWhere(" AND " + People._ID + " IN "
1449                        + mContactsProvider.getRawContactsByFilterAsNestedQuery(filterParam));
1450                break;
1451            }
1452
1453            case GROUP_NAME_MEMBERS:
1454                qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
1455                qb.setProjectionMap(sPeopleProjectionMap);
1456                applyRawContactsAccount(qb);
1457                String group = uri.getPathSegments().get(2);
1458                qb.appendWhere(" AND " + buildGroupNameMatchWhereClause(group));
1459                break;
1460
1461            case ORGANIZATIONS:
1462                qb.setTables(LegacyTables.ORGANIZATIONS + " organizations");
1463                qb.setProjectionMap(sOrganizationProjectionMap);
1464                applyRawContactsAccount(qb);
1465                break;
1466
1467            case ORGANIZATIONS_ID:
1468                qb.setTables(LegacyTables.ORGANIZATIONS + " organizations");
1469                qb.setProjectionMap(sOrganizationProjectionMap);
1470                applyRawContactsAccount(qb);
1471                qb.appendWhere(" AND " + android.provider.Contacts.Organizations._ID + "=");
1472                qb.appendWhere(uri.getPathSegments().get(1));
1473                break;
1474
1475            case CONTACTMETHODS:
1476                qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
1477                qb.setProjectionMap(sContactMethodProjectionMap);
1478                applyRawContactsAccount(qb);
1479                break;
1480
1481            case CONTACTMETHODS_ID:
1482                qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
1483                qb.setProjectionMap(sContactMethodProjectionMap);
1484                applyRawContactsAccount(qb);
1485                qb.appendWhere(" AND " + ContactMethods._ID + "=");
1486                qb.appendWhere(uri.getPathSegments().get(1));
1487                break;
1488
1489            case CONTACTMETHODS_EMAIL:
1490                qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
1491                qb.setProjectionMap(sContactMethodProjectionMap);
1492                applyRawContactsAccount(qb);
1493                qb.appendWhere(" AND " + ContactMethods.KIND + "="
1494                        + android.provider.Contacts.KIND_EMAIL);
1495                break;
1496
1497            case PEOPLE_CONTACTMETHODS:
1498                qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
1499                qb.setProjectionMap(sContactMethodProjectionMap);
1500                applyRawContactsAccount(qb);
1501                qb.appendWhere(" AND " + ContactMethods.PERSON_ID + "=");
1502                qb.appendWhere(uri.getPathSegments().get(1));
1503                qb.appendWhere(" AND " + ContactMethods.KIND + " IS NOT NULL");
1504                break;
1505
1506            case PEOPLE_CONTACTMETHODS_ID:
1507                qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
1508                qb.setProjectionMap(sContactMethodProjectionMap);
1509                applyRawContactsAccount(qb);
1510                qb.appendWhere(" AND " + ContactMethods.PERSON_ID + "=");
1511                qb.appendWhere(uri.getPathSegments().get(1));
1512                qb.appendWhere(" AND " + ContactMethods._ID + "=");
1513                qb.appendWhere(uri.getPathSegments().get(3));
1514                qb.appendWhere(" AND " + ContactMethods.KIND + " IS NOT NULL");
1515                break;
1516
1517            case PHONES:
1518                qb.setTables(LegacyTables.PHONES + " phones");
1519                qb.setProjectionMap(sPhoneProjectionMap);
1520                applyRawContactsAccount(qb);
1521                break;
1522
1523            case PHONES_ID:
1524                qb.setTables(LegacyTables.PHONES + " phones");
1525                qb.setProjectionMap(sPhoneProjectionMap);
1526                applyRawContactsAccount(qb);
1527                qb.appendWhere(" AND " + android.provider.Contacts.Phones._ID + "=");
1528                qb.appendWhere(uri.getPathSegments().get(1));
1529                break;
1530
1531            case PHONES_FILTER:
1532                qb.setTables(LegacyTables.PHONES + " phones");
1533                qb.setProjectionMap(sPhoneProjectionMap);
1534                applyRawContactsAccount(qb);
1535                if (uri.getPathSegments().size() > 2) {
1536                    String filterParam = uri.getLastPathSegment();
1537                    qb.appendWhere(" AND person =");
1538                    qb.appendWhere(mOpenHelper.buildPhoneLookupAsNestedQuery(filterParam));
1539                }
1540                break;
1541
1542            case PEOPLE_PHONES:
1543                qb.setTables(LegacyTables.PHONES + " phones");
1544                qb.setProjectionMap(sPhoneProjectionMap);
1545                applyRawContactsAccount(qb);
1546                qb.appendWhere(" AND " + android.provider.Contacts.Phones.PERSON_ID + "=");
1547                qb.appendWhere(uri.getPathSegments().get(1));
1548                break;
1549
1550            case PEOPLE_PHONES_ID:
1551                qb.setTables(LegacyTables.PHONES + " phones");
1552                qb.setProjectionMap(sPhoneProjectionMap);
1553                applyRawContactsAccount(qb);
1554                qb.appendWhere(" AND " + android.provider.Contacts.Phones.PERSON_ID + "=");
1555                qb.appendWhere(uri.getPathSegments().get(1));
1556                qb.appendWhere(" AND " + android.provider.Contacts.Phones._ID + "=");
1557                qb.appendWhere(uri.getPathSegments().get(3));
1558                break;
1559
1560            case EXTENSIONS:
1561                qb.setTables(LegacyTables.EXTENSIONS + " extensions");
1562                qb.setProjectionMap(sExtensionProjectionMap);
1563                applyRawContactsAccount(qb);
1564                break;
1565
1566            case EXTENSIONS_ID:
1567                qb.setTables(LegacyTables.EXTENSIONS + " extensions");
1568                qb.setProjectionMap(sExtensionProjectionMap);
1569                applyRawContactsAccount(qb);
1570                qb.appendWhere(" AND " + android.provider.Contacts.Extensions._ID + "=");
1571                qb.appendWhere(uri.getPathSegments().get(1));
1572                break;
1573
1574            case PEOPLE_EXTENSIONS:
1575                qb.setTables(LegacyTables.EXTENSIONS + " extensions");
1576                qb.setProjectionMap(sExtensionProjectionMap);
1577                applyRawContactsAccount(qb);
1578                qb.appendWhere(" AND " + android.provider.Contacts.Extensions.PERSON_ID + "=");
1579                qb.appendWhere(uri.getPathSegments().get(1));
1580                break;
1581
1582            case PEOPLE_EXTENSIONS_ID:
1583                qb.setTables(LegacyTables.EXTENSIONS + " extensions");
1584                qb.setProjectionMap(sExtensionProjectionMap);
1585                applyRawContactsAccount(qb);
1586                qb.appendWhere(" AND " + android.provider.Contacts.Extensions.PERSON_ID + "=");
1587                qb.appendWhere(uri.getPathSegments().get(1));
1588                qb.appendWhere(" AND " + android.provider.Contacts.Extensions._ID + "=");
1589                qb.appendWhere(uri.getPathSegments().get(3));
1590                break;
1591
1592            case GROUPS:
1593                qb.setTables(LegacyTables.GROUPS + " groups");
1594                qb.setProjectionMap(sGroupProjectionMap);
1595                applyGroupAccount(qb);
1596                break;
1597
1598            case GROUPS_ID:
1599                qb.setTables(LegacyTables.GROUPS + " groups");
1600                qb.setProjectionMap(sGroupProjectionMap);
1601                applyGroupAccount(qb);
1602                qb.appendWhere(" AND " + android.provider.Contacts.Groups._ID + "=");
1603                qb.appendWhere(uri.getPathSegments().get(1));
1604                break;
1605
1606            case GROUPMEMBERSHIP:
1607                qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
1608                qb.setProjectionMap(sGroupMembershipProjectionMap);
1609                applyRawContactsAccount(qb);
1610                break;
1611
1612            case GROUPMEMBERSHIP_ID:
1613                qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
1614                qb.setProjectionMap(sGroupMembershipProjectionMap);
1615                applyRawContactsAccount(qb);
1616                qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership._ID + "=");
1617                qb.appendWhere(uri.getPathSegments().get(1));
1618                break;
1619
1620            case PEOPLE_GROUPMEMBERSHIP:
1621                qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
1622                qb.setProjectionMap(sGroupMembershipProjectionMap);
1623                applyRawContactsAccount(qb);
1624                qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership.PERSON_ID + "=");
1625                qb.appendWhere(uri.getPathSegments().get(1));
1626                break;
1627
1628            case PEOPLE_GROUPMEMBERSHIP_ID:
1629                qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
1630                qb.setProjectionMap(sGroupMembershipProjectionMap);
1631                applyRawContactsAccount(qb);
1632                qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership.PERSON_ID + "=");
1633                qb.appendWhere(uri.getPathSegments().get(1));
1634                qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership._ID + "=");
1635                qb.appendWhere(uri.getPathSegments().get(3));
1636                break;
1637
1638            case PEOPLE_PHOTO:
1639                qb.setTables(LegacyTables.PHOTOS + " photos");
1640                qb.setProjectionMap(sPhotoProjectionMap);
1641                applyRawContactsAccount(qb);
1642                qb.appendWhere(" AND " + android.provider.Contacts.Photos.PERSON_ID + "=");
1643                qb.appendWhere(uri.getPathSegments().get(1));
1644                limit = "1";
1645                break;
1646
1647            case PHOTOS:
1648                qb.setTables(LegacyTables.PHOTOS + " photos");
1649                qb.setProjectionMap(sPhotoProjectionMap);
1650                applyRawContactsAccount(qb);
1651                break;
1652
1653            case PHOTOS_ID:
1654                qb.setTables(LegacyTables.PHOTOS + " photos");
1655                qb.setProjectionMap(sPhotoProjectionMap);
1656                applyRawContactsAccount(qb);
1657                qb.appendWhere(" AND " + android.provider.Contacts.Photos._ID + "=");
1658                qb.appendWhere(uri.getPathSegments().get(1));
1659                break;
1660
1661            case SEARCH_SUGGESTIONS:
1662
1663                // No legacy compatibility for search suggestions
1664                return mGlobalSearchSupport.handleSearchSuggestionsQuery(db, uri, limit);
1665
1666            case SEARCH_SHORTCUT: {
1667                long contactId = ContentUris.parseId(uri);
1668                return mGlobalSearchSupport.handleSearchShortcutRefresh(db, contactId, projection);
1669            }
1670
1671            case LIVE_FOLDERS_PEOPLE:
1672                return mContactsProvider.query(LIVE_FOLDERS_CONTACTS_URI,
1673                        projection, selection, selectionArgs, sortOrder);
1674
1675            case LIVE_FOLDERS_PEOPLE_WITH_PHONES:
1676                return mContactsProvider.query(LIVE_FOLDERS_CONTACTS_WITH_PHONES_URI,
1677                        projection, selection, selectionArgs, sortOrder);
1678
1679            case LIVE_FOLDERS_PEOPLE_FAVORITES:
1680                return mContactsProvider.query(LIVE_FOLDERS_CONTACTS_FAVORITES_URI,
1681                        projection, selection, selectionArgs, sortOrder);
1682
1683            case LIVE_FOLDERS_PEOPLE_GROUP_NAME:
1684                return mContactsProvider.query(Uri.withAppendedPath(LIVE_FOLDERS_CONTACTS_URI,
1685                        Uri.encode(uri.getLastPathSegment())),
1686                        projection, selection, selectionArgs, sortOrder);
1687
1688            case DELETED_PEOPLE:
1689            case DELETED_GROUPS:
1690                throw new UnsupportedOperationException();
1691
1692            default:
1693                throw new IllegalArgumentException("Unknown URL " + uri);
1694        }
1695
1696        // Perform the query and set the notification uri
1697        final Cursor c = qb.query(db, projection, selection, selectionArgs,
1698                groupBy, null, sortOrder, limit);
1699        if (c != null) {
1700            c.setNotificationUri(mContext.getContentResolver(),
1701                    android.provider.Contacts.CONTENT_URI);
1702        }
1703        return c;
1704    }
1705
1706    private void applyRawContactsAccount(SQLiteQueryBuilder qb) {
1707        StringBuilder sb = new StringBuilder();
1708        appendRawContactsAccount(sb);
1709        qb.appendWhere(sb.toString());
1710    }
1711
1712    private void appendRawContactsAccount(StringBuilder sb) {
1713        sb.append(RawContacts.ACCOUNT_NAME + "=");
1714        DatabaseUtils.appendEscapedSQLString(sb, mAccount.name);
1715        sb.append(" AND " + RawContacts.ACCOUNT_TYPE + "=");
1716        DatabaseUtils.appendEscapedSQLString(sb, mAccount.type);
1717    }
1718
1719    private void applyGroupAccount(SQLiteQueryBuilder qb) {
1720        StringBuilder sb = new StringBuilder();
1721        appendGroupAccount(sb);
1722        qb.appendWhere(sb.toString());
1723    }
1724
1725    private void appendGroupAccount(StringBuilder sb) {
1726        sb.append(Groups.ACCOUNT_NAME + "=");
1727        DatabaseUtils.appendEscapedSQLString(sb, mAccount.name);
1728        sb.append(" AND " + Groups.ACCOUNT_TYPE + "=");
1729        DatabaseUtils.appendEscapedSQLString(sb, mAccount.type);
1730    }
1731
1732    /**
1733     * Build a WHERE clause that restricts the query to match people that are a member of
1734     * a group with a particular name. The projection map of the query must include
1735     * {@link People#_ID}.
1736     *
1737     * @param groupName The name of the group
1738     * @return The where clause.
1739     */
1740    private String buildGroupNameMatchWhereClause(String groupName) {
1741        return "people._id IN "
1742                + "(SELECT " + DataColumns.CONCRETE_RAW_CONTACT_ID
1743                + " FROM " + Tables.DATA_JOIN_MIMETYPES
1744                + " WHERE " + Data.MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
1745                        + "' AND " + GroupMembership.GROUP_ROW_ID + "="
1746                                + "(SELECT " + Tables.GROUPS + "." + Groups._ID
1747                                + " FROM " + Tables.GROUPS
1748                                + " WHERE " + Groups.TITLE + "="
1749                                        + DatabaseUtils.sqlEscapeString(groupName) + "))";
1750    }
1751
1752    /**
1753     * Called when a change has been made.
1754     *
1755     * @param uri the uri that the change was made to
1756     */
1757    private void onChange(Uri uri) {
1758        mContext.getContentResolver().notifyChange(android.provider.Contacts.CONTENT_URI, null);
1759    }
1760
1761    public String getType(Uri uri) {
1762        int match = sUriMatcher.match(uri);
1763        switch (match) {
1764            case EXTENSIONS:
1765            case PEOPLE_EXTENSIONS:
1766                return Extensions.CONTENT_TYPE;
1767            case EXTENSIONS_ID:
1768            case PEOPLE_EXTENSIONS_ID:
1769                return Extensions.CONTENT_ITEM_TYPE;
1770            case PEOPLE:
1771                return "vnd.android.cursor.dir/person";
1772            case PEOPLE_ID:
1773                return "vnd.android.cursor.item/person";
1774            case PEOPLE_PHONES:
1775                return "vnd.android.cursor.dir/phone";
1776            case PEOPLE_PHONES_ID:
1777                return "vnd.android.cursor.item/phone";
1778            case PEOPLE_CONTACTMETHODS:
1779                return "vnd.android.cursor.dir/contact-methods";
1780            case PEOPLE_CONTACTMETHODS_ID:
1781                return getContactMethodType(uri);
1782            case PHONES:
1783                return "vnd.android.cursor.dir/phone";
1784            case PHONES_ID:
1785                return "vnd.android.cursor.item/phone";
1786            case PHONES_FILTER:
1787                return "vnd.android.cursor.dir/phone";
1788            case PHOTOS_ID:
1789                return "vnd.android.cursor.item/photo";
1790            case PHOTOS:
1791                return "vnd.android.cursor.dir/photo";
1792            case PEOPLE_PHOTO:
1793                return "vnd.android.cursor.item/photo";
1794            case CONTACTMETHODS:
1795                return "vnd.android.cursor.dir/contact-methods";
1796            case CONTACTMETHODS_ID:
1797                return getContactMethodType(uri);
1798            case ORGANIZATIONS:
1799                return "vnd.android.cursor.dir/organizations";
1800            case ORGANIZATIONS_ID:
1801                return "vnd.android.cursor.item/organization";
1802            case SEARCH_SUGGESTIONS:
1803                return SearchManager.SUGGEST_MIME_TYPE;
1804            case SEARCH_SHORTCUT:
1805                return SearchManager.SHORTCUT_MIME_TYPE;
1806            default:
1807                throw new IllegalArgumentException("Unknown URI");
1808        }
1809    }
1810
1811    private String getContactMethodType(Uri url) {
1812        String mime = null;
1813
1814        Cursor c = query(url, new String[] {ContactMethods.KIND}, null, null, null, null);
1815        if (c != null) {
1816            try {
1817                if (c.moveToFirst()) {
1818                    int kind = c.getInt(0);
1819                    switch (kind) {
1820                    case android.provider.Contacts.KIND_EMAIL:
1821                        mime = "vnd.android.cursor.item/email";
1822                        break;
1823
1824                    case android.provider.Contacts.KIND_IM:
1825                        mime = "vnd.android.cursor.item/jabber-im";
1826                        break;
1827
1828                    case android.provider.Contacts.KIND_POSTAL:
1829                        mime = "vnd.android.cursor.item/postal-address";
1830                        break;
1831                    }
1832                }
1833            } finally {
1834                c.close();
1835            }
1836        }
1837        return mime;
1838    }
1839}
1840