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