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