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