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