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