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