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  */
16 package com.android.providers.contacts;
17 
18 import android.accounts.Account;
19 import android.app.SearchManager;
20 import android.content.ContentUris;
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.content.UriMatcher;
24 import android.database.Cursor;
25 import android.database.DatabaseUtils;
26 import android.database.SQLException;
27 import android.database.sqlite.SQLiteDatabase;
28 import android.database.sqlite.SQLiteDoneException;
29 import android.database.sqlite.SQLiteQueryBuilder;
30 import android.database.sqlite.SQLiteStatement;
31 import android.net.Uri;
32 import android.provider.BaseColumns;
33 import android.provider.Contacts.ContactMethods;
34 import android.provider.Contacts.Extensions;
35 import android.provider.Contacts.People;
36 import android.provider.ContactsContract;
37 import android.provider.ContactsContract.CommonDataKinds.Email;
38 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
39 import android.provider.ContactsContract.CommonDataKinds.Im;
40 import android.provider.ContactsContract.CommonDataKinds.Note;
41 import android.provider.ContactsContract.CommonDataKinds.Organization;
42 import android.provider.ContactsContract.CommonDataKinds.Phone;
43 import android.provider.ContactsContract.CommonDataKinds.Photo;
44 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
45 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
46 import android.provider.ContactsContract.Contacts;
47 import android.provider.ContactsContract.Data;
48 import android.provider.ContactsContract.Groups;
49 import android.provider.ContactsContract.RawContacts;
50 import android.provider.ContactsContract.Settings;
51 import android.provider.ContactsContract.StatusUpdates;
52 import android.text.TextUtils;
53 import android.util.ArrayMap;
54 import android.util.Log;
55 
56 import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
57 import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
58 import com.android.providers.contacts.ContactsDatabaseHelper.ExtensionsColumns;
59 import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
60 import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
61 import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns;
62 import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType;
63 import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
64 import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
65 import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
66 import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
67 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
68 import com.android.providers.contacts.ContactsDatabaseHelper.Views;
69 import com.android.providers.contacts.database.MoreDatabaseUtils;
70 
71 import java.util.Locale;
72 
73 @SuppressWarnings("deprecation")
74 public class LegacyApiSupport {
75 
76     private static final String TAG = "ContactsProviderV1";
77 
78     private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
79 
80     private static final int PEOPLE = 1;
81     private static final int PEOPLE_ID = 2;
82     private static final int PEOPLE_UPDATE_CONTACT_TIME = 3;
83     private static final int ORGANIZATIONS = 4;
84     private static final int ORGANIZATIONS_ID = 5;
85     private static final int PEOPLE_CONTACTMETHODS = 6;
86     private static final int PEOPLE_CONTACTMETHODS_ID = 7;
87     private static final int CONTACTMETHODS = 8;
88     private static final int CONTACTMETHODS_ID = 9;
89     private static final int PEOPLE_PHONES = 10;
90     private static final int PEOPLE_PHONES_ID = 11;
91     private static final int PHONES = 12;
92     private static final int PHONES_ID = 13;
93     private static final int EXTENSIONS = 14;
94     private static final int EXTENSIONS_ID = 15;
95     private static final int PEOPLE_EXTENSIONS = 16;
96     private static final int PEOPLE_EXTENSIONS_ID = 17;
97     private static final int GROUPS = 18;
98     private static final int GROUPS_ID = 19;
99     private static final int GROUPMEMBERSHIP = 20;
100     private static final int GROUPMEMBERSHIP_ID = 21;
101     private static final int PEOPLE_GROUPMEMBERSHIP = 22;
102     private static final int PEOPLE_GROUPMEMBERSHIP_ID = 23;
103     private static final int PEOPLE_PHOTO = 24;
104     private static final int PHOTOS = 25;
105     private static final int PHOTOS_ID = 26;
106     private static final int PEOPLE_FILTER = 29;
107     private static final int DELETED_PEOPLE = 30;
108     private static final int DELETED_GROUPS = 31;
109     private static final int SEARCH_SUGGESTIONS = 32;
110     private static final int SEARCH_SHORTCUT = 33;
111     private static final int PHONES_FILTER = 34;
112     private static final int CONTACTMETHODS_EMAIL = 39;
113     private static final int GROUP_NAME_MEMBERS = 40;
114     private static final int GROUP_SYSTEM_ID_MEMBERS = 41;
115     private static final int PEOPLE_ORGANIZATIONS = 42;
116     private static final int PEOPLE_ORGANIZATIONS_ID = 43;
117     private static final int SETTINGS = 44;
118 
119     private static final String PEOPLE_JOINS =
120             " JOIN " + Tables.ACCOUNTS + " ON ("
121                 + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID + ")"
122             + " LEFT OUTER JOIN data name ON (raw_contacts._id = name.raw_contact_id"
123             + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = name.mimetype_id)"
124                     + "='" + StructuredName.CONTENT_ITEM_TYPE + "')"
125             + " LEFT OUTER JOIN data organization ON (raw_contacts._id = organization.raw_contact_id"
126             + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = organization.mimetype_id)"
127                     + "='" + Organization.CONTENT_ITEM_TYPE + "' AND organization.is_primary)"
128             + " LEFT OUTER JOIN data email ON (raw_contacts._id = email.raw_contact_id"
129             + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = email.mimetype_id)"
130                     + "='" + Email.CONTENT_ITEM_TYPE + "' AND email.is_primary)"
131             + " LEFT OUTER JOIN data note ON (raw_contacts._id = note.raw_contact_id"
132             + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = note.mimetype_id)"
133                     + "='" + Note.CONTENT_ITEM_TYPE + "')"
134             + " LEFT OUTER JOIN data phone ON (raw_contacts._id = phone.raw_contact_id"
135             + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = phone.mimetype_id)"
136                     + "='" + Phone.CONTENT_ITEM_TYPE + "' AND phone.is_primary)";
137 
138     public static final String DATA_JOINS =
139             " JOIN mimetypes ON (mimetypes._id = data.mimetype_id)"
140             + " JOIN raw_contacts ON (raw_contacts._id = data.raw_contact_id)"
141             + PEOPLE_JOINS;
142 
143     public static final String PRESENCE_JOINS =
144             " LEFT OUTER JOIN " + Tables.PRESENCE +
145             " ON (" + Tables.PRESENCE + "." + StatusUpdates.DATA_ID + "=" +
146                     "(SELECT MAX(" + StatusUpdates.DATA_ID + ")" +
147                     " FROM " + Tables.PRESENCE +
148                     " WHERE people._id = " + PresenceColumns.RAW_CONTACT_ID + ")" +
149             " )";
150 
151     private static final String PHONETIC_NAME_SQL = "trim(trim("
152             + "ifnull(name." + StructuredName.PHONETIC_GIVEN_NAME + ",' ')||' '||"
153             + "ifnull(name." + StructuredName.PHONETIC_MIDDLE_NAME + ",' '))||' '||"
154             + "ifnull(name." + StructuredName.PHONETIC_FAMILY_NAME + ",' ')) ";
155 
156     private static final String CONTACT_METHOD_KIND_SQL =
157             "CAST ((CASE WHEN mimetype='" + Email.CONTENT_ITEM_TYPE + "'"
158                 + " THEN " + android.provider.Contacts.KIND_EMAIL
159                 + " ELSE"
160                     + " (CASE WHEN mimetype='" + Im.CONTENT_ITEM_TYPE +"'"
161                         + " THEN " + android.provider.Contacts.KIND_IM
162                         + " ELSE"
163                         + " (CASE WHEN mimetype='" + StructuredPostal.CONTENT_ITEM_TYPE + "'"
164                             + " THEN "  + android.provider.Contacts.KIND_POSTAL
165                             + " ELSE"
166                                 + " NULL"
167                             + " END)"
168                         + " END)"
169                 + " END) AS INTEGER)";
170 
171     private static final String IM_PROTOCOL_SQL =
172             "(CASE WHEN " + StatusUpdates.PROTOCOL + "=" + Im.PROTOCOL_CUSTOM
173                 + " THEN 'custom:'||" + StatusUpdates.CUSTOM_PROTOCOL
174                 + " ELSE 'pre:'||" + StatusUpdates.PROTOCOL
175                 + " END)";
176 
177     private static String CONTACT_METHOD_DATA_SQL =
178             "(CASE WHEN " + Data.MIMETYPE + "='" + Im.CONTENT_ITEM_TYPE + "'"
179                 + " THEN (CASE WHEN " + Tables.DATA + "." + Im.PROTOCOL + "=" + Im.PROTOCOL_CUSTOM
180                     + " THEN 'custom:'||" + Tables.DATA + "." + Im.CUSTOM_PROTOCOL
181                     + " ELSE 'pre:'||" + Tables.DATA + "." + Im.PROTOCOL
182                     + " END)"
183                 + " ELSE " + Tables.DATA + "." + Email.DATA
184                 + " END)";
185 
186     private String[] mSelectionArgs1 = new String[1];
187     private String[] mSelectionArgs2 = new String[2];
188 
189     public interface LegacyTables {
190         public static final String PEOPLE = "view_v1_people";
191         public static final String PEOPLE_JOIN_PRESENCE = "view_v1_people people " + PRESENCE_JOINS;
192         public static final String GROUPS = "view_v1_groups";
193         public static final String ORGANIZATIONS = "view_v1_organizations";
194         public static final String CONTACT_METHODS = "view_v1_contact_methods";
195         public static final String PHONES = "view_v1_phones";
196         public static final String EXTENSIONS = "view_v1_extensions";
197         public static final String GROUP_MEMBERSHIP = "view_v1_group_membership";
198         public static final String PHOTOS = "view_v1_photos";
199         public static final String SETTINGS = "v1_settings";
200     }
201 
202     private static final String[] ORGANIZATION_MIME_TYPES = new String[] {
203         Organization.CONTENT_ITEM_TYPE
204     };
205 
206     private static final String[] CONTACT_METHOD_MIME_TYPES = new String[] {
207         Email.CONTENT_ITEM_TYPE,
208         Im.CONTENT_ITEM_TYPE,
209         StructuredPostal.CONTENT_ITEM_TYPE,
210     };
211 
212     private static final String[] PHONE_MIME_TYPES = new String[] {
213         Phone.CONTENT_ITEM_TYPE
214     };
215 
216     private static final String[] PHOTO_MIME_TYPES = new String[] {
217         Photo.CONTENT_ITEM_TYPE
218     };
219 
220     private static final String[] GROUP_MEMBERSHIP_MIME_TYPES = new String[] {
221         GroupMembership.CONTENT_ITEM_TYPE
222     };
223 
224     private static final String[] EXTENSION_MIME_TYPES = new String[] {
225         android.provider.Contacts.Extensions.CONTENT_ITEM_TYPE
226     };
227 
228     private interface IdQuery {
229         String[] COLUMNS = { BaseColumns._ID };
230 
231         int _ID = 0;
232     }
233 
234     /**
235      * A custom data row that is used to store legacy photo data fields no
236      * longer directly supported by the API.
237      */
238     private interface LegacyPhotoData {
239         public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/photo_v1_extras";
240 
241         public static final String PHOTO_DATA_ID = Data.DATA1;
242         public static final String LOCAL_VERSION = Data.DATA2;
243         public static final String DOWNLOAD_REQUIRED = Data.DATA3;
244         public static final String EXISTS_ON_SERVER = Data.DATA4;
245         public static final String SYNC_ERROR = Data.DATA5;
246     }
247 
248     public static final String LEGACY_PHOTO_JOIN =
249             " LEFT OUTER JOIN data legacy_photo ON (raw_contacts._id = legacy_photo.raw_contact_id"
250             + " AND (SELECT mimetype FROM mimetypes WHERE mimetypes._id = legacy_photo.mimetype_id)"
251                 + "='" + LegacyPhotoData.CONTENT_ITEM_TYPE + "'"
252             + " AND " + DataColumns.CONCRETE_ID + " = legacy_photo." + LegacyPhotoData.PHOTO_DATA_ID
253             + ")";
254 
255     private static final ArrayMap<String, String> sPeopleProjectionMap;
256     private static final ArrayMap<String, String> sOrganizationProjectionMap;
257     private static final ArrayMap<String, String> sContactMethodProjectionMap;
258     private static final ArrayMap<String, String> sPhoneProjectionMap;
259     private static final ArrayMap<String, String> sExtensionProjectionMap;
260     private static final ArrayMap<String, String> sGroupProjectionMap;
261     private static final ArrayMap<String, String> sGroupMembershipProjectionMap;
262     private static final ArrayMap<String, String> sPhotoProjectionMap;
263 
264     static {
265 
266         // Contacts URI matching table
267         UriMatcher matcher = sUriMatcher;
268 
269         String authority = android.provider.Contacts.AUTHORITY;
matcher.addURI(authority, "extensions", EXTENSIONS)270         matcher.addURI(authority, "extensions", EXTENSIONS);
matcher.addURI(authority, "extensions/#", EXTENSIONS_ID)271         matcher.addURI(authority, "extensions/#", EXTENSIONS_ID);
matcher.addURI(authority, "groups", GROUPS)272         matcher.addURI(authority, "groups", GROUPS);
matcher.addURI(authority, "groups/#", GROUPS_ID)273         matcher.addURI(authority, "groups/#", GROUPS_ID);
matcher.addURI(authority, "groups/name/*/members", GROUP_NAME_MEMBERS)274         matcher.addURI(authority, "groups/name/*/members", GROUP_NAME_MEMBERS);
275 //        matcher.addURI(authority, "groups/name/*/members/filter/*",
276 //                GROUP_NAME_MEMBERS_FILTER);
matcher.addURI(authority, "groups/system_id/*/members", GROUP_SYSTEM_ID_MEMBERS)277         matcher.addURI(authority, "groups/system_id/*/members", GROUP_SYSTEM_ID_MEMBERS);
278 //        matcher.addURI(authority, "groups/system_id/*/members/filter/*",
279 //                GROUP_SYSTEM_ID_MEMBERS_FILTER);
matcher.addURI(authority, "groupmembership", GROUPMEMBERSHIP)280         matcher.addURI(authority, "groupmembership", GROUPMEMBERSHIP);
matcher.addURI(authority, "groupmembership/#", GROUPMEMBERSHIP_ID)281         matcher.addURI(authority, "groupmembership/#", GROUPMEMBERSHIP_ID);
282 //        matcher.addURI(authority, "groupmembershipraw", GROUPMEMBERSHIP_RAW);
matcher.addURI(authority, "people", PEOPLE)283         matcher.addURI(authority, "people", PEOPLE);
284 //        matcher.addURI(authority, "people/strequent", PEOPLE_STREQUENT);
285 //        matcher.addURI(authority, "people/strequent/filter/*", PEOPLE_STREQUENT_FILTER);
matcher.addURI(authority, "people/filter/*", PEOPLE_FILTER)286         matcher.addURI(authority, "people/filter/*", PEOPLE_FILTER);
287 //        matcher.addURI(authority, "people/with_phones_filter/*",
288 //                PEOPLE_WITH_PHONES_FILTER);
289 //        matcher.addURI(authority, "people/with_email_or_im_filter/*",
290 //                PEOPLE_WITH_EMAIL_OR_IM_FILTER);
matcher.addURI(authority, "people/#", PEOPLE_ID)291         matcher.addURI(authority, "people/#", PEOPLE_ID);
matcher.addURI(authority, "people/#/extensions", PEOPLE_EXTENSIONS)292         matcher.addURI(authority, "people/#/extensions", PEOPLE_EXTENSIONS);
matcher.addURI(authority, "people/#/extensions/#", PEOPLE_EXTENSIONS_ID)293         matcher.addURI(authority, "people/#/extensions/#", PEOPLE_EXTENSIONS_ID);
matcher.addURI(authority, "people/#/phones", PEOPLE_PHONES)294         matcher.addURI(authority, "people/#/phones", PEOPLE_PHONES);
matcher.addURI(authority, "people/#/phones/#", PEOPLE_PHONES_ID)295         matcher.addURI(authority, "people/#/phones/#", PEOPLE_PHONES_ID);
296 //        matcher.addURI(authority, "people/#/phones_with_presence",
297 //                PEOPLE_PHONES_WITH_PRESENCE);
matcher.addURI(authority, "people/#/photo", PEOPLE_PHOTO)298         matcher.addURI(authority, "people/#/photo", PEOPLE_PHOTO);
299 //        matcher.addURI(authority, "people/#/photo/data", PEOPLE_PHOTO_DATA);
matcher.addURI(authority, "people/#/contact_methods", PEOPLE_CONTACTMETHODS)300         matcher.addURI(authority, "people/#/contact_methods", PEOPLE_CONTACTMETHODS);
301 //        matcher.addURI(authority, "people/#/contact_methods_with_presence",
302 //                PEOPLE_CONTACTMETHODS_WITH_PRESENCE);
matcher.addURI(authority, "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID)303         matcher.addURI(authority, "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID);
matcher.addURI(authority, "people/#/organizations", PEOPLE_ORGANIZATIONS)304         matcher.addURI(authority, "people/#/organizations", PEOPLE_ORGANIZATIONS);
matcher.addURI(authority, "people/#/organizations/#", PEOPLE_ORGANIZATIONS_ID)305         matcher.addURI(authority, "people/#/organizations/#", PEOPLE_ORGANIZATIONS_ID);
matcher.addURI(authority, "people/#/groupmembership", PEOPLE_GROUPMEMBERSHIP)306         matcher.addURI(authority, "people/#/groupmembership", PEOPLE_GROUPMEMBERSHIP);
matcher.addURI(authority, "people/#/groupmembership/#", PEOPLE_GROUPMEMBERSHIP_ID)307         matcher.addURI(authority, "people/#/groupmembership/#", PEOPLE_GROUPMEMBERSHIP_ID);
308 //        matcher.addURI(authority, "people/raw", PEOPLE_RAW);
309 //        matcher.addURI(authority, "people/owner", PEOPLE_OWNER);
matcher.addURI(authority, "people/#/update_contact_time", PEOPLE_UPDATE_CONTACT_TIME)310         matcher.addURI(authority, "people/#/update_contact_time",
311                 PEOPLE_UPDATE_CONTACT_TIME);
matcher.addURI(authority, "deleted_people", DELETED_PEOPLE)312         matcher.addURI(authority, "deleted_people", DELETED_PEOPLE);
matcher.addURI(authority, "deleted_groups", DELETED_GROUPS)313         matcher.addURI(authority, "deleted_groups", DELETED_GROUPS);
matcher.addURI(authority, "phones", PHONES)314         matcher.addURI(authority, "phones", PHONES);
315 //        matcher.addURI(authority, "phones_with_presence", PHONES_WITH_PRESENCE);
matcher.addURI(authority, "phones/filter/*", PHONES_FILTER)316         matcher.addURI(authority, "phones/filter/*", PHONES_FILTER);
317 //        matcher.addURI(authority, "phones/filter_name/*", PHONES_FILTER_NAME);
318 //        matcher.addURI(authority, "phones/mobile_filter_name/*",
319 //                PHONES_MOBILE_FILTER_NAME);
matcher.addURI(authority, "phones/#", PHONES_ID)320         matcher.addURI(authority, "phones/#", PHONES_ID);
matcher.addURI(authority, "photos", PHOTOS)321         matcher.addURI(authority, "photos", PHOTOS);
matcher.addURI(authority, "photos/#", PHOTOS_ID)322         matcher.addURI(authority, "photos/#", PHOTOS_ID);
matcher.addURI(authority, "contact_methods", CONTACTMETHODS)323         matcher.addURI(authority, "contact_methods", CONTACTMETHODS);
matcher.addURI(authority, "contact_methods/email", CONTACTMETHODS_EMAIL)324         matcher.addURI(authority, "contact_methods/email", CONTACTMETHODS_EMAIL);
325 //        matcher.addURI(authority, "contact_methods/email/*", CONTACTMETHODS_EMAIL_FILTER);
matcher.addURI(authority, "contact_methods/#", CONTACTMETHODS_ID)326         matcher.addURI(authority, "contact_methods/#", CONTACTMETHODS_ID);
327 //        matcher.addURI(authority, "contact_methods/with_presence",
328 //                CONTACTMETHODS_WITH_PRESENCE);
matcher.addURI(authority, "organizations", ORGANIZATIONS)329         matcher.addURI(authority, "organizations", ORGANIZATIONS);
matcher.addURI(authority, "organizations/#", ORGANIZATIONS_ID)330         matcher.addURI(authority, "organizations/#", ORGANIZATIONS_ID);
331 //        matcher.addURI(authority, "voice_dialer_timestamp", VOICE_DIALER_TIMESTAMP);
matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGESTIONS)332         matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY,
333                 SEARCH_SUGGESTIONS);
matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGESTIONS)334         matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY + "/*",
335                 SEARCH_SUGGESTIONS);
matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*", SEARCH_SHORTCUT)336         matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*",
337                 SEARCH_SHORTCUT);
matcher.addURI(authority, "settings", SETTINGS)338         matcher.addURI(authority, "settings", SETTINGS);
339 
340         ArrayMap<String, String> peopleProjectionMap = new ArrayMap<>();
peopleProjectionMap.put(People.NAME, People.NAME)341         peopleProjectionMap.put(People.NAME, People.NAME);
peopleProjectionMap.put(People.DISPLAY_NAME, People.DISPLAY_NAME)342         peopleProjectionMap.put(People.DISPLAY_NAME, People.DISPLAY_NAME);
peopleProjectionMap.put(People.PHONETIC_NAME, People.PHONETIC_NAME)343         peopleProjectionMap.put(People.PHONETIC_NAME, People.PHONETIC_NAME);
peopleProjectionMap.put(People.NOTES, People.NOTES)344         peopleProjectionMap.put(People.NOTES, People.NOTES);
peopleProjectionMap.put(People.TIMES_CONTACTED, People.TIMES_CONTACTED)345         peopleProjectionMap.put(People.TIMES_CONTACTED, People.TIMES_CONTACTED);
peopleProjectionMap.put(People.LAST_TIME_CONTACTED, People.LAST_TIME_CONTACTED)346         peopleProjectionMap.put(People.LAST_TIME_CONTACTED, People.LAST_TIME_CONTACTED);
peopleProjectionMap.put(People.CUSTOM_RINGTONE, People.CUSTOM_RINGTONE)347         peopleProjectionMap.put(People.CUSTOM_RINGTONE, People.CUSTOM_RINGTONE);
peopleProjectionMap.put(People.SEND_TO_VOICEMAIL, People.SEND_TO_VOICEMAIL)348         peopleProjectionMap.put(People.SEND_TO_VOICEMAIL, People.SEND_TO_VOICEMAIL);
peopleProjectionMap.put(People.STARRED, People.STARRED)349         peopleProjectionMap.put(People.STARRED, People.STARRED);
peopleProjectionMap.put(People.PRIMARY_ORGANIZATION_ID, People.PRIMARY_ORGANIZATION_ID)350         peopleProjectionMap.put(People.PRIMARY_ORGANIZATION_ID, People.PRIMARY_ORGANIZATION_ID);
peopleProjectionMap.put(People.PRIMARY_EMAIL_ID, People.PRIMARY_EMAIL_ID)351         peopleProjectionMap.put(People.PRIMARY_EMAIL_ID, People.PRIMARY_EMAIL_ID);
peopleProjectionMap.put(People.PRIMARY_PHONE_ID, People.PRIMARY_PHONE_ID)352         peopleProjectionMap.put(People.PRIMARY_PHONE_ID, People.PRIMARY_PHONE_ID);
353 
354         sPeopleProjectionMap = new ArrayMap<>(peopleProjectionMap);
sPeopleProjectionMap.put(People._ID, People._ID)355         sPeopleProjectionMap.put(People._ID, People._ID);
sPeopleProjectionMap.put(People.NUMBER, People.NUMBER)356         sPeopleProjectionMap.put(People.NUMBER, People.NUMBER);
sPeopleProjectionMap.put(People.TYPE, People.TYPE)357         sPeopleProjectionMap.put(People.TYPE, People.TYPE);
sPeopleProjectionMap.put(People.LABEL, People.LABEL)358         sPeopleProjectionMap.put(People.LABEL, People.LABEL);
sPeopleProjectionMap.put(People.NUMBER_KEY, People.NUMBER_KEY)359         sPeopleProjectionMap.put(People.NUMBER_KEY, People.NUMBER_KEY);
sPeopleProjectionMap.put(People.IM_PROTOCOL, IM_PROTOCOL_SQL + " AS " + People.IM_PROTOCOL)360         sPeopleProjectionMap.put(People.IM_PROTOCOL, IM_PROTOCOL_SQL + " AS " + People.IM_PROTOCOL);
sPeopleProjectionMap.put(People.IM_HANDLE, People.IM_HANDLE)361         sPeopleProjectionMap.put(People.IM_HANDLE, People.IM_HANDLE);
sPeopleProjectionMap.put(People.IM_ACCOUNT, People.IM_ACCOUNT)362         sPeopleProjectionMap.put(People.IM_ACCOUNT, People.IM_ACCOUNT);
sPeopleProjectionMap.put(People.PRESENCE_STATUS, People.PRESENCE_STATUS)363         sPeopleProjectionMap.put(People.PRESENCE_STATUS, People.PRESENCE_STATUS);
sPeopleProjectionMap.put(People.PRESENCE_CUSTOM_STATUS, "(SELECT " + StatusUpdates.STATUS + " FROM " + Tables.STATUS_UPDATES + " JOIN " + Tables.DATA + "   ON(" + StatusUpdatesColumns.DATA_ID + "=" + DataColumns.CONCRETE_ID + ")" + " WHERE " + DataColumns.CONCRETE_RAW_CONTACT_ID + "=people." + People._ID + " ORDER BY " + StatusUpdates.STATUS_TIMESTAMP + " DESC " + " LIMIT 1" + ") AS " + People.PRESENCE_CUSTOM_STATUS)364         sPeopleProjectionMap.put(People.PRESENCE_CUSTOM_STATUS,
365                 "(SELECT " + StatusUpdates.STATUS +
366                 " FROM " + Tables.STATUS_UPDATES +
367                 " JOIN " + Tables.DATA +
368                 "   ON(" + StatusUpdatesColumns.DATA_ID + "=" + DataColumns.CONCRETE_ID + ")" +
369                 " WHERE " + DataColumns.CONCRETE_RAW_CONTACT_ID + "=people." + People._ID +
370                 " ORDER BY " + StatusUpdates.STATUS_TIMESTAMP + " DESC " +
371                 " LIMIT 1" +
372                 ") AS " + People.PRESENCE_CUSTOM_STATUS);
373 
374         sOrganizationProjectionMap = new ArrayMap<>();
sOrganizationProjectionMap.put(android.provider.Contacts.Organizations._ID, android.provider.Contacts.Organizations._ID)375         sOrganizationProjectionMap.put(android.provider.Contacts.Organizations._ID,
376                 android.provider.Contacts.Organizations._ID);
sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.PERSON_ID, android.provider.Contacts.Organizations.PERSON_ID)377         sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.PERSON_ID,
378                 android.provider.Contacts.Organizations.PERSON_ID);
sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.ISPRIMARY, android.provider.Contacts.Organizations.ISPRIMARY)379         sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.ISPRIMARY,
380                 android.provider.Contacts.Organizations.ISPRIMARY);
sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.COMPANY, android.provider.Contacts.Organizations.COMPANY)381         sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.COMPANY,
382                 android.provider.Contacts.Organizations.COMPANY);
sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.TYPE, android.provider.Contacts.Organizations.TYPE)383         sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.TYPE,
384                 android.provider.Contacts.Organizations.TYPE);
sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.LABEL, android.provider.Contacts.Organizations.LABEL)385         sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.LABEL,
386                 android.provider.Contacts.Organizations.LABEL);
sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.TITLE, android.provider.Contacts.Organizations.TITLE)387         sOrganizationProjectionMap.put(android.provider.Contacts.Organizations.TITLE,
388                 android.provider.Contacts.Organizations.TITLE);
389 
390         sContactMethodProjectionMap = new ArrayMap<>(peopleProjectionMap);
sContactMethodProjectionMap.put(ContactMethods._ID, ContactMethods._ID)391         sContactMethodProjectionMap.put(ContactMethods._ID, ContactMethods._ID);
sContactMethodProjectionMap.put(ContactMethods.PERSON_ID, ContactMethods.PERSON_ID)392         sContactMethodProjectionMap.put(ContactMethods.PERSON_ID, ContactMethods.PERSON_ID);
sContactMethodProjectionMap.put(ContactMethods.KIND, ContactMethods.KIND)393         sContactMethodProjectionMap.put(ContactMethods.KIND, ContactMethods.KIND);
sContactMethodProjectionMap.put(ContactMethods.ISPRIMARY, ContactMethods.ISPRIMARY)394         sContactMethodProjectionMap.put(ContactMethods.ISPRIMARY, ContactMethods.ISPRIMARY);
sContactMethodProjectionMap.put(ContactMethods.TYPE, ContactMethods.TYPE)395         sContactMethodProjectionMap.put(ContactMethods.TYPE, ContactMethods.TYPE);
sContactMethodProjectionMap.put(ContactMethods.DATA, ContactMethods.DATA)396         sContactMethodProjectionMap.put(ContactMethods.DATA, ContactMethods.DATA);
sContactMethodProjectionMap.put(ContactMethods.LABEL, ContactMethods.LABEL)397         sContactMethodProjectionMap.put(ContactMethods.LABEL, ContactMethods.LABEL);
sContactMethodProjectionMap.put(ContactMethods.AUX_DATA, ContactMethods.AUX_DATA)398         sContactMethodProjectionMap.put(ContactMethods.AUX_DATA, ContactMethods.AUX_DATA);
399 
400         sPhoneProjectionMap = new ArrayMap<>(peopleProjectionMap);
sPhoneProjectionMap.put(android.provider.Contacts.Phones._ID, android.provider.Contacts.Phones._ID)401         sPhoneProjectionMap.put(android.provider.Contacts.Phones._ID,
402                 android.provider.Contacts.Phones._ID);
sPhoneProjectionMap.put(android.provider.Contacts.Phones.PERSON_ID, android.provider.Contacts.Phones.PERSON_ID)403         sPhoneProjectionMap.put(android.provider.Contacts.Phones.PERSON_ID,
404                 android.provider.Contacts.Phones.PERSON_ID);
sPhoneProjectionMap.put(android.provider.Contacts.Phones.ISPRIMARY, android.provider.Contacts.Phones.ISPRIMARY)405         sPhoneProjectionMap.put(android.provider.Contacts.Phones.ISPRIMARY,
406                 android.provider.Contacts.Phones.ISPRIMARY);
sPhoneProjectionMap.put(android.provider.Contacts.Phones.NUMBER, android.provider.Contacts.Phones.NUMBER)407         sPhoneProjectionMap.put(android.provider.Contacts.Phones.NUMBER,
408                 android.provider.Contacts.Phones.NUMBER);
sPhoneProjectionMap.put(android.provider.Contacts.Phones.TYPE, android.provider.Contacts.Phones.TYPE)409         sPhoneProjectionMap.put(android.provider.Contacts.Phones.TYPE,
410                 android.provider.Contacts.Phones.TYPE);
sPhoneProjectionMap.put(android.provider.Contacts.Phones.LABEL, android.provider.Contacts.Phones.LABEL)411         sPhoneProjectionMap.put(android.provider.Contacts.Phones.LABEL,
412                 android.provider.Contacts.Phones.LABEL);
sPhoneProjectionMap.put(android.provider.Contacts.Phones.NUMBER_KEY, android.provider.Contacts.Phones.NUMBER_KEY)413         sPhoneProjectionMap.put(android.provider.Contacts.Phones.NUMBER_KEY,
414                 android.provider.Contacts.Phones.NUMBER_KEY);
415 
416         sExtensionProjectionMap = new ArrayMap<>();
sExtensionProjectionMap.put(android.provider.Contacts.Extensions._ID, android.provider.Contacts.Extensions._ID)417         sExtensionProjectionMap.put(android.provider.Contacts.Extensions._ID,
418                 android.provider.Contacts.Extensions._ID);
sExtensionProjectionMap.put(android.provider.Contacts.Extensions.PERSON_ID, android.provider.Contacts.Extensions.PERSON_ID)419         sExtensionProjectionMap.put(android.provider.Contacts.Extensions.PERSON_ID,
420                 android.provider.Contacts.Extensions.PERSON_ID);
sExtensionProjectionMap.put(android.provider.Contacts.Extensions.NAME, android.provider.Contacts.Extensions.NAME)421         sExtensionProjectionMap.put(android.provider.Contacts.Extensions.NAME,
422                 android.provider.Contacts.Extensions.NAME);
sExtensionProjectionMap.put(android.provider.Contacts.Extensions.VALUE, android.provider.Contacts.Extensions.VALUE)423         sExtensionProjectionMap.put(android.provider.Contacts.Extensions.VALUE,
424                 android.provider.Contacts.Extensions.VALUE);
425 
426         sGroupProjectionMap = new ArrayMap<>();
sGroupProjectionMap.put(android.provider.Contacts.Groups._ID, android.provider.Contacts.Groups._ID)427         sGroupProjectionMap.put(android.provider.Contacts.Groups._ID,
428                 android.provider.Contacts.Groups._ID);
sGroupProjectionMap.put(android.provider.Contacts.Groups.NAME, android.provider.Contacts.Groups.NAME)429         sGroupProjectionMap.put(android.provider.Contacts.Groups.NAME,
430                 android.provider.Contacts.Groups.NAME);
sGroupProjectionMap.put(android.provider.Contacts.Groups.NOTES, android.provider.Contacts.Groups.NOTES)431         sGroupProjectionMap.put(android.provider.Contacts.Groups.NOTES,
432                 android.provider.Contacts.Groups.NOTES);
sGroupProjectionMap.put(android.provider.Contacts.Groups.SYSTEM_ID, android.provider.Contacts.Groups.SYSTEM_ID)433         sGroupProjectionMap.put(android.provider.Contacts.Groups.SYSTEM_ID,
434                 android.provider.Contacts.Groups.SYSTEM_ID);
435 
436         sGroupMembershipProjectionMap = new ArrayMap<>(sGroupProjectionMap);
sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership._ID, android.provider.Contacts.GroupMembership._ID)437         sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership._ID,
438                 android.provider.Contacts.GroupMembership._ID);
sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership.PERSON_ID, android.provider.Contacts.GroupMembership.PERSON_ID)439         sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership.PERSON_ID,
440                 android.provider.Contacts.GroupMembership.PERSON_ID);
sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership.GROUP_ID, android.provider.Contacts.GroupMembership.GROUP_ID)441         sGroupMembershipProjectionMap.put(android.provider.Contacts.GroupMembership.GROUP_ID,
442                 android.provider.Contacts.GroupMembership.GROUP_ID);
sGroupMembershipProjectionMap.put( android.provider.Contacts.GroupMembership.GROUP_SYNC_ID, android.provider.Contacts.GroupMembership.GROUP_SYNC_ID)443         sGroupMembershipProjectionMap.put(
444                 android.provider.Contacts.GroupMembership.GROUP_SYNC_ID,
445                 android.provider.Contacts.GroupMembership.GROUP_SYNC_ID);
sGroupMembershipProjectionMap.put( android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT, android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT)446         sGroupMembershipProjectionMap.put(
447                 android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT,
448                 android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT);
sGroupMembershipProjectionMap.put( android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT_TYPE, android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT_TYPE)449         sGroupMembershipProjectionMap.put(
450                 android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT_TYPE,
451                 android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT_TYPE);
452 
453         sPhotoProjectionMap = new ArrayMap<>();
sPhotoProjectionMap.put(android.provider.Contacts.Photos._ID, android.provider.Contacts.Photos._ID)454         sPhotoProjectionMap.put(android.provider.Contacts.Photos._ID,
455                 android.provider.Contacts.Photos._ID);
sPhotoProjectionMap.put(android.provider.Contacts.Photos.PERSON_ID, android.provider.Contacts.Photos.PERSON_ID)456         sPhotoProjectionMap.put(android.provider.Contacts.Photos.PERSON_ID,
457                 android.provider.Contacts.Photos.PERSON_ID);
sPhotoProjectionMap.put(android.provider.Contacts.Photos.DATA, android.provider.Contacts.Photos.DATA)458         sPhotoProjectionMap.put(android.provider.Contacts.Photos.DATA,
459                 android.provider.Contacts.Photos.DATA);
sPhotoProjectionMap.put(android.provider.Contacts.Photos.LOCAL_VERSION, android.provider.Contacts.Photos.LOCAL_VERSION)460         sPhotoProjectionMap.put(android.provider.Contacts.Photos.LOCAL_VERSION,
461                 android.provider.Contacts.Photos.LOCAL_VERSION);
sPhotoProjectionMap.put(android.provider.Contacts.Photos.DOWNLOAD_REQUIRED, android.provider.Contacts.Photos.DOWNLOAD_REQUIRED)462         sPhotoProjectionMap.put(android.provider.Contacts.Photos.DOWNLOAD_REQUIRED,
463                 android.provider.Contacts.Photos.DOWNLOAD_REQUIRED);
sPhotoProjectionMap.put(android.provider.Contacts.Photos.EXISTS_ON_SERVER, android.provider.Contacts.Photos.EXISTS_ON_SERVER)464         sPhotoProjectionMap.put(android.provider.Contacts.Photos.EXISTS_ON_SERVER,
465                 android.provider.Contacts.Photos.EXISTS_ON_SERVER);
sPhotoProjectionMap.put(android.provider.Contacts.Photos.SYNC_ERROR, android.provider.Contacts.Photos.SYNC_ERROR)466         sPhotoProjectionMap.put(android.provider.Contacts.Photos.SYNC_ERROR,
467                 android.provider.Contacts.Photos.SYNC_ERROR);
468     }
469 
470     private final Context mContext;
471     private final ContactsDatabaseHelper mDbHelper;
472     private final ContactsProvider2 mContactsProvider;
473     private final NameSplitter mPhoneticNameSplitter;
474     private final GlobalSearchSupport mGlobalSearchSupport;
475 
476     private final SQLiteStatement mDataMimetypeQuery;
477     private final SQLiteStatement mDataRawContactIdQuery;
478 
479     private final ContentValues mValues = new ContentValues();
480     private final ContentValues mValues2 = new ContentValues();
481     private final ContentValues mValues3 = new ContentValues();
482     private boolean mDefaultAccountKnown;
483     private Account mAccount;
484 
485     private final long mMimetypeEmail;
486     private final long mMimetypeIm;
487     private final long mMimetypePostal;
488 
489 
LegacyApiSupport(Context context, ContactsDatabaseHelper contactsDatabaseHelper, ContactsProvider2 contactsProvider, GlobalSearchSupport globalSearchSupport)490     public LegacyApiSupport(Context context, ContactsDatabaseHelper contactsDatabaseHelper,
491             ContactsProvider2 contactsProvider, GlobalSearchSupport globalSearchSupport) {
492         mContext = context;
493         mContactsProvider = contactsProvider;
494         mDbHelper = contactsDatabaseHelper;
495         mGlobalSearchSupport = globalSearchSupport;
496 
497         mPhoneticNameSplitter = new NameSplitter("", "", "", context
498                 .getString(com.android.internal.R.string.common_name_conjunctions), Locale
499                 .getDefault());
500 
501         SQLiteDatabase db = mDbHelper.getReadableDatabase();
502         mDataMimetypeQuery = db.compileStatement(
503                 "SELECT " + DataColumns.MIMETYPE_ID +
504                 " FROM " + Tables.DATA +
505                 " WHERE " + Data._ID + "=?");
506 
507         mDataRawContactIdQuery = db.compileStatement(
508                 "SELECT " + Data.RAW_CONTACT_ID +
509                 " FROM " + Tables.DATA +
510                 " WHERE " + Data._ID + "=?");
511 
512         mMimetypeEmail = mDbHelper.getMimeTypeId(Email.CONTENT_ITEM_TYPE);
513         mMimetypeIm = mDbHelper.getMimeTypeId(Im.CONTENT_ITEM_TYPE);
514         mMimetypePostal = mDbHelper.getMimeTypeId(StructuredPostal.CONTENT_ITEM_TYPE);
515     }
516 
ensureDefaultAccount()517     private void ensureDefaultAccount() {
518         if (!mDefaultAccountKnown) {
519             mAccount = mContactsProvider.getDefaultAccount();
520             mDefaultAccountKnown = true;
521         }
522     }
523 
createDatabase(SQLiteDatabase db)524     public static void createDatabase(SQLiteDatabase db) {
525         Log.i(TAG, "Bootstrapping database legacy support");
526         createViews(db);
527         createSettingsTable(db);
528     }
529 
createViews(SQLiteDatabase db)530     public static void createViews(SQLiteDatabase db) {
531 
532         String peopleColumns = "name." + StructuredName.DISPLAY_NAME
533                         + " AS " + People.NAME + ", " +
534                 Tables.RAW_CONTACTS + "." + RawContactsColumns.DISPLAY_NAME
535                         + " AS " + People.DISPLAY_NAME + ", " +
536                 PHONETIC_NAME_SQL
537                         + " AS " + People.PHONETIC_NAME + " , " +
538                 "note." + Note.NOTE
539                         + " AS " + People.NOTES + ", " +
540                 AccountsColumns.CONCRETE_ACCOUNT_NAME + ", " +
541                 AccountsColumns.CONCRETE_ACCOUNT_TYPE + ", " +
542 
543                 // We no longer return even low-res values from CP1.
544                 // Note if we just use the value 0 below, certain seletion wouldn't work.
545                 "cast(0 as int) AS " + People.TIMES_CONTACTED + ", " +
546                 "cast(0 as int) AS " + People.LAST_TIME_CONTACTED + ", " +
547 
548                 Tables.RAW_CONTACTS + "." + RawContacts.CUSTOM_RINGTONE
549                         + " AS " + People.CUSTOM_RINGTONE + ", " +
550                 Tables.RAW_CONTACTS + "." + RawContacts.SEND_TO_VOICEMAIL
551                         + " AS " + People.SEND_TO_VOICEMAIL + ", " +
552                 Tables.RAW_CONTACTS + "." + RawContacts.STARRED
553                         + " AS " + People.STARRED + ", " +
554                 "organization." + Data._ID
555                         + " AS " + People.PRIMARY_ORGANIZATION_ID + ", " +
556                 "email." + Data._ID
557                         + " AS " + People.PRIMARY_EMAIL_ID + ", " +
558                 "phone." + Data._ID
559                         + " AS " + People.PRIMARY_PHONE_ID + ", " +
560                 "phone." + Phone.NUMBER
561                         + " AS " + People.NUMBER + ", " +
562                 "phone." + Phone.TYPE
563                         + " AS " + People.TYPE + ", " +
564                 "phone." + Phone.LABEL
565                         + " AS " + People.LABEL + ", " +
566                 "_PHONE_NUMBER_STRIPPED_REVERSED(phone." + Phone.NUMBER + ")"
567                         + " AS " + People.NUMBER_KEY;
568 
569         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.PEOPLE + ";");
570         db.execSQL("CREATE VIEW " + LegacyTables.PEOPLE + " AS SELECT " +
571                 RawContactsColumns.CONCRETE_ID
572                         + " AS " + android.provider.Contacts.People._ID + ", " +
573                 peopleColumns +
574                 " FROM " + Tables.RAW_CONTACTS + PEOPLE_JOINS +
575                 " WHERE " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0;");
576 
577         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.ORGANIZATIONS + ";");
578         db.execSQL("CREATE VIEW " + LegacyTables.ORGANIZATIONS + " AS SELECT " +
579                 DataColumns.CONCRETE_ID
580                         + " AS " + android.provider.Contacts.Organizations._ID + ", " +
581                 Data.RAW_CONTACT_ID
582                         + " AS " + android.provider.Contacts.Organizations.PERSON_ID + ", " +
583                 Data.IS_PRIMARY
584                         + " AS " + android.provider.Contacts.Organizations.ISPRIMARY + ", " +
585                 AccountsColumns.CONCRETE_ACCOUNT_NAME + ", " +
586                 AccountsColumns.CONCRETE_ACCOUNT_TYPE + ", " +
587                 Organization.COMPANY
588                         + " AS " + android.provider.Contacts.Organizations.COMPANY + ", " +
589                 Organization.TYPE
590                         + " AS " + android.provider.Contacts.Organizations.TYPE + ", " +
591                 Organization.LABEL
592                         + " AS " + android.provider.Contacts.Organizations.LABEL + ", " +
593                 Organization.TITLE
594                         + " AS " + android.provider.Contacts.Organizations.TITLE +
595                 " FROM " + Tables.DATA_JOIN_MIMETYPE_RAW_CONTACTS +
596                 " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
597                         + Organization.CONTENT_ITEM_TYPE + "'"
598                         + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0" +
599         ";");
600 
601         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.CONTACT_METHODS + ";");
602         db.execSQL("CREATE VIEW " + LegacyTables.CONTACT_METHODS + " AS SELECT " +
603                 DataColumns.CONCRETE_ID
604                         + " AS " + ContactMethods._ID + ", " +
605                 DataColumns.CONCRETE_RAW_CONTACT_ID
606                         + " AS " + ContactMethods.PERSON_ID + ", " +
607                 CONTACT_METHOD_KIND_SQL
608                         + " AS " + ContactMethods.KIND + ", " +
609                 DataColumns.CONCRETE_IS_PRIMARY
610                         + " AS " + ContactMethods.ISPRIMARY + ", " +
611                 Tables.DATA + "." + Email.TYPE
612                         + " AS " + ContactMethods.TYPE + ", " +
613                 CONTACT_METHOD_DATA_SQL
614                         + " AS " + ContactMethods.DATA + ", " +
615                 Tables.DATA + "." + Email.LABEL
616                         + " AS " + ContactMethods.LABEL + ", " +
617                 DataColumns.CONCRETE_DATA14
618                         + " AS " + ContactMethods.AUX_DATA + ", " +
619                 peopleColumns +
620                 " FROM " + Tables.DATA + DATA_JOINS +
621                 " WHERE " + ContactMethods.KIND + " IS NOT NULL"
622                     + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0" +
623         ";");
624 
625 
626         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.PHONES + ";");
627         db.execSQL("CREATE VIEW " + LegacyTables.PHONES + " AS SELECT DISTINCT " +
628                 DataColumns.CONCRETE_ID
629                         + " AS " + android.provider.Contacts.Phones._ID + ", " +
630                 DataColumns.CONCRETE_RAW_CONTACT_ID
631                         + " AS " + android.provider.Contacts.Phones.PERSON_ID + ", " +
632                 DataColumns.CONCRETE_IS_PRIMARY
633                         + " AS " + android.provider.Contacts.Phones.ISPRIMARY + ", " +
634                 Tables.DATA + "." + Phone.NUMBER
635                         + " AS " + android.provider.Contacts.Phones.NUMBER + ", " +
636                 Tables.DATA + "." + Phone.TYPE
637                         + " AS " + android.provider.Contacts.Phones.TYPE + ", " +
638                 Tables.DATA + "." + Phone.LABEL
639                         + " AS " + android.provider.Contacts.Phones.LABEL + ", " +
640                 "_PHONE_NUMBER_STRIPPED_REVERSED(" + Tables.DATA + "." + Phone.NUMBER + ")"
641                         + " AS " + android.provider.Contacts.Phones.NUMBER_KEY + ", " +
642                 peopleColumns +
643                 " FROM " + Tables.DATA
644                         + " JOIN " + Tables.PHONE_LOOKUP
645                         + " ON (" + Tables.DATA + "._id = "
646                                 + Tables.PHONE_LOOKUP + "." + PhoneLookupColumns.DATA_ID + ")"
647                         + DATA_JOINS +
648                 " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
649                         + Phone.CONTENT_ITEM_TYPE + "'"
650                         + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0" +
651         ";");
652 
653         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.EXTENSIONS + ";");
654         db.execSQL("CREATE VIEW " + LegacyTables.EXTENSIONS + " AS SELECT " +
655                 DataColumns.CONCRETE_ID
656                         + " AS " + android.provider.Contacts.Extensions._ID + ", " +
657                 DataColumns.CONCRETE_RAW_CONTACT_ID
658                         + " AS " + android.provider.Contacts.Extensions.PERSON_ID + ", " +
659                 AccountsColumns.CONCRETE_ACCOUNT_NAME + ", " +
660                 AccountsColumns.CONCRETE_ACCOUNT_TYPE + ", " +
661                 ExtensionsColumns.NAME
662                         + " AS " + android.provider.Contacts.Extensions.NAME + ", " +
663                 ExtensionsColumns.VALUE
664                         + " AS " + android.provider.Contacts.Extensions.VALUE +
665                 " FROM " + Tables.DATA_JOIN_MIMETYPE_RAW_CONTACTS +
666                 " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
667                         + android.provider.Contacts.Extensions.CONTENT_ITEM_TYPE + "'"
668                         + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0" +
669         ";");
670 
671         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.GROUPS + ";");
672         db.execSQL("CREATE VIEW " + LegacyTables.GROUPS + " AS SELECT " +
673                 GroupsColumns.CONCRETE_ID + " AS " + android.provider.Contacts.Groups._ID + ", " +
674                 AccountsColumns.CONCRETE_ACCOUNT_NAME + ", " +
675                 AccountsColumns.CONCRETE_ACCOUNT_TYPE + ", " +
676                 Groups.TITLE + " AS " + android.provider.Contacts.Groups.NAME + ", " +
677                 Groups.NOTES + " AS " + android.provider.Contacts.Groups.NOTES + " , " +
678                 Groups.SYSTEM_ID + " AS " + android.provider.Contacts.Groups.SYSTEM_ID +
679                 " FROM " + Tables.GROUPS +
680                 " JOIN " + Tables.ACCOUNTS + " ON (" +
681                 GroupsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID + ")" +
682         ";");
683 
684         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.GROUP_MEMBERSHIP + ";");
685         db.execSQL("CREATE VIEW " + LegacyTables.GROUP_MEMBERSHIP + " AS SELECT " +
686                 DataColumns.CONCRETE_ID
687                         + " AS " + android.provider.Contacts.GroupMembership._ID + ", " +
688                 DataColumns.CONCRETE_RAW_CONTACT_ID
689                         + " AS " + android.provider.Contacts.GroupMembership.PERSON_ID + ", " +
690                 AccountsColumns.CONCRETE_ACCOUNT_NAME + ", " +
691                 AccountsColumns.CONCRETE_ACCOUNT_TYPE + ", " +
692                 GroupMembership.GROUP_ROW_ID
693                         + " AS " + android.provider.Contacts.GroupMembership.GROUP_ID + ", " +
694                 Groups.TITLE
695                         + " AS " + android.provider.Contacts.GroupMembership.NAME + ", " +
696                 Groups.NOTES
697                         + " AS " + android.provider.Contacts.GroupMembership.NOTES + ", " +
698                 Groups.SYSTEM_ID
699                         + " AS " + android.provider.Contacts.GroupMembership.SYSTEM_ID + ", " +
700                 GroupsColumns.CONCRETE_SOURCE_ID
701                         + " AS "
702                         + android.provider.Contacts.GroupMembership.GROUP_SYNC_ID + ", " +
703                 AccountsColumns.CONCRETE_ACCOUNT_NAME
704                         + " AS "
705                         + android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT + ", " +
706                 AccountsColumns.CONCRETE_ACCOUNT_TYPE
707                         + " AS "
708                         + android.provider.Contacts.GroupMembership.GROUP_SYNC_ACCOUNT_TYPE +
709                 " FROM " + Tables.DATA_JOIN_PACKAGES_MIMETYPES_RAW_CONTACTS_GROUPS +
710                 " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
711                         + GroupMembership.CONTENT_ITEM_TYPE + "'"
712                         + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0" +
713         ";");
714 
715         db.execSQL("DROP VIEW IF EXISTS " + LegacyTables.PHOTOS + ";");
716         db.execSQL("CREATE VIEW " + LegacyTables.PHOTOS + " AS SELECT " +
717                 DataColumns.CONCRETE_ID
718                         + " AS " + android.provider.Contacts.Photos._ID + ", " +
719                 DataColumns.CONCRETE_RAW_CONTACT_ID
720                         + " AS " + android.provider.Contacts.Photos.PERSON_ID + ", " +
721                 AccountsColumns.CONCRETE_ACCOUNT_NAME + ", " +
722                 AccountsColumns.CONCRETE_ACCOUNT_TYPE + ", " +
723                 Tables.DATA + "." + Photo.PHOTO
724                         + " AS " + android.provider.Contacts.Photos.DATA + ", " +
725                 "legacy_photo." + LegacyPhotoData.EXISTS_ON_SERVER
726                         + " AS " + android.provider.Contacts.Photos.EXISTS_ON_SERVER + ", " +
727                 "legacy_photo." + LegacyPhotoData.DOWNLOAD_REQUIRED
728                         + " AS " + android.provider.Contacts.Photos.DOWNLOAD_REQUIRED + ", " +
729                 "legacy_photo." + LegacyPhotoData.LOCAL_VERSION
730                         + " AS " + android.provider.Contacts.Photos.LOCAL_VERSION + ", " +
731                 "legacy_photo." + LegacyPhotoData.SYNC_ERROR
732                         + " AS " + android.provider.Contacts.Photos.SYNC_ERROR +
733                 " FROM " + Tables.DATA + DATA_JOINS + LEGACY_PHOTO_JOIN +
734                 " WHERE " + MimetypesColumns.CONCRETE_MIMETYPE + "='"
735                         + Photo.CONTENT_ITEM_TYPE + "'"
736                         + " AND " + Tables.RAW_CONTACTS + "." + RawContacts.DELETED + "=0" +
737         ";");
738 
739     }
740 
createSettingsTable(SQLiteDatabase db)741     public static void createSettingsTable(SQLiteDatabase db) {
742         db.execSQL("DROP TABLE IF EXISTS " + LegacyTables.SETTINGS + ";");
743         db.execSQL("CREATE TABLE " + LegacyTables.SETTINGS + " (" +
744                 android.provider.Contacts.Settings._ID + " INTEGER PRIMARY KEY," +
745                 android.provider.Contacts.Settings._SYNC_ACCOUNT + " TEXT," +
746                 android.provider.Contacts.Settings._SYNC_ACCOUNT_TYPE + " TEXT," +
747                 android.provider.Contacts.Settings.KEY + " STRING NOT NULL," +
748                 android.provider.Contacts.Settings.VALUE + " STRING " +
749         ");");
750     }
751 
insert(Uri uri, ContentValues values)752     public Uri insert(Uri uri, ContentValues values) {
753         ensureDefaultAccount();
754         final int match = sUriMatcher.match(uri);
755         long id = 0;
756         switch (match) {
757             case PEOPLE:
758                 id = insertPeople(values);
759                 break;
760 
761             case ORGANIZATIONS:
762                 id = insertOrganization(values);
763                 break;
764 
765             case PEOPLE_CONTACTMETHODS: {
766                 long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
767                 id = insertContactMethod(rawContactId, values);
768                 break;
769             }
770 
771             case CONTACTMETHODS: {
772                 long rawContactId = getRequiredValue(values, ContactMethods.PERSON_ID);
773                 id = insertContactMethod(rawContactId, values);
774                 break;
775             }
776 
777             case PHONES: {
778                 long rawContactId = getRequiredValue(values,
779                         android.provider.Contacts.Phones.PERSON_ID);
780                 id = insertPhone(rawContactId, values);
781                 break;
782             }
783 
784             case PEOPLE_PHONES: {
785                 long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
786                 id = insertPhone(rawContactId, values);
787                 break;
788             }
789 
790             case EXTENSIONS: {
791                 long rawContactId = getRequiredValue(values,
792                         android.provider.Contacts.Extensions.PERSON_ID);
793                 id = insertExtension(rawContactId, values);
794                 break;
795             }
796 
797             case GROUPS:
798                 id = insertGroup(values);
799                 break;
800 
801             case GROUPMEMBERSHIP: {
802                 long rawContactId = getRequiredValue(values,
803                         android.provider.Contacts.GroupMembership.PERSON_ID);
804                 long groupId = getRequiredValue(values,
805                         android.provider.Contacts.GroupMembership.GROUP_ID);
806                 id = insertGroupMembership(rawContactId, groupId);
807                 break;
808             }
809 
810             default:
811                 throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
812         }
813 
814         if (id < 0) {
815             return null;
816         }
817 
818         final Uri result = ContentUris.withAppendedId(uri, id);
819         onChange(result);
820         return result;
821     }
822 
getRequiredValue(ContentValues values, String column)823     private long getRequiredValue(ContentValues values, String column) {
824         final Long value = values.getAsLong(column);
825         if (value == null) {
826             throw new RuntimeException("Required value: " + column);
827         }
828 
829         return value;
830     }
831 
insertPeople(ContentValues values)832     private long insertPeople(ContentValues values) {
833         parsePeopleValues(values);
834 
835         Uri contactUri = mContactsProvider.insertInTransaction(RawContacts.CONTENT_URI, mValues);
836         long rawContactId = ContentUris.parseId(contactUri);
837 
838         if (mValues2.size() != 0) {
839             mValues2.put(Data.RAW_CONTACT_ID, rawContactId);
840             mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues2);
841         }
842         if (mValues3.size() != 0) {
843             mValues3.put(Data.RAW_CONTACT_ID, rawContactId);
844             mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues3);
845         }
846 
847         return rawContactId;
848     }
849 
insertOrganization(ContentValues values)850     private long insertOrganization(ContentValues values) {
851         parseOrganizationValues(values);
852         ContactsDatabaseHelper.copyLongValue(mValues, Data.RAW_CONTACT_ID,
853                 values, android.provider.Contacts.Organizations.PERSON_ID);
854 
855         Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
856 
857         return ContentUris.parseId(uri);
858     }
859 
insertPhone(long rawContactId, ContentValues values)860     private long insertPhone(long rawContactId, ContentValues values) {
861         parsePhoneValues(values);
862         mValues.put(Data.RAW_CONTACT_ID, rawContactId);
863 
864         Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
865 
866         return ContentUris.parseId(uri);
867     }
868 
insertContactMethod(long rawContactId, ContentValues values)869     private long insertContactMethod(long rawContactId, ContentValues values) {
870         Integer kind = values.getAsInteger(ContactMethods.KIND);
871         if (kind == null) {
872             throw new RuntimeException("Required value: " + ContactMethods.KIND);
873         }
874 
875         parseContactMethodValues(kind, values);
876 
877         mValues.put(Data.RAW_CONTACT_ID, rawContactId);
878         Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
879         return ContentUris.parseId(uri);
880     }
881 
insertExtension(long rawContactId, ContentValues values)882     private long insertExtension(long rawContactId, ContentValues values) {
883         mValues.clear();
884 
885         mValues.put(Data.RAW_CONTACT_ID, rawContactId);
886         mValues.put(Data.MIMETYPE, android.provider.Contacts.Extensions.CONTENT_ITEM_TYPE);
887 
888         parseExtensionValues(values);
889 
890         Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
891         return ContentUris.parseId(uri);
892     }
893 
insertGroup(ContentValues values)894     private long insertGroup(ContentValues values) {
895         parseGroupValues(values);
896 
897         if (mAccount != null) {
898             mValues.put(Groups.ACCOUNT_NAME, mAccount.name);
899             mValues.put(Groups.ACCOUNT_TYPE, mAccount.type);
900         }
901 
902         Uri uri = mContactsProvider.insertInTransaction(Groups.CONTENT_URI, mValues);
903         return ContentUris.parseId(uri);
904     }
905 
insertGroupMembership(long rawContactId, long groupId)906     private long insertGroupMembership(long rawContactId, long groupId) {
907         mValues.clear();
908 
909         mValues.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
910         mValues.put(GroupMembership.RAW_CONTACT_ID, rawContactId);
911         mValues.put(GroupMembership.GROUP_ROW_ID, groupId);
912 
913         Uri uri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
914         return ContentUris.parseId(uri);
915     }
916 
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)917     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
918         ensureDefaultAccount();
919 
920         int match = sUriMatcher.match(uri);
921         int count = 0;
922         switch(match) {
923             case PEOPLE_UPDATE_CONTACT_TIME: {
924                 count = 0; // No longer supported.
925                 break;
926             }
927 
928             case PEOPLE_PHOTO: {
929                 long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
930                 return updatePhoto(rawContactId, values);
931             }
932 
933             case SETTINGS: {
934                 return updateSettings(values);
935             }
936 
937             case GROUPMEMBERSHIP:
938             case GROUPMEMBERSHIP_ID:
939             case -1: {
940                 throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
941             }
942 
943             default: {
944                 count = updateAll(uri, match, values, selection, selectionArgs);
945             }
946         }
947 
948         if (count > 0) {
949             mContext.getContentResolver().notifyChange(uri, null);
950         }
951 
952         return count;
953     }
954 
updateAll(Uri uri, final int match, ContentValues values, String selection, String[] selectionArgs)955     private int updateAll(Uri uri, final int match, ContentValues values, String selection,
956             String[] selectionArgs) {
957         Cursor c = query(uri, IdQuery.COLUMNS, selection, selectionArgs, null, null);
958         if (c == null) {
959             return 0;
960         }
961 
962         int count = 0;
963         try {
964             while (c.moveToNext()) {
965                 long id = c.getLong(IdQuery._ID);
966                 count += update(match, id, values);
967             }
968         } finally {
969             c.close();
970         }
971 
972         return count;
973     }
974 
update(int match, long id, ContentValues values)975     public int update(int match, long id, ContentValues values) {
976         int count = 0;
977         switch(match) {
978             case PEOPLE:
979             case PEOPLE_ID: {
980                 count = updatePeople(id, values);
981                 break;
982             }
983 
984             case ORGANIZATIONS:
985             case ORGANIZATIONS_ID: {
986                 count = updateOrganizations(id, values);
987                 break;
988             }
989 
990             case PHONES:
991             case PHONES_ID: {
992                 count = updatePhones(id, values);
993                 break;
994             }
995 
996             case CONTACTMETHODS:
997             case CONTACTMETHODS_ID: {
998                 count = updateContactMethods(id, values);
999                 break;
1000             }
1001 
1002             case EXTENSIONS:
1003             case EXTENSIONS_ID: {
1004                 count = updateExtensions(id, values);
1005                 break;
1006             }
1007 
1008             case GROUPS:
1009             case GROUPS_ID: {
1010                 count = updateGroups(id, values);
1011                 break;
1012             }
1013 
1014             case PHOTOS:
1015             case PHOTOS_ID:
1016                 count = updatePhotoByDataId(id, values);
1017                 break;
1018         }
1019 
1020         return count;
1021     }
1022 
updatePeople(long rawContactId, ContentValues values)1023     private int updatePeople(long rawContactId, ContentValues values) {
1024         parsePeopleValues(values);
1025 
1026         int count = mContactsProvider.updateInTransaction(RawContacts.CONTENT_URI,
1027                 mValues, RawContacts._ID + "=" + rawContactId, null);
1028 
1029         if (count == 0) {
1030             return 0;
1031         }
1032 
1033         if (mValues2.size() != 0) {
1034             Uri dataUri = findFirstDataRow(rawContactId, StructuredName.CONTENT_ITEM_TYPE);
1035             if (dataUri != null) {
1036                 mContactsProvider.updateInTransaction(dataUri, mValues2, null, null);
1037             } else {
1038                 mValues2.put(Data.RAW_CONTACT_ID, rawContactId);
1039                 mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues2);
1040             }
1041         }
1042 
1043         if (mValues3.size() != 0) {
1044             Uri dataUri = findFirstDataRow(rawContactId, Note.CONTENT_ITEM_TYPE);
1045             if (dataUri != null) {
1046                 mContactsProvider.updateInTransaction(dataUri, mValues3, null, null);
1047             } else {
1048                 mValues3.put(Data.RAW_CONTACT_ID, rawContactId);
1049                 mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues3);
1050             }
1051         }
1052 
1053         return count;
1054     }
1055 
updateOrganizations(long dataId, ContentValues values)1056     private int updateOrganizations(long dataId, ContentValues values) {
1057         parseOrganizationValues(values);
1058 
1059         return mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
1060                 Data._ID + "=" + dataId, null);
1061     }
1062 
updatePhones(long dataId, ContentValues values)1063     private int updatePhones(long dataId, ContentValues values) {
1064         parsePhoneValues(values);
1065 
1066         return mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
1067                 Data._ID + "=" + dataId, null);
1068     }
1069 
updateContactMethods(long dataId, ContentValues values)1070     private int updateContactMethods(long dataId, ContentValues values) {
1071         int kind;
1072 
1073         mDataMimetypeQuery.bindLong(1, dataId);
1074         long mimetype_id;
1075         try {
1076             mimetype_id = mDataMimetypeQuery.simpleQueryForLong();
1077         } catch (SQLiteDoneException e) {
1078             // Data row not found
1079             return 0;
1080         }
1081 
1082         if (mimetype_id == mMimetypeEmail) {
1083             kind = android.provider.Contacts.KIND_EMAIL;
1084         } else if (mimetype_id == mMimetypeIm) {
1085             kind = android.provider.Contacts.KIND_IM;
1086         } else if (mimetype_id == mMimetypePostal) {
1087             kind = android.provider.Contacts.KIND_POSTAL;
1088         } else {
1089 
1090             // Non-legacy kind: return "Not found"
1091             return 0;
1092         }
1093 
1094         parseContactMethodValues(kind, values);
1095 
1096         return mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
1097                 Data._ID + "=" + dataId, null);
1098     }
1099 
updateExtensions(long dataId, ContentValues values)1100     private int updateExtensions(long dataId, ContentValues values) {
1101         parseExtensionValues(values);
1102 
1103         return mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
1104                 Data._ID + "=" + dataId, null);
1105     }
1106 
updateGroups(long groupId, ContentValues values)1107     private int updateGroups(long groupId, ContentValues values) {
1108         parseGroupValues(values);
1109 
1110         return mContactsProvider.updateInTransaction(Groups.CONTENT_URI, mValues,
1111                 Groups._ID + "=" + groupId, null);
1112     }
1113 
updatePhoto(long rawContactId, ContentValues values)1114     private int updatePhoto(long rawContactId, ContentValues values) {
1115 
1116         // TODO check sanctions
1117 
1118         int count;
1119 
1120         long dataId = findFirstDataId(rawContactId, Photo.CONTENT_ITEM_TYPE);
1121 
1122         mValues.clear();
1123         byte[] bytes = values.getAsByteArray(android.provider.Contacts.Photos.DATA);
1124         mValues.put(Photo.PHOTO, bytes);
1125 
1126         if (dataId == -1) {
1127             mValues.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
1128             mValues.put(Data.RAW_CONTACT_ID, rawContactId);
1129             Uri dataUri = mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
1130             dataId = ContentUris.parseId(dataUri);
1131             count = 1;
1132         } else {
1133             Uri dataUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
1134             count = mContactsProvider.updateInTransaction(dataUri, mValues, null, null);
1135         }
1136 
1137         updateLegacyPhotoData(rawContactId, dataId, values);
1138 
1139         return count;
1140     }
1141 
updatePhotoByDataId(long dataId, ContentValues values)1142     private int updatePhotoByDataId(long dataId, ContentValues values) {
1143 
1144         mDataRawContactIdQuery.bindLong(1, dataId);
1145         long rawContactId;
1146 
1147         try {
1148             rawContactId = mDataRawContactIdQuery.simpleQueryForLong();
1149         } catch (SQLiteDoneException e) {
1150             // Data row not found
1151             return 0;
1152         }
1153 
1154         if (values.containsKey(android.provider.Contacts.Photos.DATA)) {
1155             byte[] bytes = values.getAsByteArray(android.provider.Contacts.Photos.DATA);
1156             mValues.clear();
1157             mValues.put(Photo.PHOTO, bytes);
1158             mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
1159                     Data._ID + "=" + dataId, null);
1160         }
1161 
1162         updateLegacyPhotoData(rawContactId, dataId, values);
1163 
1164         return 1;
1165     }
1166 
updateLegacyPhotoData(long rawContactId, long dataId, ContentValues values)1167     private void updateLegacyPhotoData(long rawContactId, long dataId, ContentValues values) {
1168         mValues.clear();
1169         ContactsDatabaseHelper.copyStringValue(mValues, LegacyPhotoData.LOCAL_VERSION,
1170                 values, android.provider.Contacts.Photos.LOCAL_VERSION);
1171         ContactsDatabaseHelper.copyStringValue(mValues, LegacyPhotoData.DOWNLOAD_REQUIRED,
1172                 values, android.provider.Contacts.Photos.DOWNLOAD_REQUIRED);
1173         ContactsDatabaseHelper.copyStringValue(mValues, LegacyPhotoData.EXISTS_ON_SERVER,
1174                 values, android.provider.Contacts.Photos.EXISTS_ON_SERVER);
1175         ContactsDatabaseHelper.copyStringValue(mValues, LegacyPhotoData.SYNC_ERROR,
1176                 values, android.provider.Contacts.Photos.SYNC_ERROR);
1177 
1178         int updated = mContactsProvider.updateInTransaction(Data.CONTENT_URI, mValues,
1179                 Data.MIMETYPE + "='" + LegacyPhotoData.CONTENT_ITEM_TYPE + "'"
1180                         + " AND " + Data.RAW_CONTACT_ID + "=" + rawContactId
1181                         + " AND " + LegacyPhotoData.PHOTO_DATA_ID + "=" + dataId, null);
1182         if (updated == 0) {
1183             mValues.put(Data.RAW_CONTACT_ID, rawContactId);
1184             mValues.put(Data.MIMETYPE, LegacyPhotoData.CONTENT_ITEM_TYPE);
1185             mValues.put(LegacyPhotoData.PHOTO_DATA_ID, dataId);
1186             mContactsProvider.insertInTransaction(Data.CONTENT_URI, mValues);
1187         }
1188     }
1189 
updateSettings(ContentValues values)1190     private int updateSettings(ContentValues values) {
1191         SQLiteDatabase db = mDbHelper.getWritableDatabase();
1192         String accountName = values.getAsString(android.provider.Contacts.Settings._SYNC_ACCOUNT);
1193         String accountType =
1194                 values.getAsString(android.provider.Contacts.Settings._SYNC_ACCOUNT_TYPE);
1195         String key = values.getAsString(android.provider.Contacts.Settings.KEY);
1196         if (key == null) {
1197             throw new IllegalArgumentException("you must specify the key when updating settings");
1198         }
1199         updateSetting(db, accountName, accountType, values);
1200         if (key.equals(android.provider.Contacts.Settings.SYNC_EVERYTHING)) {
1201             mValues.clear();
1202             mValues.put(Settings.SHOULD_SYNC,
1203                     values.getAsInteger(android.provider.Contacts.Settings.VALUE));
1204             String selection;
1205             String[] selectionArgs;
1206             if (accountName != null && accountType != null) {
1207 
1208                 selectionArgs = new String[]{accountName, accountType};
1209                 selection = Settings.ACCOUNT_NAME + "=?"
1210                         + " AND " + Settings.ACCOUNT_TYPE + "=?"
1211                         + " AND " + Settings.DATA_SET + " IS NULL";
1212             } else {
1213                 selectionArgs = null;
1214                 selection = Settings.ACCOUNT_NAME + " IS NULL"
1215                         + " AND " + Settings.ACCOUNT_TYPE + " IS NULL"
1216                         + " AND " + Settings.DATA_SET + " IS NULL";
1217             }
1218             int count = mContactsProvider.updateInTransaction(Settings.CONTENT_URI, mValues,
1219                     selection, selectionArgs);
1220             if (count == 0) {
1221                 mValues.put(Settings.ACCOUNT_NAME, accountName);
1222                 mValues.put(Settings.ACCOUNT_TYPE, accountType);
1223                 mContactsProvider.insertInTransaction(Settings.CONTENT_URI, mValues);
1224             }
1225         }
1226         return 1;
1227     }
1228 
updateSetting(SQLiteDatabase db, String accountName, String accountType, ContentValues values)1229     private void updateSetting(SQLiteDatabase db, String accountName, String accountType,
1230             ContentValues values) {
1231         final String key = values.getAsString(android.provider.Contacts.Settings.KEY);
1232         if (accountName == null || accountType == null) {
1233             db.delete(LegacyTables.SETTINGS, "_sync_account IS NULL AND key=?", new String[]{key});
1234         } else {
1235             db.delete(LegacyTables.SETTINGS, "_sync_account=? AND _sync_account_type=? AND key=?",
1236                     new String[]{accountName, accountType, key});
1237         }
1238         long rowId = db.insert(LegacyTables.SETTINGS,
1239                 android.provider.Contacts.Settings.KEY, values);
1240         if (rowId < 0) {
1241             throw new SQLException("error updating settings with " + values);
1242         }
1243     }
1244 
1245     private interface SettingsMatchQuery {
1246         String SQL =
1247             "SELECT "
1248                     + ContactsContract.Settings.ACCOUNT_NAME + ","
1249                     + ContactsContract.Settings.ACCOUNT_TYPE + ","
1250                     + ContactsContract.Settings.SHOULD_SYNC +
1251             " FROM " + Views.SETTINGS + " LEFT OUTER JOIN " + LegacyTables.SETTINGS +
1252             " ON (" + ContactsContract.Settings.ACCOUNT_NAME + "="
1253                               + android.provider.Contacts.Settings._SYNC_ACCOUNT +
1254                       " AND " + ContactsContract.Settings.ACCOUNT_TYPE + "="
1255                               + android.provider.Contacts.Settings._SYNC_ACCOUNT_TYPE +
1256                       " AND " + ContactsContract.Settings.DATA_SET + " IS NULL" +
1257                       " AND " + android.provider.Contacts.Settings.KEY + "='"
1258                               + android.provider.Contacts.Settings.SYNC_EVERYTHING + "'" +
1259             ")" +
1260             " WHERE " + ContactsContract.Settings.SHOULD_SYNC + "<>"
1261                             + android.provider.Contacts.Settings.VALUE;
1262 
1263         int ACCOUNT_NAME = 0;
1264         int ACCOUNT_TYPE = 1;
1265         int SHOULD_SYNC = 2;
1266     }
1267 
1268     /**
1269      * Brings legacy settings table in sync with the new settings.
1270      */
copySettingsToLegacySettings()1271     public void copySettingsToLegacySettings() {
1272         SQLiteDatabase db = mDbHelper.getWritableDatabase();
1273         Cursor cursor = db.rawQuery(SettingsMatchQuery.SQL, null);
1274         try {
1275             while(cursor.moveToNext()) {
1276                 String accountName = cursor.getString(SettingsMatchQuery.ACCOUNT_NAME);
1277                 String accountType = cursor.getString(SettingsMatchQuery.ACCOUNT_TYPE);
1278                 String value = cursor.getString(SettingsMatchQuery.SHOULD_SYNC);
1279                 mValues.clear();
1280                 mValues.put(android.provider.Contacts.Settings._SYNC_ACCOUNT, accountName);
1281                 mValues.put(android.provider.Contacts.Settings._SYNC_ACCOUNT_TYPE, accountType);
1282                 mValues.put(android.provider.Contacts.Settings.KEY,
1283                         android.provider.Contacts.Settings.SYNC_EVERYTHING);
1284                 mValues.put(android.provider.Contacts.Settings.VALUE, value);
1285                 updateSetting(db, accountName, accountType, mValues);
1286             }
1287         } finally {
1288             cursor.close();
1289         }
1290     }
1291 
parsePeopleValues(ContentValues values)1292     private void parsePeopleValues(ContentValues values) {
1293         mValues.clear();
1294         mValues2.clear();
1295         mValues3.clear();
1296 
1297         ContactsDatabaseHelper.copyStringValue(mValues, RawContacts.CUSTOM_RINGTONE,
1298                 values, People.CUSTOM_RINGTONE);
1299         ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.SEND_TO_VOICEMAIL,
1300                 values, People.SEND_TO_VOICEMAIL);
1301 
1302         // We no longer support the following fields in CP1.
1303         // ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.LAST_TIME_CONTACTED,
1304         //         values, People.LAST_TIME_CONTACTED);
1305         // ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.TIMES_CONTACTED,
1306         //        values, People.TIMES_CONTACTED);
1307         ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.STARRED,
1308                 values, People.STARRED);
1309         if (mAccount != null) {
1310             mValues.put(RawContacts.ACCOUNT_NAME, mAccount.name);
1311             mValues.put(RawContacts.ACCOUNT_TYPE, mAccount.type);
1312         }
1313 
1314         if (values.containsKey(People.NAME) || values.containsKey(People.PHONETIC_NAME)) {
1315             mValues2.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
1316             ContactsDatabaseHelper.copyStringValue(mValues2, StructuredName.DISPLAY_NAME,
1317                     values, People.NAME);
1318             if (values.containsKey(People.PHONETIC_NAME)) {
1319                 String phoneticName = values.getAsString(People.PHONETIC_NAME);
1320                 NameSplitter.Name parsedName = new NameSplitter.Name();
1321                 mPhoneticNameSplitter.split(parsedName, phoneticName);
1322                 mValues2.put(StructuredName.PHONETIC_GIVEN_NAME, parsedName.getGivenNames());
1323                 mValues2.put(StructuredName.PHONETIC_MIDDLE_NAME, parsedName.getMiddleName());
1324                 mValues2.put(StructuredName.PHONETIC_FAMILY_NAME, parsedName.getFamilyName());
1325             }
1326         }
1327 
1328         if (values.containsKey(People.NOTES)) {
1329             mValues3.put(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
1330             ContactsDatabaseHelper.copyStringValue(mValues3, Note.NOTE, values, People.NOTES);
1331         }
1332     }
1333 
parseOrganizationValues(ContentValues values)1334     private void parseOrganizationValues(ContentValues values) {
1335         mValues.clear();
1336 
1337         mValues.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
1338 
1339         ContactsDatabaseHelper.copyLongValue(mValues, Data.IS_PRIMARY,
1340                 values, android.provider.Contacts.Organizations.ISPRIMARY);
1341 
1342         ContactsDatabaseHelper.copyStringValue(mValues, Organization.COMPANY,
1343                 values, android.provider.Contacts.Organizations.COMPANY);
1344 
1345         // TYPE values happen to remain the same between V1 and V2 - can just copy the value
1346         ContactsDatabaseHelper.copyLongValue(mValues, Organization.TYPE,
1347                 values, android.provider.Contacts.Organizations.TYPE);
1348 
1349         ContactsDatabaseHelper.copyStringValue(mValues, Organization.LABEL,
1350                 values, android.provider.Contacts.Organizations.LABEL);
1351         ContactsDatabaseHelper.copyStringValue(mValues, Organization.TITLE,
1352                 values, android.provider.Contacts.Organizations.TITLE);
1353     }
1354 
parsePhoneValues(ContentValues values)1355     private void parsePhoneValues(ContentValues values) {
1356         mValues.clear();
1357 
1358         mValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
1359 
1360         ContactsDatabaseHelper.copyLongValue(mValues, Data.IS_PRIMARY,
1361                 values, android.provider.Contacts.Phones.ISPRIMARY);
1362 
1363         ContactsDatabaseHelper.copyStringValue(mValues, Phone.NUMBER,
1364                 values, android.provider.Contacts.Phones.NUMBER);
1365 
1366         // TYPE values happen to remain the same between V1 and V2 - can just copy the value
1367         ContactsDatabaseHelper.copyLongValue(mValues, Phone.TYPE,
1368                 values, android.provider.Contacts.Phones.TYPE);
1369 
1370         ContactsDatabaseHelper.copyStringValue(mValues, Phone.LABEL,
1371                 values, android.provider.Contacts.Phones.LABEL);
1372     }
1373 
parseContactMethodValues(int kind, ContentValues values)1374     private void parseContactMethodValues(int kind, ContentValues values) {
1375         mValues.clear();
1376 
1377         ContactsDatabaseHelper.copyLongValue(mValues, Data.IS_PRIMARY, values,
1378                 ContactMethods.ISPRIMARY);
1379 
1380         switch (kind) {
1381             case android.provider.Contacts.KIND_EMAIL: {
1382                 copyCommonFields(values, Email.CONTENT_ITEM_TYPE, Email.TYPE, Email.LABEL,
1383                         Data.DATA14);
1384                 ContactsDatabaseHelper.copyStringValue(mValues, Email.DATA, values,
1385                         ContactMethods.DATA);
1386                 break;
1387             }
1388 
1389             case android.provider.Contacts.KIND_IM: {
1390                 String protocol = values.getAsString(ContactMethods.DATA);
1391                 if (protocol.startsWith("pre:")) {
1392                     mValues.put(Im.PROTOCOL, Integer.parseInt(protocol.substring(4)));
1393                 } else if (protocol.startsWith("custom:")) {
1394                     mValues.put(Im.PROTOCOL, Im.PROTOCOL_CUSTOM);
1395                     mValues.put(Im.CUSTOM_PROTOCOL, protocol.substring(7));
1396                 }
1397 
1398                 copyCommonFields(values, Im.CONTENT_ITEM_TYPE, Im.TYPE, Im.LABEL, Data.DATA14);
1399                 break;
1400             }
1401 
1402             case android.provider.Contacts.KIND_POSTAL: {
1403                 copyCommonFields(values, StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.TYPE,
1404                         StructuredPostal.LABEL, Data.DATA14);
1405                 ContactsDatabaseHelper.copyStringValue(mValues, StructuredPostal.FORMATTED_ADDRESS,
1406                         values, ContactMethods.DATA);
1407                 break;
1408             }
1409         }
1410     }
1411 
copyCommonFields(ContentValues values, String mimeType, String typeColumn, String labelColumn, String auxDataColumn)1412     private void copyCommonFields(ContentValues values, String mimeType, String typeColumn,
1413             String labelColumn, String auxDataColumn) {
1414         mValues.put(Data.MIMETYPE, mimeType);
1415         ContactsDatabaseHelper.copyLongValue(mValues, typeColumn, values,
1416                 ContactMethods.TYPE);
1417         ContactsDatabaseHelper.copyStringValue(mValues, labelColumn, values,
1418                 ContactMethods.LABEL);
1419         ContactsDatabaseHelper.copyStringValue(mValues, auxDataColumn, values,
1420                 ContactMethods.AUX_DATA);
1421     }
1422 
parseGroupValues(ContentValues values)1423     private void parseGroupValues(ContentValues values) {
1424         mValues.clear();
1425 
1426         ContactsDatabaseHelper.copyStringValue(mValues, Groups.TITLE,
1427                 values, android.provider.Contacts.Groups.NAME);
1428         ContactsDatabaseHelper.copyStringValue(mValues, Groups.NOTES,
1429                 values, android.provider.Contacts.Groups.NOTES);
1430         ContactsDatabaseHelper.copyStringValue(mValues, Groups.SYSTEM_ID,
1431                 values, android.provider.Contacts.Groups.SYSTEM_ID);
1432     }
1433 
parseExtensionValues(ContentValues values)1434     private void parseExtensionValues(ContentValues values) {
1435         ContactsDatabaseHelper.copyStringValue(mValues, ExtensionsColumns.NAME,
1436                 values, android.provider.Contacts.People.Extensions.NAME);
1437         ContactsDatabaseHelper.copyStringValue(mValues, ExtensionsColumns.VALUE,
1438                 values, android.provider.Contacts.People.Extensions.VALUE);
1439     }
1440 
findFirstDataRow(long rawContactId, String contentItemType)1441     private Uri findFirstDataRow(long rawContactId, String contentItemType) {
1442         long dataId = findFirstDataId(rawContactId, contentItemType);
1443         if (dataId == -1) {
1444             return null;
1445         }
1446 
1447         return ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
1448     }
1449 
findFirstDataId(long rawContactId, String mimeType)1450     private long findFirstDataId(long rawContactId, String mimeType) {
1451         long dataId = -1;
1452         Cursor c = mContactsProvider.query(Data.CONTENT_URI, IdQuery.COLUMNS,
1453                 Data.RAW_CONTACT_ID + "=" + rawContactId + " AND "
1454                         + Data.MIMETYPE + "='" + mimeType + "'",
1455                 null, null);
1456         try {
1457             if (c.moveToFirst()) {
1458                 dataId = c.getLong(IdQuery._ID);
1459             }
1460         } finally {
1461             c.close();
1462         }
1463         return dataId;
1464     }
1465 
1466 
delete(Uri uri, String selection, String[] selectionArgs)1467     public int delete(Uri uri, String selection, String[] selectionArgs) {
1468         final int match = sUriMatcher.match(uri);
1469         if (match == -1 || match == SETTINGS) {
1470             throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
1471         }
1472 
1473         Cursor c = query(uri, IdQuery.COLUMNS, selection, selectionArgs, null, null);
1474         if (c == null) {
1475             return 0;
1476         }
1477 
1478         int count = 0;
1479         try {
1480             while (c.moveToNext()) {
1481                 long id = c.getLong(IdQuery._ID);
1482                 count += delete(uri, match, id);
1483             }
1484         } finally {
1485             c.close();
1486         }
1487 
1488         return count;
1489     }
1490 
delete(Uri uri, int match, long id)1491     public int delete(Uri uri, int match, long id) {
1492         int count = 0;
1493         switch (match) {
1494             case PEOPLE:
1495             case PEOPLE_ID:
1496                 count = mContactsProvider.deleteRawContact(id, mDbHelper.getContactId(id), false);
1497                 break;
1498 
1499             case PEOPLE_PHOTO:
1500                 mValues.clear();
1501                 mValues.putNull(android.provider.Contacts.Photos.DATA);
1502                 updatePhoto(id, mValues);
1503                 break;
1504 
1505             case ORGANIZATIONS:
1506             case ORGANIZATIONS_ID:
1507                 count = mContactsProvider.deleteData(id, ORGANIZATION_MIME_TYPES);
1508                 break;
1509 
1510             case CONTACTMETHODS:
1511             case CONTACTMETHODS_ID:
1512                 count = mContactsProvider.deleteData(id, CONTACT_METHOD_MIME_TYPES);
1513                 break;
1514 
1515             case PHONES:
1516             case PHONES_ID:
1517                 count = mContactsProvider.deleteData(id, PHONE_MIME_TYPES);
1518                 break;
1519 
1520             case EXTENSIONS:
1521             case EXTENSIONS_ID:
1522                 count = mContactsProvider.deleteData(id, EXTENSION_MIME_TYPES);
1523                 break;
1524 
1525             case PHOTOS:
1526             case PHOTOS_ID:
1527                 count = mContactsProvider.deleteData(id, PHOTO_MIME_TYPES);
1528                 break;
1529 
1530             case GROUPMEMBERSHIP:
1531             case GROUPMEMBERSHIP_ID:
1532                 count = mContactsProvider.deleteData(id, GROUP_MEMBERSHIP_MIME_TYPES);
1533                 break;
1534 
1535             case GROUPS:
1536             case GROUPS_ID:
1537                 count = mContactsProvider.deleteGroup(uri, id, false);
1538                 break;
1539 
1540             default:
1541                 throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
1542         }
1543 
1544         return count;
1545     }
1546 
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, String limit)1547     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
1548             String sortOrder, String limit) {
1549         ensureDefaultAccount();
1550 
1551         final SQLiteDatabase db = mDbHelper.getReadableDatabase();
1552         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
1553         String groupBy = null;
1554 
1555         final int match = sUriMatcher.match(uri);
1556         switch (match) {
1557             case PEOPLE: {
1558                 qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
1559                 qb.setProjectionMap(sPeopleProjectionMap);
1560                 applyRawContactsAccount(qb);
1561                 break;
1562             }
1563 
1564             case PEOPLE_ID:
1565                 qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
1566                 qb.setProjectionMap(sPeopleProjectionMap);
1567                 applyRawContactsAccount(qb);
1568                 qb.appendWhere(" AND " + People._ID + "=");
1569                 qb.appendWhere(uri.getPathSegments().get(1));
1570                 break;
1571 
1572             case PEOPLE_FILTER: {
1573                 qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
1574                 qb.setProjectionMap(sPeopleProjectionMap);
1575                 applyRawContactsAccount(qb);
1576                 String filterParam = uri.getPathSegments().get(2);
1577                 qb.appendWhere(" AND " + People._ID + " IN "
1578                         + getRawContactsByFilterAsNestedQuery(filterParam));
1579                 break;
1580             }
1581 
1582             case GROUP_NAME_MEMBERS:
1583                 qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
1584                 qb.setProjectionMap(sPeopleProjectionMap);
1585                 applyRawContactsAccount(qb);
1586                 String group = uri.getPathSegments().get(2);
1587                 qb.appendWhere(" AND " + buildGroupNameMatchWhereClause(group));
1588                 break;
1589 
1590             case GROUP_SYSTEM_ID_MEMBERS:
1591                 qb.setTables(LegacyTables.PEOPLE_JOIN_PRESENCE);
1592                 qb.setProjectionMap(sPeopleProjectionMap);
1593                 applyRawContactsAccount(qb);
1594                 String systemId = uri.getPathSegments().get(2);
1595                 qb.appendWhere(" AND " + buildGroupSystemIdMatchWhereClause(systemId));
1596                 break;
1597 
1598             case ORGANIZATIONS:
1599                 qb.setTables(LegacyTables.ORGANIZATIONS + " organizations");
1600                 qb.setProjectionMap(sOrganizationProjectionMap);
1601                 applyRawContactsAccount(qb);
1602                 break;
1603 
1604             case ORGANIZATIONS_ID:
1605                 qb.setTables(LegacyTables.ORGANIZATIONS + " organizations");
1606                 qb.setProjectionMap(sOrganizationProjectionMap);
1607                 applyRawContactsAccount(qb);
1608                 qb.appendWhere(" AND " + android.provider.Contacts.Organizations._ID + "=");
1609                 qb.appendWhere(uri.getPathSegments().get(1));
1610                 break;
1611 
1612             case PEOPLE_ORGANIZATIONS:
1613                 qb.setTables(LegacyTables.ORGANIZATIONS + " organizations");
1614                 qb.setProjectionMap(sOrganizationProjectionMap);
1615                 applyRawContactsAccount(qb);
1616                 qb.appendWhere(" AND " + android.provider.Contacts.Organizations.PERSON_ID + "=");
1617                 qb.appendWhere(uri.getPathSegments().get(1));
1618                 break;
1619 
1620             case PEOPLE_ORGANIZATIONS_ID:
1621                 qb.setTables(LegacyTables.ORGANIZATIONS + " organizations");
1622                 qb.setProjectionMap(sOrganizationProjectionMap);
1623                 applyRawContactsAccount(qb);
1624                 qb.appendWhere(" AND " + android.provider.Contacts.Organizations.PERSON_ID + "=");
1625                 qb.appendWhere(uri.getPathSegments().get(1));
1626                 qb.appendWhere(" AND " + android.provider.Contacts.Organizations._ID + "=");
1627                 qb.appendWhere(uri.getPathSegments().get(3));
1628                 break;
1629 
1630             case CONTACTMETHODS:
1631                 qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
1632                 qb.setProjectionMap(sContactMethodProjectionMap);
1633                 applyRawContactsAccount(qb);
1634                 break;
1635 
1636             case CONTACTMETHODS_ID:
1637                 qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
1638                 qb.setProjectionMap(sContactMethodProjectionMap);
1639                 applyRawContactsAccount(qb);
1640                 qb.appendWhere(" AND " + ContactMethods._ID + "=");
1641                 qb.appendWhere(uri.getPathSegments().get(1));
1642                 break;
1643 
1644             case CONTACTMETHODS_EMAIL:
1645                 qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
1646                 qb.setProjectionMap(sContactMethodProjectionMap);
1647                 applyRawContactsAccount(qb);
1648                 qb.appendWhere(" AND " + ContactMethods.KIND + "="
1649                         + android.provider.Contacts.KIND_EMAIL);
1650                 break;
1651 
1652             case PEOPLE_CONTACTMETHODS:
1653                 qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
1654                 qb.setProjectionMap(sContactMethodProjectionMap);
1655                 applyRawContactsAccount(qb);
1656                 qb.appendWhere(" AND " + ContactMethods.PERSON_ID + "=");
1657                 qb.appendWhere(uri.getPathSegments().get(1));
1658                 qb.appendWhere(" AND " + ContactMethods.KIND + " IS NOT NULL");
1659                 break;
1660 
1661             case PEOPLE_CONTACTMETHODS_ID:
1662                 qb.setTables(LegacyTables.CONTACT_METHODS + " contact_methods");
1663                 qb.setProjectionMap(sContactMethodProjectionMap);
1664                 applyRawContactsAccount(qb);
1665                 qb.appendWhere(" AND " + ContactMethods.PERSON_ID + "=");
1666                 qb.appendWhere(uri.getPathSegments().get(1));
1667                 qb.appendWhere(" AND " + ContactMethods._ID + "=");
1668                 qb.appendWhere(uri.getPathSegments().get(3));
1669                 qb.appendWhere(" AND " + ContactMethods.KIND + " IS NOT NULL");
1670                 break;
1671 
1672             case PHONES:
1673                 qb.setTables(LegacyTables.PHONES + " phones");
1674                 qb.setProjectionMap(sPhoneProjectionMap);
1675                 applyRawContactsAccount(qb);
1676                 break;
1677 
1678             case PHONES_ID:
1679                 qb.setTables(LegacyTables.PHONES + " phones");
1680                 qb.setProjectionMap(sPhoneProjectionMap);
1681                 applyRawContactsAccount(qb);
1682                 qb.appendWhere(" AND " + android.provider.Contacts.Phones._ID + "=");
1683                 qb.appendWhere(uri.getPathSegments().get(1));
1684                 break;
1685 
1686             case PHONES_FILTER:
1687                 qb.setTables(LegacyTables.PHONES + " phones");
1688                 qb.setProjectionMap(sPhoneProjectionMap);
1689                 applyRawContactsAccount(qb);
1690                 if (uri.getPathSegments().size() > 2) {
1691                     String filterParam = uri.getLastPathSegment();
1692                     qb.appendWhere(" AND person =");
1693                     qb.appendWhere(mDbHelper.buildPhoneLookupAsNestedQuery(filterParam));
1694                     qb.setDistinct(true);
1695                 }
1696                 break;
1697 
1698             case PEOPLE_PHONES:
1699                 qb.setTables(LegacyTables.PHONES + " phones");
1700                 qb.setProjectionMap(sPhoneProjectionMap);
1701                 applyRawContactsAccount(qb);
1702                 qb.appendWhere(" AND " + android.provider.Contacts.Phones.PERSON_ID + "=");
1703                 qb.appendWhere(uri.getPathSegments().get(1));
1704                 break;
1705 
1706             case PEOPLE_PHONES_ID:
1707                 qb.setTables(LegacyTables.PHONES + " phones");
1708                 qb.setProjectionMap(sPhoneProjectionMap);
1709                 applyRawContactsAccount(qb);
1710                 qb.appendWhere(" AND " + android.provider.Contacts.Phones.PERSON_ID + "=");
1711                 qb.appendWhere(uri.getPathSegments().get(1));
1712                 qb.appendWhere(" AND " + android.provider.Contacts.Phones._ID + "=");
1713                 qb.appendWhere(uri.getPathSegments().get(3));
1714                 break;
1715 
1716             case EXTENSIONS:
1717                 qb.setTables(LegacyTables.EXTENSIONS + " extensions");
1718                 qb.setProjectionMap(sExtensionProjectionMap);
1719                 applyRawContactsAccount(qb);
1720                 break;
1721 
1722             case EXTENSIONS_ID:
1723                 qb.setTables(LegacyTables.EXTENSIONS + " extensions");
1724                 qb.setProjectionMap(sExtensionProjectionMap);
1725                 applyRawContactsAccount(qb);
1726                 qb.appendWhere(" AND " + android.provider.Contacts.Extensions._ID + "=");
1727                 qb.appendWhere(uri.getPathSegments().get(1));
1728                 break;
1729 
1730             case PEOPLE_EXTENSIONS:
1731                 qb.setTables(LegacyTables.EXTENSIONS + " extensions");
1732                 qb.setProjectionMap(sExtensionProjectionMap);
1733                 applyRawContactsAccount(qb);
1734                 qb.appendWhere(" AND " + android.provider.Contacts.Extensions.PERSON_ID + "=");
1735                 qb.appendWhere(uri.getPathSegments().get(1));
1736                 break;
1737 
1738             case PEOPLE_EXTENSIONS_ID:
1739                 qb.setTables(LegacyTables.EXTENSIONS + " extensions");
1740                 qb.setProjectionMap(sExtensionProjectionMap);
1741                 applyRawContactsAccount(qb);
1742                 qb.appendWhere(" AND " + android.provider.Contacts.Extensions.PERSON_ID + "=");
1743                 qb.appendWhere(uri.getPathSegments().get(1));
1744                 qb.appendWhere(" AND " + android.provider.Contacts.Extensions._ID + "=");
1745                 qb.appendWhere(uri.getPathSegments().get(3));
1746                 break;
1747 
1748             case GROUPS:
1749                 qb.setTables(LegacyTables.GROUPS + " groups");
1750                 qb.setProjectionMap(sGroupProjectionMap);
1751                 applyGroupAccount(qb);
1752                 break;
1753 
1754             case GROUPS_ID:
1755                 qb.setTables(LegacyTables.GROUPS + " groups");
1756                 qb.setProjectionMap(sGroupProjectionMap);
1757                 applyGroupAccount(qb);
1758                 qb.appendWhere(" AND " + android.provider.Contacts.Groups._ID + "=");
1759                 qb.appendWhere(uri.getPathSegments().get(1));
1760                 break;
1761 
1762             case GROUPMEMBERSHIP:
1763                 qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
1764                 qb.setProjectionMap(sGroupMembershipProjectionMap);
1765                 applyRawContactsAccount(qb);
1766                 break;
1767 
1768             case GROUPMEMBERSHIP_ID:
1769                 qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
1770                 qb.setProjectionMap(sGroupMembershipProjectionMap);
1771                 applyRawContactsAccount(qb);
1772                 qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership._ID + "=");
1773                 qb.appendWhere(uri.getPathSegments().get(1));
1774                 break;
1775 
1776             case PEOPLE_GROUPMEMBERSHIP:
1777                 qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
1778                 qb.setProjectionMap(sGroupMembershipProjectionMap);
1779                 applyRawContactsAccount(qb);
1780                 qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership.PERSON_ID + "=");
1781                 qb.appendWhere(uri.getPathSegments().get(1));
1782                 break;
1783 
1784             case PEOPLE_GROUPMEMBERSHIP_ID:
1785                 qb.setTables(LegacyTables.GROUP_MEMBERSHIP + " groupmembership");
1786                 qb.setProjectionMap(sGroupMembershipProjectionMap);
1787                 applyRawContactsAccount(qb);
1788                 qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership.PERSON_ID + "=");
1789                 qb.appendWhere(uri.getPathSegments().get(1));
1790                 qb.appendWhere(" AND " + android.provider.Contacts.GroupMembership._ID + "=");
1791                 qb.appendWhere(uri.getPathSegments().get(3));
1792                 break;
1793 
1794             case PEOPLE_PHOTO:
1795                 qb.setTables(LegacyTables.PHOTOS + " photos");
1796                 qb.setProjectionMap(sPhotoProjectionMap);
1797                 applyRawContactsAccount(qb);
1798                 qb.appendWhere(" AND " + android.provider.Contacts.Photos.PERSON_ID + "=");
1799                 qb.appendWhere(uri.getPathSegments().get(1));
1800                 limit = "1";
1801                 break;
1802 
1803             case PHOTOS:
1804                 qb.setTables(LegacyTables.PHOTOS + " photos");
1805                 qb.setProjectionMap(sPhotoProjectionMap);
1806                 applyRawContactsAccount(qb);
1807                 break;
1808 
1809             case PHOTOS_ID:
1810                 qb.setTables(LegacyTables.PHOTOS + " photos");
1811                 qb.setProjectionMap(sPhotoProjectionMap);
1812                 applyRawContactsAccount(qb);
1813                 qb.appendWhere(" AND " + android.provider.Contacts.Photos._ID + "=");
1814                 qb.appendWhere(uri.getPathSegments().get(1));
1815                 break;
1816 
1817             case SEARCH_SUGGESTIONS:
1818                 return mGlobalSearchSupport.handleSearchSuggestionsQuery(
1819                         db, uri, projection, limit, null);
1820 
1821             case SEARCH_SHORTCUT: {
1822                 String lookupKey = uri.getLastPathSegment();
1823                 String filter = ContactsProvider2.getQueryParameter(uri, "filter");
1824                 return mGlobalSearchSupport.handleSearchShortcutRefresh(
1825                         db, projection, lookupKey, filter, null);
1826             }
1827 
1828             case DELETED_PEOPLE:
1829             case DELETED_GROUPS:
1830                 throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
1831 
1832             case SETTINGS:
1833                 copySettingsToLegacySettings();
1834                 qb.setTables(LegacyTables.SETTINGS);
1835                 break;
1836 
1837             default:
1838                 throw new IllegalArgumentException(mDbHelper.exceptionMessage(uri));
1839         }
1840 
1841         // Perform the query and set the notification uri
1842         final Cursor c = qb.query(db, projection, selection, selectionArgs,
1843                 groupBy, null, sortOrder, limit);
1844         if (c != null) {
1845             c.setNotificationUri(mContext.getContentResolver(),
1846                     android.provider.Contacts.CONTENT_URI);
1847         }
1848         return c;
1849     }
1850 
applyRawContactsAccount(SQLiteQueryBuilder qb)1851     private void applyRawContactsAccount(SQLiteQueryBuilder qb) {
1852         StringBuilder sb = new StringBuilder();
1853         appendRawContactsAccount(sb);
1854         qb.appendWhere(sb.toString());
1855     }
1856 
appendRawContactsAccount(StringBuilder sb)1857     private void appendRawContactsAccount(StringBuilder sb) {
1858         if (mAccount != null) {
1859             sb.append(RawContacts.ACCOUNT_NAME + "=");
1860             DatabaseUtils.appendEscapedSQLString(sb, mAccount.name);
1861             sb.append(" AND " + RawContacts.ACCOUNT_TYPE + "=");
1862             DatabaseUtils.appendEscapedSQLString(sb, mAccount.type);
1863         } else {
1864             sb.append(RawContacts.ACCOUNT_NAME + " IS ");
1865             MoreDatabaseUtils.appendEscapedSQLStringOrLiteralNull(
1866                     sb, AccountWithDataSet.LOCAL.getAccountName());
1867             sb.append(" AND ").append(RawContacts.ACCOUNT_TYPE + " IS ");
1868             MoreDatabaseUtils.appendEscapedSQLStringOrLiteralNull(
1869                     sb, AccountWithDataSet.LOCAL.getAccountType());
1870         }
1871     }
1872 
applyGroupAccount(SQLiteQueryBuilder qb)1873     private void applyGroupAccount(SQLiteQueryBuilder qb) {
1874         StringBuilder sb = new StringBuilder();
1875         appendGroupAccount(sb);
1876         qb.appendWhere(sb.toString());
1877     }
1878 
appendGroupAccount(StringBuilder sb)1879     private void appendGroupAccount(StringBuilder sb) {
1880         if (mAccount != null) {
1881             sb.append(Groups.ACCOUNT_NAME + "=");
1882             DatabaseUtils.appendEscapedSQLString(sb, mAccount.name);
1883             sb.append(" AND " + Groups.ACCOUNT_TYPE + "=");
1884             DatabaseUtils.appendEscapedSQLString(sb, mAccount.type);
1885         } else {
1886             sb.append(Groups.ACCOUNT_NAME + " IS ");
1887             MoreDatabaseUtils.appendEscapedSQLStringOrLiteralNull(
1888                     sb, AccountWithDataSet.LOCAL.getAccountName());
1889             sb.append(" AND " + Groups.ACCOUNT_TYPE + " IS ");
1890             MoreDatabaseUtils.appendEscapedSQLStringOrLiteralNull(
1891                     sb, AccountWithDataSet.LOCAL.getAccountType());
1892         }
1893     }
1894 
1895     /**
1896      * Build a WHERE clause that restricts the query to match people that are a member of
1897      * a group with a particular name. The projection map of the query must include
1898      * {@link People#_ID}.
1899      *
1900      * @param groupName The name of the group
1901      * @return The where clause.
1902      */
buildGroupNameMatchWhereClause(String groupName)1903     private String buildGroupNameMatchWhereClause(String groupName) {
1904         return "people._id IN "
1905                 + "(SELECT " + DataColumns.CONCRETE_RAW_CONTACT_ID
1906                 + " FROM " + Tables.DATA_JOIN_MIMETYPES
1907                 + " WHERE " + Data.MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
1908                         + "' AND " + GroupMembership.GROUP_ROW_ID + "="
1909                                 + "(SELECT " + Tables.GROUPS + "." + Groups._ID
1910                                 + " FROM " + Tables.GROUPS
1911                                 + " WHERE " + Groups.TITLE + "="
1912                                         + DatabaseUtils.sqlEscapeString(groupName) + "))";
1913     }
1914 
1915     /**
1916      * Build a WHERE clause that restricts the query to match people that are a member of
1917      * a group with a particular system id. The projection map of the query must include
1918      * {@link People#_ID}.
1919      *
1920      * @return The where clause.
1921      */
buildGroupSystemIdMatchWhereClause(String systemId)1922     private String buildGroupSystemIdMatchWhereClause(String systemId) {
1923         return "people._id IN "
1924                 + "(SELECT " + DataColumns.CONCRETE_RAW_CONTACT_ID
1925                 + " FROM " + Tables.DATA_JOIN_MIMETYPES
1926                 + " WHERE " + Data.MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
1927                         + "' AND " + GroupMembership.GROUP_ROW_ID + "="
1928                                 + "(SELECT " + Tables.GROUPS + "." + Groups._ID
1929                                 + " FROM " + Tables.GROUPS
1930                                 + " WHERE " + Groups.SYSTEM_ID + "="
1931                                         + DatabaseUtils.sqlEscapeString(systemId) + "))";
1932     }
1933 
getRawContactsByFilterAsNestedQuery(String filterParam)1934     private String getRawContactsByFilterAsNestedQuery(String filterParam) {
1935         StringBuilder sb = new StringBuilder();
1936         String normalizedName = NameNormalizer.normalize(filterParam);
1937         if (TextUtils.isEmpty(normalizedName)) {
1938             // Effectively an empty IN clause - SQL syntax does not allow an actual empty list here
1939             sb.append("(0)");
1940         } else {
1941             sb.append("(" +
1942                     "SELECT " + NameLookupColumns.RAW_CONTACT_ID +
1943                     " FROM " + Tables.NAME_LOOKUP +
1944                     " WHERE " + NameLookupColumns.NORMALIZED_NAME +
1945                     " GLOB '");
1946             // Should not use a "?" argument placeholder here, because
1947             // that would prevent the SQL optimizer from using the index on NORMALIZED_NAME.
1948             sb.append(normalizedName);
1949             sb.append("*' AND " + NameLookupColumns.NAME_TYPE + " IN ("
1950                     + NameLookupType.NAME_COLLATION_KEY + ","
1951                     + NameLookupType.NICKNAME);
1952             if (true) {
1953                 sb.append("," + NameLookupType.EMAIL_BASED_NICKNAME);
1954             }
1955             sb.append("))");
1956         }
1957         return sb.toString();
1958     }
1959 
1960     /**
1961      * Called when a change has been made.
1962      *
1963      * @param uri the uri that the change was made to
1964      */
onChange(Uri uri)1965     private void onChange(Uri uri) {
1966         mContext.getContentResolver().notifyChange(android.provider.Contacts.CONTENT_URI, null);
1967     }
1968 
getType(Uri uri)1969     public String getType(Uri uri) {
1970         int match = sUriMatcher.match(uri);
1971         switch (match) {
1972             case EXTENSIONS:
1973             case PEOPLE_EXTENSIONS:
1974                 return Extensions.CONTENT_TYPE;
1975             case EXTENSIONS_ID:
1976             case PEOPLE_EXTENSIONS_ID:
1977                 return Extensions.CONTENT_ITEM_TYPE;
1978             case PEOPLE:
1979                 return "vnd.android.cursor.dir/person";
1980             case PEOPLE_ID:
1981                 return "vnd.android.cursor.item/person";
1982             case PEOPLE_PHONES:
1983                 return "vnd.android.cursor.dir/phone";
1984             case PEOPLE_PHONES_ID:
1985                 return "vnd.android.cursor.item/phone";
1986             case PEOPLE_CONTACTMETHODS:
1987                 return "vnd.android.cursor.dir/contact-methods";
1988             case PEOPLE_CONTACTMETHODS_ID:
1989                 return getContactMethodType(uri);
1990             case PHONES:
1991                 return "vnd.android.cursor.dir/phone";
1992             case PHONES_ID:
1993                 return "vnd.android.cursor.item/phone";
1994             case PHONES_FILTER:
1995                 return "vnd.android.cursor.dir/phone";
1996             case PHOTOS_ID:
1997                 return "vnd.android.cursor.item/photo";
1998             case PHOTOS:
1999                 return "vnd.android.cursor.dir/photo";
2000             case PEOPLE_PHOTO:
2001                 return "vnd.android.cursor.item/photo";
2002             case CONTACTMETHODS:
2003                 return "vnd.android.cursor.dir/contact-methods";
2004             case CONTACTMETHODS_ID:
2005                 return getContactMethodType(uri);
2006             case ORGANIZATIONS:
2007                 return "vnd.android.cursor.dir/organizations";
2008             case ORGANIZATIONS_ID:
2009                 return "vnd.android.cursor.item/organization";
2010             case SEARCH_SUGGESTIONS:
2011                 return SearchManager.SUGGEST_MIME_TYPE;
2012             case SEARCH_SHORTCUT:
2013                 return SearchManager.SHORTCUT_MIME_TYPE;
2014             default:
2015                 throw new IllegalArgumentException(mDbHelper.exceptionMessage(uri));
2016         }
2017     }
2018 
getContactMethodType(Uri url)2019     private String getContactMethodType(Uri url) {
2020         String mime = null;
2021 
2022         Cursor c = query(url, new String[] {ContactMethods.KIND}, null, null, null, null);
2023         if (c != null) {
2024             try {
2025                 if (c.moveToFirst()) {
2026                     int kind = c.getInt(0);
2027                     switch (kind) {
2028                     case android.provider.Contacts.KIND_EMAIL:
2029                         mime = "vnd.android.cursor.item/email";
2030                         break;
2031 
2032                     case android.provider.Contacts.KIND_IM:
2033                         mime = "vnd.android.cursor.item/jabber-im";
2034                         break;
2035 
2036                     case android.provider.Contacts.KIND_POSTAL:
2037                         mime = "vnd.android.cursor.item/postal-address";
2038                         break;
2039                     }
2040                 }
2041             } finally {
2042                 c.close();
2043             }
2044         }
2045         return mime;
2046     }
2047 }
2048