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