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