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