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