ContactsDatabaseHelper.java revision 1440fb9d8e4edefb57336532734f7cbab13a955f
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
17package com.android.providers.contacts;
18
19import android.content.ComponentName;
20import android.content.ContentResolver;
21import android.content.ContentValues;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.ApplicationInfo;
25import android.content.pm.PackageManager;
26import android.content.pm.PackageManager.NameNotFoundException;
27import android.content.pm.UserInfo;
28import android.content.res.Resources;
29import android.database.CharArrayBuffer;
30import android.database.Cursor;
31import android.database.DatabaseUtils;
32import android.database.SQLException;
33import android.database.sqlite.SQLiteConstraintException;
34import android.database.sqlite.SQLiteDatabase;
35import android.database.sqlite.SQLiteDoneException;
36import android.database.sqlite.SQLiteException;
37import android.database.sqlite.SQLiteOpenHelper;
38import android.database.sqlite.SQLiteQueryBuilder;
39import android.database.sqlite.SQLiteStatement;
40import android.net.Uri;
41import android.os.Binder;
42import android.os.Bundle;
43import android.os.SystemClock;
44import android.os.UserManager;
45import android.provider.BaseColumns;
46import android.provider.CallLog.Calls;
47import android.provider.ContactsContract;
48import android.provider.ContactsContract.AggregationExceptions;
49import android.provider.ContactsContract.CommonDataKinds.Email;
50import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
51import android.provider.ContactsContract.CommonDataKinds.Im;
52import android.provider.ContactsContract.CommonDataKinds.Nickname;
53import android.provider.ContactsContract.CommonDataKinds.Organization;
54import android.provider.ContactsContract.CommonDataKinds.Phone;
55import android.provider.ContactsContract.CommonDataKinds.SipAddress;
56import android.provider.ContactsContract.CommonDataKinds.StructuredName;
57import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
58import android.provider.ContactsContract.Contacts;
59import android.provider.ContactsContract.Contacts.Photo;
60import android.provider.ContactsContract.Data;
61import android.provider.ContactsContract.Directory;
62import android.provider.ContactsContract.DisplayNameSources;
63import android.provider.ContactsContract.DisplayPhoto;
64import android.provider.ContactsContract.FullNameStyle;
65import android.provider.ContactsContract.Groups;
66import android.provider.ContactsContract.MetadataSync;
67import android.provider.ContactsContract.PhoneticNameStyle;
68import android.provider.ContactsContract.PhotoFiles;
69import android.provider.ContactsContract.PinnedPositions;
70import android.provider.ContactsContract.RawContacts;
71import android.provider.ContactsContract.Settings;
72import android.provider.ContactsContract.StatusUpdates;
73import android.provider.ContactsContract.StreamItemPhotos;
74import android.provider.ContactsContract.StreamItems;
75import android.provider.VoicemailContract;
76import android.provider.VoicemailContract.Voicemails;
77import android.telephony.PhoneNumberUtils;
78import android.telephony.SubscriptionInfo;
79import android.telephony.SubscriptionManager;
80import android.text.TextUtils;
81import android.text.util.Rfc822Token;
82import android.text.util.Rfc822Tokenizer;
83import android.util.Log;
84
85import com.android.common.content.SyncStateContentProviderHelper;
86import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
87import com.android.providers.contacts.database.ContactsTableUtil;
88import com.android.providers.contacts.database.DeletedContactsTableUtil;
89import com.android.providers.contacts.database.MoreDatabaseUtils;
90import com.android.providers.contacts.util.NeededForTesting;
91import com.google.android.collect.Sets;
92import com.google.common.annotations.VisibleForTesting;
93
94import libcore.icu.ICU;
95
96import java.util.Locale;
97import java.util.Set;
98import java.util.concurrent.ConcurrentHashMap;
99
100/**
101 * Database helper for contacts. Designed as a singleton to make sure that all
102 * {@link android.content.ContentProvider} users get the same reference.
103 * Provides handy methods for maintaining package and mime-type lookup tables.
104 */
105public class ContactsDatabaseHelper extends SQLiteOpenHelper {
106
107    /**
108     * Contacts DB version ranges:
109     * <pre>
110     *   0-98    Cupcake/Donut
111     *   100-199 Eclair
112     *   200-299 Eclair-MR1
113     *   300-349 Froyo
114     *   350-399 Gingerbread
115     *   400-499 Honeycomb
116     *   500-549 Honeycomb-MR1
117     *   550-599 Honeycomb-MR2
118     *   600-699 Ice Cream Sandwich
119     *   700-799 Jelly Bean
120     *   800-899 Kitkat
121     *   900-999 Lollipop
122     *   1000-1099 M
123     * </pre>
124     */
125    static final int DATABASE_VERSION = 1010;
126
127    public interface Tables {
128        public static final String CONTACTS = "contacts";
129        public static final String DELETED_CONTACTS = "deleted_contacts";
130        public static final String RAW_CONTACTS = "raw_contacts";
131        public static final String STREAM_ITEMS = "stream_items";
132        public static final String STREAM_ITEM_PHOTOS = "stream_item_photos";
133        public static final String PHOTO_FILES = "photo_files";
134        public static final String PACKAGES = "packages";
135        public static final String MIMETYPES = "mimetypes";
136        public static final String PHONE_LOOKUP = "phone_lookup";
137        public static final String NAME_LOOKUP = "name_lookup";
138        public static final String AGGREGATION_EXCEPTIONS = "agg_exceptions";
139        public static final String SETTINGS = "settings";
140        public static final String DATA = "data";
141        public static final String GROUPS = "groups";
142        public static final String PRESENCE = "presence";
143        public static final String AGGREGATED_PRESENCE = "agg_presence";
144        public static final String NICKNAME_LOOKUP = "nickname_lookup";
145        public static final String CALLS = "calls";
146        public static final String STATUS_UPDATES = "status_updates";
147        public static final String PROPERTIES = "properties";
148        public static final String ACCOUNTS = "accounts";
149        public static final String VISIBLE_CONTACTS = "visible_contacts";
150        public static final String DIRECTORIES = "directories";
151        public static final String DEFAULT_DIRECTORY = "default_directory";
152        public static final String SEARCH_INDEX = "search_index";
153        public static final String VOICEMAIL_STATUS = "voicemail_status";
154        public static final String METADATA_SYNC = "metadata_sync";
155        public static final String PRE_AUTHORIZED_URIS = "pre_authorized_uris";
156
157        // This list of tables contains auto-incremented sequences.
158        public static final String[] SEQUENCE_TABLES = new String[] {
159                CONTACTS,
160                RAW_CONTACTS,
161                STREAM_ITEMS,
162                STREAM_ITEM_PHOTOS,
163                PHOTO_FILES,
164                DATA,
165                GROUPS,
166                CALLS,
167                DIRECTORIES};
168
169        /**
170         * For {@link android.provider.ContactsContract.DataUsageFeedback}. The table structure
171         * itself is not exposed outside.
172         */
173        public static final String DATA_USAGE_STAT = "data_usage_stat";
174
175        public static final String DATA_JOIN_MIMETYPES = "data "
176                + "JOIN mimetypes ON (data.mimetype_id = mimetypes._id)";
177
178        public static final String DATA_JOIN_RAW_CONTACTS = "data "
179                + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id)";
180
181        // NOTE: If you want to refer to account name/type/data_set, AccountsColumns.CONCRETE_XXX
182        // MUST be used, as upgraded raw_contacts may have the account info columns too.
183        public static final String DATA_JOIN_MIMETYPE_RAW_CONTACTS = "data "
184                + "JOIN mimetypes ON (data.mimetype_id = mimetypes._id) "
185                + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id)"
186                + " JOIN " + Tables.ACCOUNTS + " ON ("
187                    + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
188                    + ")";
189
190        // NOTE: This requires late binding of GroupMembership MIME-type
191        // TODO Consolidate settings and accounts
192        public static final String RAW_CONTACTS_JOIN_SETTINGS_DATA_GROUPS = Tables.RAW_CONTACTS
193                + " JOIN " + Tables.ACCOUNTS + " ON ("
194                +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
195                    + ")"
196                + "LEFT OUTER JOIN " + Tables.SETTINGS + " ON ("
197                    + AccountsColumns.CONCRETE_ACCOUNT_NAME + "="
198                        + SettingsColumns.CONCRETE_ACCOUNT_NAME + " AND "
199                    + AccountsColumns.CONCRETE_ACCOUNT_TYPE + "="
200                        + SettingsColumns.CONCRETE_ACCOUNT_TYPE + " AND "
201                    + "((" + AccountsColumns.CONCRETE_DATA_SET + " IS NULL AND "
202                            + SettingsColumns.CONCRETE_DATA_SET + " IS NULL) OR ("
203                        + AccountsColumns.CONCRETE_DATA_SET + "="
204                            + SettingsColumns.CONCRETE_DATA_SET + "))) "
205                + "LEFT OUTER JOIN data ON (data.mimetype_id=? AND "
206                    + "data.raw_contact_id = raw_contacts._id) "
207                + "LEFT OUTER JOIN groups ON (groups._id = data." + GroupMembership.GROUP_ROW_ID
208                + ")";
209
210        // NOTE: This requires late binding of GroupMembership MIME-type
211        // TODO Add missing DATA_SET join -- or just consolidate settings and accounts
212        public static final String SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS = "settings "
213                + "LEFT OUTER JOIN raw_contacts ON ("
214                    + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=(SELECT "
215                        + AccountsColumns.CONCRETE_ID
216                        + " FROM " + Tables.ACCOUNTS
217                        + " WHERE "
218                            + "(" + AccountsColumns.CONCRETE_ACCOUNT_NAME
219                                + "=" + SettingsColumns.CONCRETE_ACCOUNT_NAME + ") AND "
220                            + "(" + AccountsColumns.CONCRETE_ACCOUNT_TYPE
221                                + "=" + SettingsColumns.CONCRETE_ACCOUNT_TYPE + ")))"
222                + "LEFT OUTER JOIN data ON (data.mimetype_id=? AND "
223                    + "data.raw_contact_id = raw_contacts._id) "
224                + "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)";
225
226        public static final String CONTACTS_JOIN_RAW_CONTACTS_DATA_FILTERED_BY_GROUPMEMBERSHIP =
227                Tables.CONTACTS
228                    + " INNER JOIN " + Tables.RAW_CONTACTS
229                        + " ON (" + RawContactsColumns.CONCRETE_CONTACT_ID + "="
230                            + ContactsColumns.CONCRETE_ID
231                        + ")"
232                    + " INNER JOIN " + Tables.DATA
233                        + " ON (" + DataColumns.CONCRETE_DATA1 + "=" + GroupsColumns.CONCRETE_ID
234                        + " AND "
235                        + DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID
236                        + " AND "
237                        + DataColumns.CONCRETE_MIMETYPE_ID + "="
238                            + "(SELECT " + MimetypesColumns._ID
239                            + " FROM " + Tables.MIMETYPES
240                            + " WHERE "
241                            + MimetypesColumns.CONCRETE_MIMETYPE + "="
242                                + "'" + GroupMembership.CONTENT_ITEM_TYPE + "'"
243                            + ")"
244                        + ")";
245
246        // NOTE: If you want to refer to account name/type/data_set, AccountsColumns.CONCRETE_XXX
247        // MUST be used, as upgraded raw_contacts may have the account info columns too.
248        public static final String DATA_JOIN_PACKAGES_MIMETYPES_RAW_CONTACTS_GROUPS = "data "
249                + "JOIN mimetypes ON (data.mimetype_id = mimetypes._id) "
250                + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) "
251                + " JOIN " + Tables.ACCOUNTS + " ON ("
252                    + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
253                    + ")"
254                + "LEFT OUTER JOIN packages ON (data.package_id = packages._id) "
255                + "LEFT OUTER JOIN groups "
256                + "  ON (mimetypes.mimetype='" + GroupMembership.CONTENT_ITEM_TYPE + "' "
257                + "      AND groups._id = data." + GroupMembership.GROUP_ROW_ID + ") ";
258
259        public static final String ACTIVITIES_JOIN_MIMETYPES = "activities "
260                + "LEFT OUTER JOIN mimetypes ON (activities.mimetype_id = mimetypes._id)";
261
262        public static final String ACTIVITIES_JOIN_PACKAGES_MIMETYPES_RAW_CONTACTS_CONTACTS =
263                "activities "
264                + "LEFT OUTER JOIN packages ON (activities.package_id = packages._id) "
265                + "LEFT OUTER JOIN mimetypes ON (activities.mimetype_id = mimetypes._id) "
266                + "LEFT OUTER JOIN raw_contacts ON (activities.author_contact_id = " +
267                        "raw_contacts._id) "
268                + "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)";
269
270        public static final String NAME_LOOKUP_JOIN_RAW_CONTACTS = "name_lookup "
271                + "INNER JOIN view_raw_contacts ON (name_lookup.raw_contact_id = "
272                + "view_raw_contacts._id)";
273
274        public static final String RAW_CONTACTS_JOIN_ACCOUNTS = Tables.RAW_CONTACTS
275                + " JOIN " + Tables.ACCOUNTS + " ON ("
276                + AccountsColumns.CONCRETE_ID + "=" + RawContactsColumns.CONCRETE_ACCOUNT_ID
277                + ")";
278    }
279
280    public interface Joins {
281        /**
282         * Join string intended to be used with the GROUPS table/view.  The main table must be named
283         * as "groups".
284         *
285         * Adds the "group_member_count column" to the query, which will be null if a group has
286         * no members.  Use ifnull(group_member_count, 0) if 0 is needed instead.
287         */
288        public static final String GROUP_MEMBER_COUNT =
289                " LEFT OUTER JOIN (SELECT "
290                        + "data.data1 AS member_count_group_id, "
291                        + "COUNT(data.raw_contact_id) AS group_member_count "
292                    + "FROM data "
293                    + "WHERE "
294                        + "data.mimetype_id = (SELECT _id FROM mimetypes WHERE "
295                            + "mimetypes.mimetype = '" + GroupMembership.CONTENT_ITEM_TYPE + "')"
296                    + "GROUP BY member_count_group_id) AS member_count_table" // End of inner query
297                + " ON (groups._id = member_count_table.member_count_group_id)";
298    }
299
300    public interface Views {
301        public static final String DATA = "view_data";
302        public static final String RAW_CONTACTS = "view_raw_contacts";
303        public static final String CONTACTS = "view_contacts";
304        public static final String ENTITIES = "view_entities";
305        public static final String RAW_ENTITIES = "view_raw_entities";
306        public static final String GROUPS = "view_groups";
307        public static final String DATA_USAGE_STAT = "view_data_usage_stat";
308        public static final String STREAM_ITEMS = "view_stream_items";
309        public static final String METADATA_SYNC = "view_metadata_sync";
310    }
311
312    public interface Projections {
313        String[] ID = new String[] {BaseColumns._ID};
314        String[] LITERAL_ONE = new String[] {"1"};
315    }
316
317    /**
318     * Property names for {@link ContactsDatabaseHelper#getProperty} and
319     * {@link ContactsDatabaseHelper#setProperty}.
320     */
321    public interface DbProperties {
322        String DIRECTORY_SCAN_COMPLETE = "directoryScanComplete";
323        String AGGREGATION_ALGORITHM = "aggregation_v2";
324        String KNOWN_ACCOUNTS = "known_accounts";
325        String ICU_VERSION = "icu_version";
326        String LOCALE = "locale";
327        String DATABASE_TIME_CREATED = "database_time_created";
328        String CALL_LOG_LAST_SYNCED = "call_log_last_synced";
329    }
330
331    public interface Clauses {
332        final String HAVING_NO_GROUPS = "COUNT(" + DataColumns.CONCRETE_GROUP_ID + ") == 0";
333
334        final String GROUP_BY_ACCOUNT_CONTACT_ID = SettingsColumns.CONCRETE_ACCOUNT_NAME + ","
335                + SettingsColumns.CONCRETE_ACCOUNT_TYPE + "," + RawContacts.CONTACT_ID;
336
337        String LOCAL_ACCOUNT_ID =
338                "(SELECT " + AccountsColumns._ID +
339                " FROM " + Tables.ACCOUNTS +
340                " WHERE " +
341                    AccountsColumns.ACCOUNT_NAME + " IS NULL AND " +
342                    AccountsColumns.ACCOUNT_TYPE + " IS NULL AND " +
343                    AccountsColumns.DATA_SET + " IS NULL)";
344
345        final String RAW_CONTACT_IS_LOCAL = RawContactsColumns.CONCRETE_ACCOUNT_ID
346                + "=" + LOCAL_ACCOUNT_ID;
347
348        final String ZERO_GROUP_MEMBERSHIPS = "COUNT(" + GroupsColumns.CONCRETE_ID + ")=0";
349
350        final String OUTER_RAW_CONTACTS = "outer_raw_contacts";
351        final String OUTER_RAW_CONTACTS_ID = OUTER_RAW_CONTACTS + "." + RawContacts._ID;
352
353        final String CONTACT_IS_VISIBLE =
354                "SELECT " +
355                    "MAX((SELECT (CASE WHEN " +
356                        "(CASE" +
357                            " WHEN " + RAW_CONTACT_IS_LOCAL +
358                            " THEN 1 " +
359                            " WHEN " + ZERO_GROUP_MEMBERSHIPS +
360                            " THEN " + Settings.UNGROUPED_VISIBLE +
361                            " ELSE MAX(" + Groups.GROUP_VISIBLE + ")" +
362                         "END)=1 THEN 1 ELSE 0 END)" +
363                " FROM " + Tables.RAW_CONTACTS_JOIN_SETTINGS_DATA_GROUPS +
364                " WHERE " + RawContactsColumns.CONCRETE_ID + "=" + OUTER_RAW_CONTACTS_ID + "))" +
365                " FROM " + Tables.RAW_CONTACTS + " AS " + OUTER_RAW_CONTACTS +
366                " WHERE " + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID +
367                " GROUP BY " + RawContacts.CONTACT_ID;
368
369        final String GROUP_HAS_ACCOUNT_AND_SOURCE_ID = Groups.SOURCE_ID + "=? AND "
370                + GroupsColumns.ACCOUNT_ID + "=?";
371
372        public static final String CONTACT_VISIBLE =
373            "EXISTS (SELECT _id FROM " + Tables.VISIBLE_CONTACTS
374                + " WHERE " + Tables.CONTACTS +"." + Contacts._ID
375                        + "=" + Tables.VISIBLE_CONTACTS +"." + Contacts._ID + ")";
376
377        public static final String CONTACT_IN_DEFAULT_DIRECTORY =
378                "EXISTS (SELECT _id FROM " + Tables.DEFAULT_DIRECTORY
379                        + " WHERE " + Tables.CONTACTS +"." + Contacts._ID
380                        + "=" + Tables.DEFAULT_DIRECTORY +"." + Contacts._ID + ")";
381    }
382
383    public interface ContactsColumns {
384        public static final String LAST_STATUS_UPDATE_ID = "status_update_id";
385
386        public static final String CONCRETE_ID = Tables.CONTACTS + "." + BaseColumns._ID;
387
388        public static final String CONCRETE_PHOTO_FILE_ID = Tables.CONTACTS + "."
389                + Contacts.PHOTO_FILE_ID;
390        public static final String CONCRETE_TIMES_CONTACTED = Tables.CONTACTS + "."
391                + Contacts.TIMES_CONTACTED;
392        public static final String CONCRETE_LAST_TIME_CONTACTED = Tables.CONTACTS + "."
393                + Contacts.LAST_TIME_CONTACTED;
394        public static final String CONCRETE_STARRED = Tables.CONTACTS + "." + Contacts.STARRED;
395        public static final String CONCRETE_PINNED = Tables.CONTACTS + "." + Contacts.PINNED;
396        public static final String CONCRETE_CUSTOM_RINGTONE = Tables.CONTACTS + "."
397                + Contacts.CUSTOM_RINGTONE;
398        public static final String CONCRETE_SEND_TO_VOICEMAIL = Tables.CONTACTS + "."
399                + Contacts.SEND_TO_VOICEMAIL;
400        public static final String CONCRETE_LOOKUP_KEY = Tables.CONTACTS + "."
401                + Contacts.LOOKUP_KEY;
402        public static final String CONCRETE_CONTACT_LAST_UPDATED_TIMESTAMP = Tables.CONTACTS + "."
403                + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP;
404        public static final String PHONEBOOK_LABEL_PRIMARY = "phonebook_label";
405        public static final String PHONEBOOK_BUCKET_PRIMARY = "phonebook_bucket";
406        public static final String PHONEBOOK_LABEL_ALTERNATIVE = "phonebook_label_alt";
407        public static final String PHONEBOOK_BUCKET_ALTERNATIVE = "phonebook_bucket_alt";
408    }
409
410    public interface RawContactsColumns {
411        public static final String CONCRETE_ID =
412                Tables.RAW_CONTACTS + "." + BaseColumns._ID;
413
414        public static final String ACCOUNT_ID = "account_id";
415        public static final String CONCRETE_ACCOUNT_ID = Tables.RAW_CONTACTS + "." + ACCOUNT_ID;
416        public static final String CONCRETE_SOURCE_ID =
417                Tables.RAW_CONTACTS + "." + RawContacts.SOURCE_ID;
418        public static final String CONCRETE_BACKUP_ID =
419                Tables.RAW_CONTACTS + "." + RawContacts.BACKUP_ID;
420        public static final String CONCRETE_VERSION =
421                Tables.RAW_CONTACTS + "." + RawContacts.VERSION;
422        public static final String CONCRETE_DIRTY =
423                Tables.RAW_CONTACTS + "." + RawContacts.DIRTY;
424        public static final String CONCRETE_DELETED =
425                Tables.RAW_CONTACTS + "." + RawContacts.DELETED;
426        public static final String CONCRETE_SYNC1 =
427                Tables.RAW_CONTACTS + "." + RawContacts.SYNC1;
428        public static final String CONCRETE_SYNC2 =
429                Tables.RAW_CONTACTS + "." + RawContacts.SYNC2;
430        public static final String CONCRETE_SYNC3 =
431                Tables.RAW_CONTACTS + "." + RawContacts.SYNC3;
432        public static final String CONCRETE_SYNC4 =
433                Tables.RAW_CONTACTS + "." + RawContacts.SYNC4;
434        public static final String CONCRETE_CUSTOM_RINGTONE =
435                Tables.RAW_CONTACTS + "." + RawContacts.CUSTOM_RINGTONE;
436        public static final String CONCRETE_SEND_TO_VOICEMAIL =
437                Tables.RAW_CONTACTS + "." + RawContacts.SEND_TO_VOICEMAIL;
438        public static final String CONCRETE_LAST_TIME_CONTACTED =
439                Tables.RAW_CONTACTS + "." + RawContacts.LAST_TIME_CONTACTED;
440        public static final String CONCRETE_TIMES_CONTACTED =
441                Tables.RAW_CONTACTS + "." + RawContacts.TIMES_CONTACTED;
442        public static final String CONCRETE_STARRED =
443                Tables.RAW_CONTACTS + "." + RawContacts.STARRED;
444        public static final String CONCRETE_PINNED =
445                Tables.RAW_CONTACTS + "." + RawContacts.PINNED;
446
447        public static final String DISPLAY_NAME = RawContacts.DISPLAY_NAME_PRIMARY;
448        public static final String DISPLAY_NAME_SOURCE = RawContacts.DISPLAY_NAME_SOURCE;
449        public static final String AGGREGATION_NEEDED = "aggregation_needed";
450
451        public static final String CONCRETE_DISPLAY_NAME =
452                Tables.RAW_CONTACTS + "." + DISPLAY_NAME;
453        public static final String CONCRETE_CONTACT_ID =
454                Tables.RAW_CONTACTS + "." + RawContacts.CONTACT_ID;
455        public static final String PHONEBOOK_LABEL_PRIMARY =
456            ContactsColumns.PHONEBOOK_LABEL_PRIMARY;
457        public static final String PHONEBOOK_BUCKET_PRIMARY =
458            ContactsColumns.PHONEBOOK_BUCKET_PRIMARY;
459        public static final String PHONEBOOK_LABEL_ALTERNATIVE =
460            ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE;
461        public static final String PHONEBOOK_BUCKET_ALTERNATIVE =
462            ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE;
463
464        /**
465         * This column is no longer used, but we keep it in the table so an upgraded database
466         * will look the same as a new database. This reduces the chance of OEMs adding a second
467         * column with the same name.
468         */
469        public static final String NAME_VERIFIED_OBSOLETE = "name_verified";
470    }
471
472    public interface ViewRawContactsColumns {
473        String CONCRETE_ACCOUNT_NAME = Views.RAW_CONTACTS + "." + RawContacts.ACCOUNT_NAME;
474        String CONCRETE_ACCOUNT_TYPE = Views.RAW_CONTACTS + "." + RawContacts.ACCOUNT_TYPE;
475        String CONCRETE_DATA_SET = Views.RAW_CONTACTS + "." + RawContacts.DATA_SET;
476    }
477
478    public interface DataColumns {
479        public static final String PACKAGE_ID = "package_id";
480        public static final String MIMETYPE_ID = "mimetype_id";
481
482        public static final String CONCRETE_ID = Tables.DATA + "." + BaseColumns._ID;
483        public static final String CONCRETE_MIMETYPE_ID = Tables.DATA + "." + MIMETYPE_ID;
484        public static final String CONCRETE_RAW_CONTACT_ID = Tables.DATA + "."
485                + Data.RAW_CONTACT_ID;
486        public static final String CONCRETE_GROUP_ID = Tables.DATA + "."
487                + GroupMembership.GROUP_ROW_ID;
488
489        public static final String CONCRETE_DATA1 = Tables.DATA + "." + Data.DATA1;
490        public static final String CONCRETE_DATA2 = Tables.DATA + "." + Data.DATA2;
491        public static final String CONCRETE_DATA3 = Tables.DATA + "." + Data.DATA3;
492        public static final String CONCRETE_DATA4 = Tables.DATA + "." + Data.DATA4;
493        public static final String CONCRETE_DATA5 = Tables.DATA + "." + Data.DATA5;
494        public static final String CONCRETE_DATA6 = Tables.DATA + "." + Data.DATA6;
495        public static final String CONCRETE_DATA7 = Tables.DATA + "." + Data.DATA7;
496        public static final String CONCRETE_DATA8 = Tables.DATA + "." + Data.DATA8;
497        public static final String CONCRETE_DATA9 = Tables.DATA + "." + Data.DATA9;
498        public static final String CONCRETE_DATA10 = Tables.DATA + "." + Data.DATA10;
499        public static final String CONCRETE_DATA11 = Tables.DATA + "." + Data.DATA11;
500        public static final String CONCRETE_DATA12 = Tables.DATA + "." + Data.DATA12;
501        public static final String CONCRETE_DATA13 = Tables.DATA + "." + Data.DATA13;
502        public static final String CONCRETE_DATA14 = Tables.DATA + "." + Data.DATA14;
503        public static final String CONCRETE_DATA15 = Tables.DATA + "." + Data.DATA15;
504        public static final String CONCRETE_IS_PRIMARY = Tables.DATA + "." + Data.IS_PRIMARY;
505        public static final String CONCRETE_PACKAGE_ID = Tables.DATA + "." + PACKAGE_ID;
506    }
507
508    // Used only for legacy API support.
509    public interface ExtensionsColumns {
510        public static final String NAME = Data.DATA1;
511        public static final String VALUE = Data.DATA2;
512    }
513
514    public interface GroupMembershipColumns {
515        public static final String RAW_CONTACT_ID = Data.RAW_CONTACT_ID;
516        public static final String GROUP_ROW_ID = GroupMembership.GROUP_ROW_ID;
517    }
518
519    public interface GroupsColumns {
520        public static final String PACKAGE_ID = "package_id";
521        public static final String CONCRETE_PACKAGE_ID = Tables.GROUPS + "." + PACKAGE_ID;
522
523        public static final String CONCRETE_ID = Tables.GROUPS + "." + BaseColumns._ID;
524        public static final String CONCRETE_SOURCE_ID = Tables.GROUPS + "." + Groups.SOURCE_ID;
525
526        public static final String ACCOUNT_ID = "account_id";
527        public static final String CONCRETE_ACCOUNT_ID = Tables.GROUPS + "." + ACCOUNT_ID;
528    }
529
530    public interface ViewGroupsColumns {
531        String CONCRETE_ACCOUNT_NAME = Views.GROUPS + "." + Groups.ACCOUNT_NAME;
532        String CONCRETE_ACCOUNT_TYPE = Views.GROUPS + "." + Groups.ACCOUNT_TYPE;
533        String CONCRETE_DATA_SET = Views.GROUPS + "." + Groups.DATA_SET;
534    }
535
536    public interface ActivitiesColumns {
537        public static final String PACKAGE_ID = "package_id";
538        public static final String MIMETYPE_ID = "mimetype_id";
539    }
540
541    public interface PhoneLookupColumns {
542        public static final String _ID = BaseColumns._ID;
543        public static final String DATA_ID = "data_id";
544        public static final String RAW_CONTACT_ID = "raw_contact_id";
545        public static final String NORMALIZED_NUMBER = "normalized_number";
546        public static final String MIN_MATCH = "min_match";
547    }
548
549    public interface NameLookupColumns {
550        public static final String RAW_CONTACT_ID = "raw_contact_id";
551        public static final String DATA_ID = "data_id";
552        public static final String NORMALIZED_NAME = "normalized_name";
553        public static final String NAME_TYPE = "name_type";
554    }
555
556    public interface PackagesColumns {
557        public static final String _ID = BaseColumns._ID;
558        public static final String PACKAGE = "package";
559
560        public static final String CONCRETE_ID = Tables.PACKAGES + "." + _ID;
561    }
562
563    public interface MimetypesColumns {
564        public static final String _ID = BaseColumns._ID;
565        public static final String MIMETYPE = "mimetype";
566
567        public static final String CONCRETE_ID = Tables.MIMETYPES + "." + BaseColumns._ID;
568        public static final String CONCRETE_MIMETYPE = Tables.MIMETYPES + "." + MIMETYPE;
569    }
570
571    public interface AggregationExceptionColumns {
572        public static final String _ID = BaseColumns._ID;
573    }
574
575    public interface NicknameLookupColumns {
576        public static final String NAME = "name";
577        public static final String CLUSTER = "cluster";
578    }
579
580    public interface SettingsColumns {
581        public static final String CONCRETE_ACCOUNT_NAME = Tables.SETTINGS + "."
582                + Settings.ACCOUNT_NAME;
583        public static final String CONCRETE_ACCOUNT_TYPE = Tables.SETTINGS + "."
584                + Settings.ACCOUNT_TYPE;
585        public static final String CONCRETE_DATA_SET = Tables.SETTINGS + "."
586                + Settings.DATA_SET;
587    }
588
589    public interface PresenceColumns {
590        String RAW_CONTACT_ID = "presence_raw_contact_id";
591        String CONTACT_ID = "presence_contact_id";
592    }
593
594    public interface AggregatedPresenceColumns {
595        String CONTACT_ID = "presence_contact_id";
596        String CONCRETE_CONTACT_ID = Tables.AGGREGATED_PRESENCE + "." + CONTACT_ID;
597    }
598
599    public interface StatusUpdatesColumns {
600        String DATA_ID = "status_update_data_id";
601
602        String CONCRETE_DATA_ID = Tables.STATUS_UPDATES + "." + DATA_ID;
603
604        String CONCRETE_PRESENCE = Tables.STATUS_UPDATES + "." + StatusUpdates.PRESENCE;
605        String CONCRETE_STATUS = Tables.STATUS_UPDATES + "." + StatusUpdates.STATUS;
606        String CONCRETE_STATUS_TIMESTAMP = Tables.STATUS_UPDATES + "."
607                + StatusUpdates.STATUS_TIMESTAMP;
608        String CONCRETE_STATUS_RES_PACKAGE = Tables.STATUS_UPDATES + "."
609                + StatusUpdates.STATUS_RES_PACKAGE;
610        String CONCRETE_STATUS_LABEL = Tables.STATUS_UPDATES + "." + StatusUpdates.STATUS_LABEL;
611        String CONCRETE_STATUS_ICON = Tables.STATUS_UPDATES + "." + StatusUpdates.STATUS_ICON;
612    }
613
614    public interface ContactsStatusUpdatesColumns {
615        String ALIAS = "contacts_" + Tables.STATUS_UPDATES;
616
617        String CONCRETE_DATA_ID = ALIAS + "." + StatusUpdatesColumns.DATA_ID;
618
619        String CONCRETE_PRESENCE = ALIAS + "." + StatusUpdates.PRESENCE;
620        String CONCRETE_STATUS = ALIAS + "." + StatusUpdates.STATUS;
621        String CONCRETE_STATUS_TIMESTAMP = ALIAS + "." + StatusUpdates.STATUS_TIMESTAMP;
622        String CONCRETE_STATUS_RES_PACKAGE = ALIAS + "." + StatusUpdates.STATUS_RES_PACKAGE;
623        String CONCRETE_STATUS_LABEL = ALIAS + "." + StatusUpdates.STATUS_LABEL;
624        String CONCRETE_STATUS_ICON = ALIAS + "." + StatusUpdates.STATUS_ICON;
625    }
626
627    public interface StreamItemsColumns {
628        final String CONCRETE_ID = Tables.STREAM_ITEMS + "." + BaseColumns._ID;
629        final String CONCRETE_RAW_CONTACT_ID =
630                Tables.STREAM_ITEMS + "." + StreamItems.RAW_CONTACT_ID;
631        final String CONCRETE_PACKAGE = Tables.STREAM_ITEMS + "." + StreamItems.RES_PACKAGE;
632        final String CONCRETE_ICON = Tables.STREAM_ITEMS + "." + StreamItems.RES_ICON;
633        final String CONCRETE_LABEL = Tables.STREAM_ITEMS + "." + StreamItems.RES_LABEL;
634        final String CONCRETE_TEXT = Tables.STREAM_ITEMS + "." + StreamItems.TEXT;
635        final String CONCRETE_TIMESTAMP = Tables.STREAM_ITEMS + "." + StreamItems.TIMESTAMP;
636        final String CONCRETE_COMMENTS = Tables.STREAM_ITEMS + "." + StreamItems.COMMENTS;
637        final String CONCRETE_SYNC1 = Tables.STREAM_ITEMS + "." + StreamItems.SYNC1;
638        final String CONCRETE_SYNC2 = Tables.STREAM_ITEMS + "." + StreamItems.SYNC2;
639        final String CONCRETE_SYNC3 = Tables.STREAM_ITEMS + "." + StreamItems.SYNC3;
640        final String CONCRETE_SYNC4 = Tables.STREAM_ITEMS + "." + StreamItems.SYNC4;
641    }
642
643    public interface StreamItemPhotosColumns {
644        final String CONCRETE_ID = Tables.STREAM_ITEM_PHOTOS + "." + BaseColumns._ID;
645        final String CONCRETE_STREAM_ITEM_ID = Tables.STREAM_ITEM_PHOTOS + "."
646                + StreamItemPhotos.STREAM_ITEM_ID;
647        final String CONCRETE_SORT_INDEX =
648                Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.SORT_INDEX;
649        final String CONCRETE_PHOTO_FILE_ID = Tables.STREAM_ITEM_PHOTOS + "."
650                + StreamItemPhotos.PHOTO_FILE_ID;
651        final String CONCRETE_SYNC1 = Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.SYNC1;
652        final String CONCRETE_SYNC2 = Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.SYNC2;
653        final String CONCRETE_SYNC3 = Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.SYNC3;
654        final String CONCRETE_SYNC4 = Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.SYNC4;
655    }
656
657    public interface PhotoFilesColumns {
658        String CONCRETE_ID = Tables.PHOTO_FILES + "." + BaseColumns._ID;
659        String CONCRETE_HEIGHT = Tables.PHOTO_FILES + "." + PhotoFiles.HEIGHT;
660        String CONCRETE_WIDTH = Tables.PHOTO_FILES + "." + PhotoFiles.WIDTH;
661        String CONCRETE_FILESIZE = Tables.PHOTO_FILES + "." + PhotoFiles.FILESIZE;
662    }
663
664    public interface PropertiesColumns {
665        String PROPERTY_KEY = "property_key";
666        String PROPERTY_VALUE = "property_value";
667    }
668
669    public interface AccountsColumns extends BaseColumns {
670        String CONCRETE_ID = Tables.ACCOUNTS + "." + BaseColumns._ID;
671
672        String ACCOUNT_NAME = RawContacts.ACCOUNT_NAME;
673        String ACCOUNT_TYPE = RawContacts.ACCOUNT_TYPE;
674        String DATA_SET = RawContacts.DATA_SET;
675
676        String CONCRETE_ACCOUNT_NAME = Tables.ACCOUNTS + "." + ACCOUNT_NAME;
677        String CONCRETE_ACCOUNT_TYPE = Tables.ACCOUNTS + "." + ACCOUNT_TYPE;
678        String CONCRETE_DATA_SET = Tables.ACCOUNTS + "." + DATA_SET;
679    }
680
681    public interface DirectoryColumns {
682        public static final String TYPE_RESOURCE_NAME = "typeResourceName";
683    }
684
685    public interface SearchIndexColumns {
686        public static final String CONTACT_ID = "contact_id";
687        public static final String CONTENT = "content";
688        public static final String NAME = "name";
689        public static final String TOKENS = "tokens";
690    }
691
692    public interface PreAuthorizedUris {
693        public static final String _ID = BaseColumns._ID;
694        public static final String URI = "uri";
695        public static final String EXPIRATION = "expiration";
696    }
697
698    /**
699     * Private table for calculating per-contact-method ranking.
700     */
701    public interface DataUsageStatColumns {
702        /** type: INTEGER (long) */
703        public static final String _ID = "stat_id";
704        public static final String CONCRETE_ID = Tables.DATA_USAGE_STAT + "." + _ID;
705
706        /** type: INTEGER (long) */
707        public static final String DATA_ID = "data_id";
708        public static final String CONCRETE_DATA_ID = Tables.DATA_USAGE_STAT + "." + DATA_ID;
709
710        /** type: INTEGER (long) */
711        public static final String LAST_TIME_USED = "last_time_used";
712        public static final String CONCRETE_LAST_TIME_USED =
713                Tables.DATA_USAGE_STAT + "." + LAST_TIME_USED;
714
715        /** type: INTEGER */
716        public static final String TIMES_USED = "times_used";
717        public static final String CONCRETE_TIMES_USED =
718                Tables.DATA_USAGE_STAT + "." + TIMES_USED;
719
720        /** type: INTEGER */
721        public static final String USAGE_TYPE_INT = "usage_type";
722        public static final String CONCRETE_USAGE_TYPE =
723                Tables.DATA_USAGE_STAT + "." + USAGE_TYPE_INT;
724
725        /**
726         * Integer values for USAGE_TYPE.
727         *
728         * @see android.provider.ContactsContract.DataUsageFeedback#USAGE_TYPE
729         */
730        public static final int USAGE_TYPE_INT_CALL = 0;
731        public static final int USAGE_TYPE_INT_LONG_TEXT = 1;
732        public static final int USAGE_TYPE_INT_SHORT_TEXT = 2;
733    }
734
735    public interface MetadataSyncColumns {
736        static final String CONCRETE_ID = Tables.METADATA_SYNC + "._id";
737        static final String ACCOUNT_ID = "account_id";
738        static final String CONCRETE_ACCOUNT_ID = Tables.METADATA_SYNC + "." + ACCOUNT_ID;
739    }
740
741    private  interface EmailQuery {
742        public static final String TABLE = Tables.DATA;
743
744        public static final String SELECTION =
745                DataColumns.MIMETYPE_ID + "=? AND " + Data.DATA1 + " NOT NULL";
746
747        public static final String COLUMNS[] = {
748                Email._ID,
749                Email.RAW_CONTACT_ID,
750                Email.ADDRESS};
751
752        public static final int ID = 0;
753        public static final int RAW_CONTACT_ID = 1;
754        public static final int ADDRESS = 2;
755    }
756
757    private interface Upgrade303Query {
758        public static final String TABLE = Tables.DATA;
759
760        public static final String SELECTION =
761                DataColumns.MIMETYPE_ID + "=?" +
762                    " AND " + Data._ID + " NOT IN " +
763                    "(SELECT " + NameLookupColumns.DATA_ID + " FROM " + Tables.NAME_LOOKUP + ")" +
764                    " AND " + Data.DATA1 + " NOT NULL";
765
766        public static final String COLUMNS[] = {
767                Data._ID,
768                Data.RAW_CONTACT_ID,
769                Data.DATA1,
770        };
771
772        public static final int ID = 0;
773        public static final int RAW_CONTACT_ID = 1;
774        public static final int DATA1 = 2;
775    }
776
777    private interface StructuredNameQuery {
778        public static final String TABLE = Tables.DATA;
779
780        public static final String SELECTION =
781                DataColumns.MIMETYPE_ID + "=? AND " + Data.DATA1 + " NOT NULL";
782
783        public static final String COLUMNS[] = {
784                StructuredName._ID,
785                StructuredName.RAW_CONTACT_ID,
786                StructuredName.DISPLAY_NAME,
787        };
788
789        public static final int ID = 0;
790        public static final int RAW_CONTACT_ID = 1;
791        public static final int DISPLAY_NAME = 2;
792    }
793
794    private interface NicknameQuery {
795        public static final String TABLE = Tables.DATA;
796
797        public static final String SELECTION =
798                DataColumns.MIMETYPE_ID + "=? AND " + Data.DATA1 + " NOT NULL";
799
800        public static final String COLUMNS[] = {
801                Nickname._ID,
802                Nickname.RAW_CONTACT_ID,
803                Nickname.NAME};
804
805        public static final int ID = 0;
806        public static final int RAW_CONTACT_ID = 1;
807        public static final int NAME = 2;
808    }
809
810    private interface StructName205Query {
811        String TABLE = Tables.DATA_JOIN_RAW_CONTACTS;
812        String COLUMNS[] = {
813                DataColumns.CONCRETE_ID,
814                Data.RAW_CONTACT_ID,
815                RawContacts.DISPLAY_NAME_SOURCE,
816                RawContacts.DISPLAY_NAME_PRIMARY,
817                StructuredName.PREFIX,
818                StructuredName.GIVEN_NAME,
819                StructuredName.MIDDLE_NAME,
820                StructuredName.FAMILY_NAME,
821                StructuredName.SUFFIX,
822                StructuredName.PHONETIC_FAMILY_NAME,
823                StructuredName.PHONETIC_MIDDLE_NAME,
824                StructuredName.PHONETIC_GIVEN_NAME,
825        };
826
827        int ID = 0;
828        int RAW_CONTACT_ID = 1;
829        int DISPLAY_NAME_SOURCE = 2;
830        int DISPLAY_NAME = 3;
831        int PREFIX = 4;
832        int GIVEN_NAME = 5;
833        int MIDDLE_NAME = 6;
834        int FAMILY_NAME = 7;
835        int SUFFIX = 8;
836        int PHONETIC_FAMILY_NAME = 9;
837        int PHONETIC_MIDDLE_NAME = 10;
838        int PHONETIC_GIVEN_NAME = 11;
839    }
840
841    private interface RawContactNameQuery {
842        public static final String RAW_SQL =
843                "SELECT "
844                        + DataColumns.MIMETYPE_ID + ","
845                        + Data.IS_PRIMARY + ","
846                        + Data.DATA1 + ","
847                        + Data.DATA2 + ","
848                        + Data.DATA3 + ","
849                        + Data.DATA4 + ","
850                        + Data.DATA5 + ","
851                        + Data.DATA6 + ","
852                        + Data.DATA7 + ","
853                        + Data.DATA8 + ","
854                        + Data.DATA9 + ","
855                        + Data.DATA10 + ","
856                        + Data.DATA11 +
857                " FROM " + Tables.DATA +
858                " WHERE " + Data.RAW_CONTACT_ID + "=?" +
859                        " AND (" + Data.DATA1 + " NOT NULL OR " +
860                                Data.DATA8 + " NOT NULL OR " +
861                                Data.DATA9 + " NOT NULL OR " +
862                                Data.DATA10 + " NOT NULL OR " +  // Phonetic name not empty
863                                Organization.TITLE + " NOT NULL)";
864
865        public static final int MIMETYPE = 0;
866        public static final int IS_PRIMARY = 1;
867        public static final int DATA1 = 2;
868        public static final int GIVEN_NAME = 3;                         // data2
869        public static final int FAMILY_NAME = 4;                        // data3
870        public static final int PREFIX = 5;                             // data4
871        public static final int TITLE = 5;                              // data4
872        public static final int MIDDLE_NAME = 6;                        // data5
873        public static final int SUFFIX = 7;                             // data6
874        public static final int PHONETIC_GIVEN_NAME = 8;                // data7
875        public static final int PHONETIC_MIDDLE_NAME = 9;               // data8
876        public static final int ORGANIZATION_PHONETIC_NAME = 9;         // data8
877        public static final int PHONETIC_FAMILY_NAME = 10;              // data9
878        public static final int FULL_NAME_STYLE = 11;                   // data10
879        public static final int ORGANIZATION_PHONETIC_NAME_STYLE = 11;  // data10
880        public static final int PHONETIC_NAME_STYLE = 12;               // data11
881    }
882
883    private interface Organization205Query {
884        String TABLE = Tables.DATA_JOIN_RAW_CONTACTS;
885        String COLUMNS[] = {
886                DataColumns.CONCRETE_ID,
887                Data.RAW_CONTACT_ID,
888                Organization.COMPANY,
889                Organization.PHONETIC_NAME,
890        };
891
892        int ID = 0;
893        int RAW_CONTACT_ID = 1;
894        int COMPANY = 2;
895        int PHONETIC_NAME = 3;
896    }
897
898    public final static class NameLookupType {
899        public static final int NAME_EXACT = 0;
900        public static final int NAME_VARIANT = 1;
901        public static final int NAME_COLLATION_KEY = 2;
902        public static final int NICKNAME = 3;
903        public static final int EMAIL_BASED_NICKNAME = 4;
904
905        // The highest name-lookup type plus one.
906        public static final int TYPE_COUNT = 5;
907
908        public static boolean isBasedOnStructuredName(int nameLookupType) {
909            return nameLookupType == NameLookupType.NAME_EXACT
910                    || nameLookupType == NameLookupType.NAME_VARIANT
911                    || nameLookupType == NameLookupType.NAME_COLLATION_KEY;
912        }
913    }
914
915    private class StructuredNameLookupBuilder extends NameLookupBuilder {
916        // NOTE(gilad): Is in intentional that we don't use the declaration on L960?
917        private final SQLiteStatement mNameLookupInsert;
918        private final CommonNicknameCache mCommonNicknameCache;
919
920        public StructuredNameLookupBuilder(NameSplitter splitter,
921                CommonNicknameCache commonNicknameCache, SQLiteStatement nameLookupInsert) {
922
923            super(splitter);
924            this.mCommonNicknameCache = commonNicknameCache;
925            this.mNameLookupInsert = nameLookupInsert;
926        }
927
928        @Override
929        protected void insertNameLookup(
930                long rawContactId, long dataId, int lookupType, String name) {
931
932            if (!TextUtils.isEmpty(name)) {
933                ContactsDatabaseHelper.this.insertNormalizedNameLookup(
934                        mNameLookupInsert, rawContactId, dataId, lookupType, name);
935            }
936        }
937
938        @Override
939        protected String[] getCommonNicknameClusters(String normalizedName) {
940            return mCommonNicknameCache.getCommonNicknameClusters(normalizedName);
941        }
942    }
943
944    private static final String TAG = "ContactsDatabaseHelper";
945
946    private static final String DATABASE_NAME = "contacts2.db";
947    private static final String DATABASE_PRESENCE = "presence_db";
948
949    private static ContactsDatabaseHelper sSingleton = null;
950
951    /** In-memory cache of previously found MIME-type mappings */
952    @VisibleForTesting
953    final ConcurrentHashMap<String, Long> mMimetypeCache = new ConcurrentHashMap<>();
954
955    /** In-memory cache the packages table */
956    @VisibleForTesting
957    final ConcurrentHashMap<String, Long> mPackageCache = new ConcurrentHashMap<>();
958
959    private final Context mContext;
960    private final boolean mDatabaseOptimizationEnabled;
961    private final SyncStateContentProviderHelper mSyncState;
962    private final CountryMonitor mCountryMonitor;
963
964    private long mMimeTypeIdEmail;
965    private long mMimeTypeIdIm;
966    private long mMimeTypeIdNickname;
967    private long mMimeTypeIdOrganization;
968    private long mMimeTypeIdPhone;
969    private long mMimeTypeIdSip;
970    private long mMimeTypeIdStructuredName;
971    private long mMimeTypeIdStructuredPostal;
972
973    /** Compiled statements for querying and inserting mappings */
974    private SQLiteStatement mContactIdQuery;
975    private SQLiteStatement mAggregationModeQuery;
976    private SQLiteStatement mDataMimetypeQuery;
977
978    /** Precompiled SQL statement for setting a data record to the primary. */
979    private SQLiteStatement mSetPrimaryStatement;
980    /** Precompiled SQL statement for setting a data record to the super primary. */
981    private SQLiteStatement mSetSuperPrimaryStatement;
982    /** Precompiled SQL statement for clearing super primary of a single record. */
983    private SQLiteStatement mClearSuperPrimaryStatement;
984    /** Precompiled SQL statement for updating a contact display name */
985    private SQLiteStatement mRawContactDisplayNameUpdate;
986
987    private SQLiteStatement mNameLookupInsert;
988    private SQLiteStatement mNameLookupDelete;
989    private SQLiteStatement mStatusUpdateAutoTimestamp;
990    private SQLiteStatement mStatusUpdateInsert;
991    private SQLiteStatement mStatusUpdateReplace;
992    private SQLiteStatement mStatusAttributionUpdate;
993    private SQLiteStatement mStatusUpdateDelete;
994    private SQLiteStatement mResetNameVerifiedForOtherRawContacts;
995    private SQLiteStatement mContactInDefaultDirectoryQuery;
996
997    private StringBuilder mSb = new StringBuilder();
998
999    private boolean mUseStrictPhoneNumberComparison;
1000
1001    private String[] mSelectionArgs1 = new String[1];
1002    private NameSplitter.Name mName = new NameSplitter.Name();
1003    private CharArrayBuffer mCharArrayBuffer = new CharArrayBuffer(128);
1004    private NameSplitter mNameSplitter;
1005
1006    public static synchronized ContactsDatabaseHelper getInstance(Context context) {
1007        if (sSingleton == null) {
1008            sSingleton = new ContactsDatabaseHelper(context, DATABASE_NAME, true);
1009        }
1010        return sSingleton;
1011    }
1012
1013    /**
1014     * Returns a new instance for unit tests.
1015     */
1016    @NeededForTesting
1017    static ContactsDatabaseHelper getNewInstanceForTest(Context context) {
1018        return new ContactsDatabaseHelper(context, null, false);
1019    }
1020
1021    protected ContactsDatabaseHelper(
1022            Context context, String databaseName, boolean optimizationEnabled) {
1023        super(context, databaseName, null, DATABASE_VERSION);
1024        mDatabaseOptimizationEnabled = optimizationEnabled;
1025        Resources resources = context.getResources();
1026
1027        mContext = context;
1028        mSyncState = new SyncStateContentProviderHelper();
1029        mCountryMonitor = new CountryMonitor(context);
1030        mUseStrictPhoneNumberComparison = resources.getBoolean(
1031                com.android.internal.R.bool.config_use_strict_phone_number_comparation);
1032    }
1033
1034    public SQLiteDatabase getDatabase(boolean writable) {
1035        return writable ? getWritableDatabase() : getReadableDatabase();
1036    }
1037
1038    /**
1039     * Clear all the cached database information and re-initialize it.
1040     *
1041     * @param db target database
1042     */
1043    private void refreshDatabaseCaches(SQLiteDatabase db) {
1044        mStatusUpdateDelete = null;
1045        mStatusUpdateReplace = null;
1046        mStatusUpdateInsert = null;
1047        mStatusUpdateAutoTimestamp = null;
1048        mStatusAttributionUpdate = null;
1049        mResetNameVerifiedForOtherRawContacts = null;
1050        mRawContactDisplayNameUpdate = null;
1051        mSetPrimaryStatement = null;
1052        mClearSuperPrimaryStatement = null;
1053        mSetSuperPrimaryStatement = null;
1054        mNameLookupInsert = null;
1055        mNameLookupDelete = null;
1056        mDataMimetypeQuery = null;
1057        mContactIdQuery = null;
1058        mAggregationModeQuery = null;
1059        mContactInDefaultDirectoryQuery = null;
1060
1061        initializeCache(db);
1062    }
1063
1064    /**
1065     * (Re-)initialize the cached database information.
1066     *
1067     * @param db target database
1068     */
1069    private void initializeCache(SQLiteDatabase db) {
1070        mMimetypeCache.clear();
1071        mPackageCache.clear();
1072
1073        // TODO: This could be optimized into one query instead of 7
1074        //        Also: We shouldn't have those fields in the first place. This should just be
1075        //        in the cache
1076        mMimeTypeIdEmail = lookupMimeTypeId(Email.CONTENT_ITEM_TYPE, db);
1077        mMimeTypeIdIm = lookupMimeTypeId(Im.CONTENT_ITEM_TYPE, db);
1078        mMimeTypeIdNickname = lookupMimeTypeId(Nickname.CONTENT_ITEM_TYPE, db);
1079        mMimeTypeIdOrganization = lookupMimeTypeId(Organization.CONTENT_ITEM_TYPE, db);
1080        mMimeTypeIdPhone = lookupMimeTypeId(Phone.CONTENT_ITEM_TYPE, db);
1081        mMimeTypeIdSip = lookupMimeTypeId(SipAddress.CONTENT_ITEM_TYPE, db);
1082        mMimeTypeIdStructuredName = lookupMimeTypeId(StructuredName.CONTENT_ITEM_TYPE, db);
1083        mMimeTypeIdStructuredPostal = lookupMimeTypeId(StructuredPostal.CONTENT_ITEM_TYPE, db);
1084    }
1085
1086    @Override
1087    public void onOpen(SQLiteDatabase db) {
1088        refreshDatabaseCaches(db);
1089
1090        mSyncState.onDatabaseOpened(db);
1091
1092        db.execSQL("ATTACH DATABASE ':memory:' AS " + DATABASE_PRESENCE + ";");
1093        db.execSQL("CREATE TABLE IF NOT EXISTS " + DATABASE_PRESENCE + "." + Tables.PRESENCE + " ("+
1094                StatusUpdates.DATA_ID + " INTEGER PRIMARY KEY REFERENCES data(_id)," +
1095                StatusUpdates.PROTOCOL + " INTEGER NOT NULL," +
1096                StatusUpdates.CUSTOM_PROTOCOL + " TEXT," +
1097                StatusUpdates.IM_HANDLE + " TEXT," +
1098                StatusUpdates.IM_ACCOUNT + " TEXT," +
1099                PresenceColumns.CONTACT_ID + " INTEGER REFERENCES contacts(_id)," +
1100                PresenceColumns.RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)," +
1101                StatusUpdates.PRESENCE + " INTEGER," +
1102                StatusUpdates.CHAT_CAPABILITY + " INTEGER NOT NULL DEFAULT 0," +
1103                "UNIQUE(" + StatusUpdates.PROTOCOL + ", " + StatusUpdates.CUSTOM_PROTOCOL
1104                    + ", " + StatusUpdates.IM_HANDLE + ", " + StatusUpdates.IM_ACCOUNT + ")" +
1105        ");");
1106
1107        db.execSQL("CREATE INDEX IF NOT EXISTS " + DATABASE_PRESENCE + ".presenceIndex" + " ON "
1108                + Tables.PRESENCE + " (" + PresenceColumns.RAW_CONTACT_ID + ");");
1109        db.execSQL("CREATE INDEX IF NOT EXISTS " + DATABASE_PRESENCE + ".presenceIndex2" + " ON "
1110                + Tables.PRESENCE + " (" + PresenceColumns.CONTACT_ID + ");");
1111
1112        db.execSQL("CREATE TABLE IF NOT EXISTS "
1113                + DATABASE_PRESENCE + "." + Tables.AGGREGATED_PRESENCE + " ("+
1114                AggregatedPresenceColumns.CONTACT_ID
1115                        + " INTEGER PRIMARY KEY REFERENCES contacts(_id)," +
1116                StatusUpdates.PRESENCE + " INTEGER," +
1117                StatusUpdates.CHAT_CAPABILITY + " INTEGER NOT NULL DEFAULT 0" +
1118        ");");
1119
1120        db.execSQL("CREATE TRIGGER " + DATABASE_PRESENCE + "." + Tables.PRESENCE + "_deleted"
1121                + " BEFORE DELETE ON " + DATABASE_PRESENCE + "." + Tables.PRESENCE
1122                + " BEGIN "
1123                + "   DELETE FROM " + Tables.AGGREGATED_PRESENCE
1124                + "     WHERE " + AggregatedPresenceColumns.CONTACT_ID + " = " +
1125                        "(SELECT " + PresenceColumns.CONTACT_ID +
1126                        " FROM " + Tables.PRESENCE +
1127                        " WHERE " + PresenceColumns.RAW_CONTACT_ID
1128                                + "=OLD." + PresenceColumns.RAW_CONTACT_ID +
1129                        " AND NOT EXISTS" +
1130                                "(SELECT " + PresenceColumns.RAW_CONTACT_ID +
1131                                " FROM " + Tables.PRESENCE +
1132                                " WHERE " + PresenceColumns.CONTACT_ID
1133                                        + "=OLD." + PresenceColumns.CONTACT_ID +
1134                                " AND " + PresenceColumns.RAW_CONTACT_ID
1135                                        + "!=OLD." + PresenceColumns.RAW_CONTACT_ID + "));"
1136                + " END");
1137
1138        final String replaceAggregatePresenceSql =
1139                "INSERT OR REPLACE INTO " + Tables.AGGREGATED_PRESENCE + "("
1140                        + AggregatedPresenceColumns.CONTACT_ID + ", "
1141                        + StatusUpdates.PRESENCE + ", "
1142                        + StatusUpdates.CHAT_CAPABILITY + ")"
1143                + " SELECT "
1144                        + PresenceColumns.CONTACT_ID + ","
1145                        + StatusUpdates.PRESENCE + ","
1146                        + StatusUpdates.CHAT_CAPABILITY
1147                + " FROM " + Tables.PRESENCE
1148                + " WHERE "
1149                    + " (ifnull(" + StatusUpdates.PRESENCE + ",0)  * 10 "
1150                            + "+ ifnull(" + StatusUpdates.CHAT_CAPABILITY + ", 0))"
1151                    + " = (SELECT "
1152                        + "MAX (ifnull(" + StatusUpdates.PRESENCE + ",0)  * 10 "
1153                                + "+ ifnull(" + StatusUpdates.CHAT_CAPABILITY + ", 0))"
1154                        + " FROM " + Tables.PRESENCE
1155                        + " WHERE " + PresenceColumns.CONTACT_ID
1156                            + "=NEW." + PresenceColumns.CONTACT_ID
1157                    + ")"
1158                + " AND " + PresenceColumns.CONTACT_ID + "=NEW." + PresenceColumns.CONTACT_ID + ";";
1159
1160        db.execSQL("CREATE TRIGGER " + DATABASE_PRESENCE + "." + Tables.PRESENCE + "_inserted"
1161                + " AFTER INSERT ON " + DATABASE_PRESENCE + "." + Tables.PRESENCE
1162                + " BEGIN "
1163                + replaceAggregatePresenceSql
1164                + " END");
1165
1166        db.execSQL("CREATE TRIGGER " + DATABASE_PRESENCE + "." + Tables.PRESENCE + "_updated"
1167                + " AFTER UPDATE ON " + DATABASE_PRESENCE + "." + Tables.PRESENCE
1168                + " BEGIN "
1169                + replaceAggregatePresenceSql
1170                + " END");
1171    }
1172
1173    @Override
1174    public void onCreate(SQLiteDatabase db) {
1175        Log.i(TAG, "Bootstrapping database version: " + DATABASE_VERSION);
1176
1177        mSyncState.createDatabase(db);
1178
1179        // Create the properties table first so the create time is available as soon as possible.
1180        // The create time is needed by BOOT_COMPLETE to send broadcasts.
1181        db.execSQL("CREATE TABLE " + Tables.PROPERTIES + " (" +
1182                PropertiesColumns.PROPERTY_KEY + " TEXT PRIMARY KEY, " +
1183                PropertiesColumns.PROPERTY_VALUE + " TEXT " +
1184                ");");
1185        setProperty(db, DbProperties.DATABASE_TIME_CREATED, String.valueOf(
1186                System.currentTimeMillis()));
1187
1188        db.execSQL("CREATE TABLE " + Tables.ACCOUNTS + " (" +
1189                AccountsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1190                AccountsColumns.ACCOUNT_NAME + " TEXT, " +
1191                AccountsColumns.ACCOUNT_TYPE + " TEXT, " +
1192                AccountsColumns.DATA_SET + " TEXT" +
1193        ");");
1194
1195        // One row per group of contacts corresponding to the same person
1196        db.execSQL("CREATE TABLE " + Tables.CONTACTS + " (" +
1197                BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1198                Contacts.NAME_RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)," +
1199                Contacts.PHOTO_ID + " INTEGER REFERENCES data(_id)," +
1200                Contacts.PHOTO_FILE_ID + " INTEGER REFERENCES photo_files(_id)," +
1201                Contacts.CUSTOM_RINGTONE + " TEXT," +
1202                Contacts.SEND_TO_VOICEMAIL + " INTEGER NOT NULL DEFAULT 0," +
1203                Contacts.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
1204                Contacts.LAST_TIME_CONTACTED + " INTEGER," +
1205                Contacts.STARRED + " INTEGER NOT NULL DEFAULT 0," +
1206                Contacts.PINNED + " INTEGER NOT NULL DEFAULT " + PinnedPositions.UNPINNED + "," +
1207                Contacts.HAS_PHONE_NUMBER + " INTEGER NOT NULL DEFAULT 0," +
1208                Contacts.LOOKUP_KEY + " TEXT," +
1209                ContactsColumns.LAST_STATUS_UPDATE_ID + " INTEGER REFERENCES data(_id)," +
1210                Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + " INTEGER" +
1211        ");");
1212
1213        ContactsTableUtil.createIndexes(db);
1214
1215        // deleted_contacts table
1216        DeletedContactsTableUtil.create(db);
1217
1218        // Raw_contacts table
1219        db.execSQL("CREATE TABLE " + Tables.RAW_CONTACTS + " (" +
1220                RawContacts._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1221                RawContactsColumns.ACCOUNT_ID + " INTEGER REFERENCES " +
1222                    Tables.ACCOUNTS + "(" + AccountsColumns._ID + ")," +
1223                RawContacts.SOURCE_ID + " TEXT," +
1224                RawContacts.BACKUP_ID + " TEXT," +
1225                RawContacts.RAW_CONTACT_IS_READ_ONLY + " INTEGER NOT NULL DEFAULT 0," +
1226                RawContacts.VERSION + " INTEGER NOT NULL DEFAULT 1," +
1227                RawContacts.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
1228                RawContacts.DELETED + " INTEGER NOT NULL DEFAULT 0," +
1229                RawContacts.CONTACT_ID + " INTEGER REFERENCES contacts(_id)," +
1230                RawContacts.AGGREGATION_MODE + " INTEGER NOT NULL DEFAULT " +
1231                        RawContacts.AGGREGATION_MODE_DEFAULT + "," +
1232                RawContactsColumns.AGGREGATION_NEEDED + " INTEGER NOT NULL DEFAULT 1," +
1233                RawContacts.CUSTOM_RINGTONE + " TEXT," +
1234                RawContacts.SEND_TO_VOICEMAIL + " INTEGER NOT NULL DEFAULT 0," +
1235                RawContacts.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
1236                RawContacts.LAST_TIME_CONTACTED + " INTEGER," +
1237                RawContacts.STARRED + " INTEGER NOT NULL DEFAULT 0," +
1238                RawContacts.PINNED + " INTEGER NOT NULL DEFAULT "  + PinnedPositions.UNPINNED +
1239                    "," + RawContacts.DISPLAY_NAME_PRIMARY + " TEXT," +
1240                RawContacts.DISPLAY_NAME_ALTERNATIVE + " TEXT," +
1241                RawContacts.DISPLAY_NAME_SOURCE + " INTEGER NOT NULL DEFAULT " +
1242                        DisplayNameSources.UNDEFINED + "," +
1243                RawContacts.PHONETIC_NAME + " TEXT," +
1244                // TODO: PHONETIC_NAME_STYLE should be INTEGER. There is a
1245                // mismatch between how the column is created here (TEXT) and
1246                // how it is created in upgradeToVersion205 (INTEGER).
1247                RawContacts.PHONETIC_NAME_STYLE + " TEXT," +
1248                RawContacts.SORT_KEY_PRIMARY + " TEXT COLLATE " +
1249                        ContactsProvider2.PHONEBOOK_COLLATOR_NAME + "," +
1250                RawContactsColumns.PHONEBOOK_LABEL_PRIMARY + " TEXT," +
1251                RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY + " INTEGER," +
1252                RawContacts.SORT_KEY_ALTERNATIVE + " TEXT COLLATE " +
1253                        ContactsProvider2.PHONEBOOK_COLLATOR_NAME + "," +
1254                RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + " TEXT," +
1255                RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE + " INTEGER," +
1256                RawContactsColumns.NAME_VERIFIED_OBSOLETE + " INTEGER NOT NULL DEFAULT 0," +
1257                RawContacts.SYNC1 + " TEXT, " +
1258                RawContacts.SYNC2 + " TEXT, " +
1259                RawContacts.SYNC3 + " TEXT, " +
1260                RawContacts.SYNC4 + " TEXT " +
1261        ");");
1262
1263        db.execSQL("CREATE INDEX raw_contacts_contact_id_index ON " + Tables.RAW_CONTACTS + " (" +
1264                RawContacts.CONTACT_ID +
1265        ");");
1266
1267        db.execSQL("CREATE INDEX raw_contacts_source_id_account_id_index ON " +
1268                Tables.RAW_CONTACTS + " (" +
1269                RawContacts.SOURCE_ID + ", " +
1270                RawContactsColumns.ACCOUNT_ID +
1271        ");");
1272
1273        db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS raw_contacts_backup_id_account_id_index ON " +
1274                Tables.RAW_CONTACTS + " (" +
1275                RawContacts.BACKUP_ID + ", " +
1276                RawContactsColumns.ACCOUNT_ID +
1277        ");");
1278
1279        db.execSQL("CREATE TABLE " + Tables.STREAM_ITEMS + " (" +
1280                StreamItems._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
1281                StreamItems.RAW_CONTACT_ID + " INTEGER NOT NULL, " +
1282                StreamItems.RES_PACKAGE + " TEXT, " +
1283                StreamItems.RES_ICON + " TEXT, " +
1284                StreamItems.RES_LABEL + " TEXT, " +
1285                StreamItems.TEXT + " TEXT, " +
1286                StreamItems.TIMESTAMP + " INTEGER NOT NULL, " +
1287                StreamItems.COMMENTS + " TEXT, " +
1288                StreamItems.SYNC1 + " TEXT, " +
1289                StreamItems.SYNC2 + " TEXT, " +
1290                StreamItems.SYNC3 + " TEXT, " +
1291                StreamItems.SYNC4 + " TEXT, " +
1292                "FOREIGN KEY(" + StreamItems.RAW_CONTACT_ID + ") REFERENCES " +
1293                        Tables.RAW_CONTACTS + "(" + RawContacts._ID + "));");
1294
1295        db.execSQL("CREATE TABLE " + Tables.STREAM_ITEM_PHOTOS + " (" +
1296                StreamItemPhotos._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
1297                StreamItemPhotos.STREAM_ITEM_ID + " INTEGER NOT NULL, " +
1298                StreamItemPhotos.SORT_INDEX + " INTEGER, " +
1299                StreamItemPhotos.PHOTO_FILE_ID + " INTEGER NOT NULL, " +
1300                StreamItemPhotos.SYNC1 + " TEXT, " +
1301                StreamItemPhotos.SYNC2 + " TEXT, " +
1302                StreamItemPhotos.SYNC3 + " TEXT, " +
1303                StreamItemPhotos.SYNC4 + " TEXT, " +
1304                "FOREIGN KEY(" + StreamItemPhotos.STREAM_ITEM_ID + ") REFERENCES " +
1305                        Tables.STREAM_ITEMS + "(" + StreamItems._ID + "));");
1306
1307        db.execSQL("CREATE TABLE " + Tables.PHOTO_FILES + " (" +
1308                PhotoFiles._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
1309                PhotoFiles.HEIGHT + " INTEGER NOT NULL, " +
1310                PhotoFiles.WIDTH + " INTEGER NOT NULL, " +
1311                PhotoFiles.FILESIZE + " INTEGER NOT NULL);");
1312
1313        // TODO readd the index and investigate a controlled use of it
1314//        db.execSQL("CREATE INDEX raw_contacts_agg_index ON " + Tables.RAW_CONTACTS + " (" +
1315//                RawContactsColumns.AGGREGATION_NEEDED +
1316//        ");");
1317
1318        // Package name mapping table
1319        db.execSQL("CREATE TABLE " + Tables.PACKAGES + " (" +
1320                PackagesColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1321                PackagesColumns.PACKAGE + " TEXT NOT NULL" +
1322        ");");
1323
1324        // Mimetype mapping table
1325        db.execSQL("CREATE TABLE " + Tables.MIMETYPES + " (" +
1326                MimetypesColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1327                MimetypesColumns.MIMETYPE + " TEXT NOT NULL" +
1328        ");");
1329
1330        // Mimetype table requires an index on mime type
1331        db.execSQL("CREATE UNIQUE INDEX mime_type ON " + Tables.MIMETYPES + " (" +
1332                MimetypesColumns.MIMETYPE +
1333        ");");
1334
1335        // Public generic data table
1336        db.execSQL("CREATE TABLE " + Tables.DATA + " (" +
1337                Data._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1338                DataColumns.PACKAGE_ID + " INTEGER REFERENCES package(_id)," +
1339                DataColumns.MIMETYPE_ID + " INTEGER REFERENCES mimetype(_id) NOT NULL," +
1340                Data.RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," +
1341                Data.HASH_ID + " TEXT," +
1342                Data.IS_READ_ONLY + " INTEGER NOT NULL DEFAULT 0," +
1343                Data.IS_PRIMARY + " INTEGER NOT NULL DEFAULT 0," +
1344                Data.IS_SUPER_PRIMARY + " INTEGER NOT NULL DEFAULT 0," +
1345                Data.DATA_VERSION + " INTEGER NOT NULL DEFAULT 0," +
1346                Data.DATA1 + " TEXT," +
1347                Data.DATA2 + " TEXT," +
1348                Data.DATA3 + " TEXT," +
1349                Data.DATA4 + " TEXT," +
1350                Data.DATA5 + " TEXT," +
1351                Data.DATA6 + " TEXT," +
1352                Data.DATA7 + " TEXT," +
1353                Data.DATA8 + " TEXT," +
1354                Data.DATA9 + " TEXT," +
1355                Data.DATA10 + " TEXT," +
1356                Data.DATA11 + " TEXT," +
1357                Data.DATA12 + " TEXT," +
1358                Data.DATA13 + " TEXT," +
1359                Data.DATA14 + " TEXT," +
1360                Data.DATA15 + " TEXT," +
1361                Data.SYNC1 + " TEXT, " +
1362                Data.SYNC2 + " TEXT, " +
1363                Data.SYNC3 + " TEXT, " +
1364                Data.SYNC4 + " TEXT, " +
1365                Data.CARRIER_PRESENCE + " INTEGER NOT NULL DEFAULT 0 " +
1366        ");");
1367
1368        db.execSQL("CREATE INDEX data_raw_contact_id ON " + Tables.DATA + " (" +
1369                Data.RAW_CONTACT_ID +
1370        ");");
1371
1372        /**
1373         * For email lookup and similar queries.
1374         */
1375        db.execSQL("CREATE INDEX data_mimetype_data1_index ON " + Tables.DATA + " (" +
1376                DataColumns.MIMETYPE_ID + "," +
1377                Data.DATA1 +
1378        ");");
1379
1380        /**
1381         * For contact backup restore queries.
1382         */
1383        db.execSQL("CREATE INDEX IF NOT EXISTS data_hash_id_index ON " + Tables.DATA + " (" +
1384                Data.HASH_ID +
1385        ");");
1386
1387
1388        // Private phone numbers table used for lookup
1389        db.execSQL("CREATE TABLE " + Tables.PHONE_LOOKUP + " (" +
1390                PhoneLookupColumns.DATA_ID
1391                        + " INTEGER REFERENCES data(_id) NOT NULL," +
1392                PhoneLookupColumns.RAW_CONTACT_ID
1393                        + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," +
1394                PhoneLookupColumns.NORMALIZED_NUMBER + " TEXT NOT NULL," +
1395                PhoneLookupColumns.MIN_MATCH + " TEXT NOT NULL" +
1396        ");");
1397
1398        db.execSQL("CREATE INDEX phone_lookup_index ON " + Tables.PHONE_LOOKUP + " (" +
1399                PhoneLookupColumns.NORMALIZED_NUMBER + "," +
1400                PhoneLookupColumns.RAW_CONTACT_ID + "," +
1401                PhoneLookupColumns.DATA_ID +
1402        ");");
1403
1404        db.execSQL("CREATE INDEX phone_lookup_min_match_index ON " + Tables.PHONE_LOOKUP + " (" +
1405                PhoneLookupColumns.MIN_MATCH + "," +
1406                PhoneLookupColumns.RAW_CONTACT_ID + "," +
1407                PhoneLookupColumns.DATA_ID +
1408        ");");
1409
1410        db.execSQL("CREATE INDEX phone_lookup_data_id_min_match_index ON " + Tables.PHONE_LOOKUP +
1411                " (" + PhoneLookupColumns.DATA_ID + ", " + PhoneLookupColumns.MIN_MATCH + ");");
1412
1413        // Private name/nickname table used for lookup.
1414        db.execSQL("CREATE TABLE " + Tables.NAME_LOOKUP + " (" +
1415                NameLookupColumns.DATA_ID
1416                        + " INTEGER REFERENCES data(_id) NOT NULL," +
1417                NameLookupColumns.RAW_CONTACT_ID
1418                        + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," +
1419                NameLookupColumns.NORMALIZED_NAME + " TEXT NOT NULL," +
1420                NameLookupColumns.NAME_TYPE + " INTEGER NOT NULL," +
1421                "PRIMARY KEY ("
1422                        + NameLookupColumns.DATA_ID + ", "
1423                        + NameLookupColumns.NORMALIZED_NAME + ", "
1424                        + NameLookupColumns.NAME_TYPE + ")" +
1425        ");");
1426
1427        db.execSQL("CREATE INDEX name_lookup_raw_contact_id_index ON " + Tables.NAME_LOOKUP + " (" +
1428                NameLookupColumns.RAW_CONTACT_ID +
1429        ");");
1430
1431        db.execSQL("CREATE TABLE " + Tables.NICKNAME_LOOKUP + " (" +
1432                NicknameLookupColumns.NAME + " TEXT," +
1433                NicknameLookupColumns.CLUSTER + " TEXT" +
1434        ");");
1435
1436        db.execSQL("CREATE UNIQUE INDEX nickname_lookup_index ON " + Tables.NICKNAME_LOOKUP + " (" +
1437                NicknameLookupColumns.NAME + ", " +
1438                NicknameLookupColumns.CLUSTER +
1439        ");");
1440
1441        // Groups table.
1442        db.execSQL("CREATE TABLE " + Tables.GROUPS + " (" +
1443                Groups._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1444                GroupsColumns.PACKAGE_ID + " INTEGER REFERENCES package(_id)," +
1445                GroupsColumns.ACCOUNT_ID + " INTEGER REFERENCES " +
1446                    Tables.ACCOUNTS + "(" + AccountsColumns._ID + ")," +
1447                Groups.SOURCE_ID + " TEXT," +
1448                Groups.VERSION + " INTEGER NOT NULL DEFAULT 1," +
1449                Groups.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
1450                Groups.TITLE + " TEXT," +
1451                Groups.TITLE_RES + " INTEGER," +
1452                Groups.NOTES + " TEXT," +
1453                Groups.SYSTEM_ID + " TEXT," +
1454                Groups.DELETED + " INTEGER NOT NULL DEFAULT 0," +
1455                Groups.GROUP_VISIBLE + " INTEGER NOT NULL DEFAULT 0," +
1456                Groups.SHOULD_SYNC + " INTEGER NOT NULL DEFAULT 1," +
1457                Groups.AUTO_ADD + " INTEGER NOT NULL DEFAULT 0," +
1458                Groups.FAVORITES + " INTEGER NOT NULL DEFAULT 0," +
1459                Groups.GROUP_IS_READ_ONLY + " INTEGER NOT NULL DEFAULT 0," +
1460                Groups.SYNC1 + " TEXT, " +
1461                Groups.SYNC2 + " TEXT, " +
1462                Groups.SYNC3 + " TEXT, " +
1463                Groups.SYNC4 + " TEXT " +
1464        ");");
1465
1466        db.execSQL("CREATE INDEX groups_source_id_account_id_index ON " + Tables.GROUPS + " (" +
1467                Groups.SOURCE_ID + ", " +
1468                GroupsColumns.ACCOUNT_ID +
1469        ");");
1470
1471        db.execSQL("CREATE TABLE IF NOT EXISTS " + Tables.AGGREGATION_EXCEPTIONS + " (" +
1472                AggregationExceptionColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1473                AggregationExceptions.TYPE + " INTEGER NOT NULL, " +
1474                AggregationExceptions.RAW_CONTACT_ID1
1475                        + " INTEGER REFERENCES raw_contacts(_id), " +
1476                AggregationExceptions.RAW_CONTACT_ID2
1477                        + " INTEGER REFERENCES raw_contacts(_id)" +
1478        ");");
1479
1480        db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS aggregation_exception_index1 ON " +
1481                Tables.AGGREGATION_EXCEPTIONS + " (" +
1482                AggregationExceptions.RAW_CONTACT_ID1 + ", " +
1483                AggregationExceptions.RAW_CONTACT_ID2 +
1484        ");");
1485
1486        db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS aggregation_exception_index2 ON " +
1487                Tables.AGGREGATION_EXCEPTIONS + " (" +
1488                AggregationExceptions.RAW_CONTACT_ID2 + ", " +
1489                AggregationExceptions.RAW_CONTACT_ID1 +
1490        ");");
1491
1492        db.execSQL("CREATE TABLE IF NOT EXISTS " + Tables.SETTINGS + " (" +
1493                Settings.ACCOUNT_NAME + " STRING NOT NULL," +
1494                Settings.ACCOUNT_TYPE + " STRING NOT NULL," +
1495                Settings.DATA_SET + " STRING," +
1496                Settings.UNGROUPED_VISIBLE + " INTEGER NOT NULL DEFAULT 0," +
1497                Settings.SHOULD_SYNC + " INTEGER NOT NULL DEFAULT 1" +
1498        ");");
1499
1500        db.execSQL("CREATE TABLE " + Tables.VISIBLE_CONTACTS + " (" +
1501                Contacts._ID + " INTEGER PRIMARY KEY" +
1502        ");");
1503
1504        db.execSQL("CREATE TABLE " + Tables.DEFAULT_DIRECTORY + " (" +
1505                Contacts._ID + " INTEGER PRIMARY KEY" +
1506        ");");
1507
1508        // The table for recent calls is here so we can do table joins on people, phones, and
1509        // calls all in one place.
1510        // NOTE: When adding a new column to Tables.CALLS, make sure to also add it to
1511        // CallLogProvider.CALL_LOG_SYNC_PROJECTION, if it is a column that should be copied to
1512        // the call log of secondary users.
1513        db.execSQL("CREATE TABLE " + Tables.CALLS + " (" +
1514                Calls._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1515                Calls.NUMBER + " TEXT," +
1516                Calls.NUMBER_PRESENTATION + " INTEGER NOT NULL DEFAULT " +
1517                        Calls.PRESENTATION_ALLOWED + "," +
1518                Calls.DATE + " INTEGER," +
1519                Calls.DURATION + " INTEGER," +
1520                Calls.DATA_USAGE + " INTEGER," +
1521                Calls.TYPE + " INTEGER," +
1522                Calls.FEATURES + " INTEGER NOT NULL DEFAULT 0," +
1523                Calls.PHONE_ACCOUNT_COMPONENT_NAME + " TEXT," +
1524                Calls.PHONE_ACCOUNT_ID + " TEXT," +
1525                Calls.PHONE_ACCOUNT_ADDRESS + " TEXT," +
1526                Calls.PHONE_ACCOUNT_HIDDEN + " INTEGER NOT NULL DEFAULT 0," +
1527                Calls.SUB_ID + " INTEGER DEFAULT -1," +
1528                Calls.NEW + " INTEGER," +
1529                Calls.CACHED_NAME + " TEXT," +
1530                Calls.CACHED_NUMBER_TYPE + " INTEGER," +
1531                Calls.CACHED_NUMBER_LABEL + " TEXT," +
1532                Calls.COUNTRY_ISO + " TEXT," +
1533                Calls.VOICEMAIL_URI + " TEXT," +
1534                Calls.IS_READ + " INTEGER," +
1535                Calls.GEOCODED_LOCATION + " TEXT," +
1536                Calls.CACHED_LOOKUP_URI + " TEXT," +
1537                Calls.CACHED_MATCHED_NUMBER + " TEXT," +
1538                Calls.CACHED_NORMALIZED_NUMBER + " TEXT," +
1539                Calls.CACHED_PHOTO_ID + " INTEGER NOT NULL DEFAULT 0," +
1540                Calls.CACHED_PHOTO_URI + " TEXT," +
1541                Calls.CACHED_FORMATTED_NUMBER + " TEXT," +
1542                Voicemails._DATA + " TEXT," +
1543                Voicemails.HAS_CONTENT + " INTEGER," +
1544                Voicemails.MIME_TYPE + " TEXT," +
1545                Voicemails.SOURCE_DATA + " TEXT," +
1546                Voicemails.SOURCE_PACKAGE + " TEXT," +
1547                Voicemails.TRANSCRIPTION + " TEXT," +
1548                Voicemails.STATE + " INTEGER," +
1549                Voicemails.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
1550                Voicemails.DELETED + " INTEGER NOT NULL DEFAULT 0" +
1551        ");");
1552
1553        // Voicemail source status table.
1554        db.execSQL("CREATE TABLE " + Tables.VOICEMAIL_STATUS + " (" +
1555                VoicemailContract.Status._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1556                VoicemailContract.Status.SOURCE_PACKAGE + " TEXT UNIQUE NOT NULL," +
1557                VoicemailContract.Status.PHONE_ACCOUNT_COMPONENT_NAME + " TEXT," +
1558                VoicemailContract.Status.PHONE_ACCOUNT_ID + " TEXT," +
1559                VoicemailContract.Status.SETTINGS_URI + " TEXT," +
1560                VoicemailContract.Status.VOICEMAIL_ACCESS_URI + " TEXT," +
1561                VoicemailContract.Status.CONFIGURATION_STATE + " INTEGER," +
1562                VoicemailContract.Status.DATA_CHANNEL_STATE + " INTEGER," +
1563                VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE + " INTEGER" +
1564        ");");
1565
1566        db.execSQL("CREATE TABLE " + Tables.STATUS_UPDATES + " (" +
1567                StatusUpdatesColumns.DATA_ID + " INTEGER PRIMARY KEY REFERENCES data(_id)," +
1568                StatusUpdates.STATUS + " TEXT," +
1569                StatusUpdates.STATUS_TIMESTAMP + " INTEGER," +
1570                StatusUpdates.STATUS_RES_PACKAGE + " TEXT, " +
1571                StatusUpdates.STATUS_LABEL + " INTEGER, " +
1572                StatusUpdates.STATUS_ICON + " INTEGER" +
1573        ");");
1574
1575        createDirectoriesTable(db);
1576        createSearchIndexTable(db, false /* we build stats table later */);
1577
1578        db.execSQL("CREATE TABLE " + Tables.DATA_USAGE_STAT + "(" +
1579                DataUsageStatColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
1580                DataUsageStatColumns.DATA_ID + " INTEGER NOT NULL, " +
1581                DataUsageStatColumns.USAGE_TYPE_INT + " INTEGER NOT NULL DEFAULT 0, " +
1582                DataUsageStatColumns.TIMES_USED + " INTEGER NOT NULL DEFAULT 0, " +
1583                DataUsageStatColumns.LAST_TIME_USED + " INTERGER NOT NULL DEFAULT 0, " +
1584                "FOREIGN KEY(" + DataUsageStatColumns.DATA_ID + ") REFERENCES "
1585                        + Tables.DATA + "(" + Data._ID + ")" +
1586        ");");
1587        db.execSQL("CREATE UNIQUE INDEX data_usage_stat_index ON " +
1588                Tables.DATA_USAGE_STAT + " (" +
1589                DataUsageStatColumns.DATA_ID + ", " +
1590                DataUsageStatColumns.USAGE_TYPE_INT +
1591        ");");
1592
1593        db.execSQL("CREATE TABLE IF NOT EXISTS "
1594                + Tables.METADATA_SYNC + " (" +
1595                MetadataSync._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1596                MetadataSync.RAW_CONTACT_BACKUP_ID + " TEXT NOT NULL," +
1597                MetadataSyncColumns.ACCOUNT_ID + " INTEGER NOT NULL," +
1598                MetadataSync.DATA + " TEXT," +
1599                MetadataSync.DELETED + " INTEGER NOT NULL DEFAULT 0);");
1600
1601        db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS metadata_sync_index ON " +
1602                Tables.METADATA_SYNC + " (" +
1603                MetadataSync.RAW_CONTACT_BACKUP_ID + ", " +
1604                MetadataSyncColumns.ACCOUNT_ID +");");
1605
1606        db.execSQL("CREATE TABLE " + Tables.PRE_AUTHORIZED_URIS + " ("+
1607                PreAuthorizedUris._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
1608                PreAuthorizedUris.URI + " STRING NOT NULL, " +
1609                PreAuthorizedUris.EXPIRATION + " INTEGER NOT NULL DEFAULT 0);");
1610
1611        // When adding new tables, be sure to also add size-estimates in updateSqliteStats
1612        createContactsViews(db);
1613        createGroupsView(db);
1614        createContactsTriggers(db);
1615        createContactsIndexes(db, false /* we build stats table later */);
1616
1617        loadNicknameLookupTable(db);
1618
1619        // Set sequence starts.
1620        initializeAutoIncrementSequences(db);
1621
1622        // Add the legacy API support views, etc.
1623        LegacyApiSupport.createDatabase(db);
1624
1625        if (mDatabaseOptimizationEnabled) {
1626            // This will create a sqlite_stat1 table that is used for query optimization
1627            db.execSQL("ANALYZE;");
1628
1629            updateSqliteStats(db);
1630        }
1631
1632        ContentResolver.requestSync(null /* all accounts */,
1633                ContactsContract.AUTHORITY, new Bundle());
1634
1635        // Only send broadcasts for regular contacts db.
1636        if (dbForProfile() == 0) {
1637            final Intent dbCreatedIntent = new Intent(
1638                    ContactsContract.Intents.CONTACTS_DATABASE_CREATED);
1639            dbCreatedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
1640            mContext.sendBroadcast(dbCreatedIntent, android.Manifest.permission.READ_CONTACTS);
1641        }
1642    }
1643
1644    protected void initializeAutoIncrementSequences(SQLiteDatabase db) {
1645        // Default implementation does nothing.
1646    }
1647
1648    private void createDirectoriesTable(SQLiteDatabase db) {
1649        db.execSQL("CREATE TABLE " + Tables.DIRECTORIES + "(" +
1650                Directory._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1651                Directory.PACKAGE_NAME + " TEXT NOT NULL," +
1652                Directory.DIRECTORY_AUTHORITY + " TEXT NOT NULL," +
1653                Directory.TYPE_RESOURCE_ID + " INTEGER," +
1654                DirectoryColumns.TYPE_RESOURCE_NAME + " TEXT," +
1655                Directory.ACCOUNT_TYPE + " TEXT," +
1656                Directory.ACCOUNT_NAME + " TEXT," +
1657                Directory.DISPLAY_NAME + " TEXT, " +
1658                Directory.EXPORT_SUPPORT + " INTEGER NOT NULL" +
1659                        " DEFAULT " + Directory.EXPORT_SUPPORT_NONE + "," +
1660                Directory.SHORTCUT_SUPPORT + " INTEGER NOT NULL" +
1661                        " DEFAULT " + Directory.SHORTCUT_SUPPORT_NONE + "," +
1662                Directory.PHOTO_SUPPORT + " INTEGER NOT NULL" +
1663                        " DEFAULT " + Directory.PHOTO_SUPPORT_NONE +
1664        ");");
1665
1666        // Trigger a full scan of directories in the system
1667        setProperty(db, DbProperties.DIRECTORY_SCAN_COMPLETE, "0");
1668    }
1669
1670    public void createSearchIndexTable(SQLiteDatabase db, boolean rebuildSqliteStats) {
1671        db.execSQL("DROP TABLE IF EXISTS " + Tables.SEARCH_INDEX);
1672        db.execSQL("CREATE VIRTUAL TABLE " + Tables.SEARCH_INDEX
1673                + " USING FTS4 ("
1674                    + SearchIndexColumns.CONTACT_ID + " INTEGER REFERENCES contacts(_id) NOT NULL,"
1675                    + SearchIndexColumns.CONTENT + " TEXT, "
1676                    + SearchIndexColumns.NAME + " TEXT, "
1677                    + SearchIndexColumns.TOKENS + " TEXT"
1678                + ")");
1679        if (rebuildSqliteStats) {
1680            updateSqliteStats(db);
1681        }
1682    }
1683
1684    private void createContactsTriggers(SQLiteDatabase db) {
1685
1686        // Automatically delete Data rows when a raw contact is deleted.
1687        db.execSQL("DROP TRIGGER IF EXISTS " + Tables.RAW_CONTACTS + "_deleted;");
1688        db.execSQL("CREATE TRIGGER " + Tables.RAW_CONTACTS + "_deleted "
1689                + "   BEFORE DELETE ON " + Tables.RAW_CONTACTS
1690                + " BEGIN "
1691                + "   DELETE FROM " + Tables.DATA
1692                + "     WHERE " + Data.RAW_CONTACT_ID
1693                                + "=OLD." + RawContacts._ID + ";"
1694                + "   DELETE FROM " + Tables.AGGREGATION_EXCEPTIONS
1695                + "     WHERE " + AggregationExceptions.RAW_CONTACT_ID1
1696                                + "=OLD." + RawContacts._ID
1697                + "        OR " + AggregationExceptions.RAW_CONTACT_ID2
1698                                + "=OLD." + RawContacts._ID + ";"
1699                + "   DELETE FROM " + Tables.VISIBLE_CONTACTS
1700                + "     WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID
1701                + "       AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS
1702                + "            WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID
1703                + "           )=1;"
1704                + "   DELETE FROM " + Tables.DEFAULT_DIRECTORY
1705                + "     WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID
1706                + "       AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS
1707                + "            WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID
1708                + "           )=1;"
1709                + "   DELETE FROM " + Tables.CONTACTS
1710                + "     WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID
1711                + "       AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS
1712                + "            WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID
1713                + "           )=1;"
1714                + " END");
1715
1716
1717        db.execSQL("DROP TRIGGER IF EXISTS contacts_times_contacted;");
1718        db.execSQL("DROP TRIGGER IF EXISTS raw_contacts_times_contacted;");
1719
1720        // Triggers that update {@link RawContacts#VERSION} when the contact is marked for deletion
1721        // or any time a data row is inserted, updated or deleted.
1722        db.execSQL("DROP TRIGGER IF EXISTS " + Tables.RAW_CONTACTS + "_marked_deleted;");
1723        db.execSQL("CREATE TRIGGER " + Tables.RAW_CONTACTS + "_marked_deleted "
1724                + "   AFTER UPDATE ON " + Tables.RAW_CONTACTS
1725                + " BEGIN "
1726                + "   UPDATE " + Tables.RAW_CONTACTS
1727                + "     SET "
1728                +         RawContacts.VERSION + "=OLD." + RawContacts.VERSION + "+1 "
1729                + "     WHERE " + RawContacts._ID + "=OLD." + RawContacts._ID
1730                + "       AND NEW." + RawContacts.DELETED + "!= OLD." + RawContacts.DELETED + ";"
1731                + " END");
1732
1733        db.execSQL("DROP TRIGGER IF EXISTS " + Tables.DATA + "_updated;");
1734        db.execSQL("CREATE TRIGGER " + Tables.DATA + "_updated AFTER UPDATE ON " + Tables.DATA
1735                + " BEGIN "
1736                + "   UPDATE " + Tables.DATA
1737                + "     SET " + Data.DATA_VERSION + "=OLD." + Data.DATA_VERSION + "+1 "
1738                + "     WHERE " + Data._ID + "=OLD." + Data._ID + ";"
1739                + "   UPDATE " + Tables.RAW_CONTACTS
1740                + "     SET " + RawContacts.VERSION + "=" + RawContacts.VERSION + "+1 "
1741                + "     WHERE " + RawContacts._ID + "=OLD." + Data.RAW_CONTACT_ID + ";"
1742                + " END");
1743
1744        db.execSQL("DROP TRIGGER IF EXISTS " + Tables.DATA + "_deleted;");
1745        db.execSQL("CREATE TRIGGER " + Tables.DATA + "_deleted BEFORE DELETE ON " + Tables.DATA
1746                + " BEGIN "
1747                + "   UPDATE " + Tables.RAW_CONTACTS
1748                + "     SET " + RawContacts.VERSION + "=" + RawContacts.VERSION + "+1 "
1749                + "     WHERE " + RawContacts._ID + "=OLD." + Data.RAW_CONTACT_ID + ";"
1750                + "   DELETE FROM " + Tables.PHONE_LOOKUP
1751                + "     WHERE " + PhoneLookupColumns.DATA_ID + "=OLD." + Data._ID + ";"
1752                + "   DELETE FROM " + Tables.STATUS_UPDATES
1753                + "     WHERE " + StatusUpdatesColumns.DATA_ID + "=OLD." + Data._ID + ";"
1754                + "   DELETE FROM " + Tables.NAME_LOOKUP
1755                + "     WHERE " + NameLookupColumns.DATA_ID + "=OLD." + Data._ID + ";"
1756                + " END");
1757
1758
1759        db.execSQL("DROP TRIGGER IF EXISTS " + Tables.GROUPS + "_updated1;");
1760        db.execSQL("CREATE TRIGGER " + Tables.GROUPS + "_updated1 "
1761                + "   AFTER UPDATE ON " + Tables.GROUPS
1762                + " BEGIN "
1763                + "   UPDATE " + Tables.GROUPS
1764                + "     SET "
1765                +         Groups.VERSION + "=OLD." + Groups.VERSION + "+1"
1766                + "     WHERE " + Groups._ID + "=OLD." + Groups._ID + ";"
1767                + " END");
1768
1769        // Update DEFAULT_FILTER table per AUTO_ADD column update, see upgradeToVersion411.
1770        final String insertContactsWithoutAccount = (
1771                " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
1772                "     SELECT " + RawContacts.CONTACT_ID +
1773                "     FROM " + Tables.RAW_CONTACTS +
1774                "     WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID +
1775                            "=" + Clauses.LOCAL_ACCOUNT_ID + ";");
1776
1777        final String insertContactsWithAccountNoDefaultGroup = (
1778                " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
1779                "     SELECT " + RawContacts.CONTACT_ID +
1780                "         FROM " + Tables.RAW_CONTACTS +
1781                "     WHERE NOT EXISTS" +
1782                "         (SELECT " + Groups._ID +
1783                "             FROM " + Tables.GROUPS +
1784                "             WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID + " = " +
1785                                    GroupsColumns.CONCRETE_ACCOUNT_ID +
1786                "             AND " + Groups.AUTO_ADD + " != 0" + ");");
1787
1788        final String insertContactsWithAccountDefaultGroup = (
1789                " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
1790                "     SELECT " + RawContacts.CONTACT_ID +
1791                "         FROM " + Tables.RAW_CONTACTS +
1792                "     JOIN " + Tables.DATA +
1793                "           ON (" + RawContactsColumns.CONCRETE_ID + "=" +
1794                        Data.RAW_CONTACT_ID + ")" +
1795                "     WHERE " + DataColumns.MIMETYPE_ID + "=" +
1796                    "(SELECT " + MimetypesColumns._ID + " FROM " + Tables.MIMETYPES +
1797                        " WHERE " + MimetypesColumns.MIMETYPE +
1798                            "='" + GroupMembership.CONTENT_ITEM_TYPE + "')" +
1799                "     AND EXISTS" +
1800                "         (SELECT " + Groups._ID +
1801                "             FROM " + Tables.GROUPS +
1802                "                 WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID + " = " +
1803                                        GroupsColumns.CONCRETE_ACCOUNT_ID +
1804                "                 AND " + Groups.AUTO_ADD + " != 0" + ");");
1805
1806        db.execSQL("DROP TRIGGER IF EXISTS " + Tables.GROUPS + "_auto_add_updated1;");
1807        db.execSQL("CREATE TRIGGER " + Tables.GROUPS + "_auto_add_updated1 "
1808                + "   AFTER UPDATE OF " + Groups.AUTO_ADD + " ON " + Tables.GROUPS
1809                + " BEGIN "
1810                + "   DELETE FROM " + Tables.DEFAULT_DIRECTORY + ";"
1811                    + insertContactsWithoutAccount
1812                    + insertContactsWithAccountNoDefaultGroup
1813                    + insertContactsWithAccountDefaultGroup
1814                + " END");
1815    }
1816
1817    private void createContactsIndexes(SQLiteDatabase db, boolean rebuildSqliteStats) {
1818        db.execSQL("DROP INDEX IF EXISTS name_lookup_index");
1819        db.execSQL("CREATE INDEX name_lookup_index ON " + Tables.NAME_LOOKUP + " (" +
1820                NameLookupColumns.NORMALIZED_NAME + "," +
1821                NameLookupColumns.NAME_TYPE + ", " +
1822                NameLookupColumns.RAW_CONTACT_ID + ", " +
1823                NameLookupColumns.DATA_ID +
1824        ");");
1825
1826        db.execSQL("DROP INDEX IF EXISTS raw_contact_sort_key1_index");
1827        db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" +
1828                RawContacts.SORT_KEY_PRIMARY +
1829        ");");
1830
1831        db.execSQL("DROP INDEX IF EXISTS raw_contact_sort_key2_index");
1832        db.execSQL("CREATE INDEX raw_contact_sort_key2_index ON " + Tables.RAW_CONTACTS + " (" +
1833                RawContacts.SORT_KEY_ALTERNATIVE +
1834        ");");
1835
1836        if (rebuildSqliteStats) {
1837            updateSqliteStats(db);
1838        }
1839    }
1840
1841    private void createContactsViews(SQLiteDatabase db) {
1842        db.execSQL("DROP VIEW IF EXISTS " + Views.CONTACTS + ";");
1843        db.execSQL("DROP VIEW IF EXISTS " + Views.DATA + ";");
1844        db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_CONTACTS + ";");
1845        db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_ENTITIES + ";");
1846        db.execSQL("DROP VIEW IF EXISTS " + Views.ENTITIES + ";");
1847        db.execSQL("DROP VIEW IF EXISTS " + Views.DATA_USAGE_STAT + ";");
1848        db.execSQL("DROP VIEW IF EXISTS " + Views.STREAM_ITEMS + ";");
1849        db.execSQL("DROP VIEW IF EXISTS " + Views.METADATA_SYNC + ";");
1850
1851        String dataColumns =
1852                Data.IS_PRIMARY + ", "
1853                + Data.IS_SUPER_PRIMARY + ", "
1854                + Data.DATA_VERSION + ", "
1855                + DataColumns.CONCRETE_PACKAGE_ID + ","
1856                + PackagesColumns.PACKAGE + " AS " + Data.RES_PACKAGE + ","
1857                + DataColumns.CONCRETE_MIMETYPE_ID + ","
1858                + MimetypesColumns.MIMETYPE + " AS " + Data.MIMETYPE + ", "
1859                + Data.IS_READ_ONLY + ", "
1860                + Data.DATA1 + ", "
1861                + Data.DATA2 + ", "
1862                + Data.DATA3 + ", "
1863                + Data.DATA4 + ", "
1864                + Data.DATA5 + ", "
1865                + Data.DATA6 + ", "
1866                + Data.DATA7 + ", "
1867                + Data.DATA8 + ", "
1868                + Data.DATA9 + ", "
1869                + Data.DATA10 + ", "
1870                + Data.DATA11 + ", "
1871                + Data.DATA12 + ", "
1872                + Data.DATA13 + ", "
1873                + Data.DATA14 + ", "
1874                + Data.DATA15 + ", "
1875                + Data.CARRIER_PRESENCE + ", "
1876                + Data.SYNC1 + ", "
1877                + Data.SYNC2 + ", "
1878                + Data.SYNC3 + ", "
1879                + Data.SYNC4;
1880
1881        String syncColumns =
1882                RawContactsColumns.CONCRETE_ACCOUNT_ID + ","
1883                + AccountsColumns.CONCRETE_ACCOUNT_NAME + " AS " + RawContacts.ACCOUNT_NAME + ","
1884                + AccountsColumns.CONCRETE_ACCOUNT_TYPE + " AS " + RawContacts.ACCOUNT_TYPE + ","
1885                + AccountsColumns.CONCRETE_DATA_SET + " AS " + RawContacts.DATA_SET + ","
1886                + "(CASE WHEN " + AccountsColumns.CONCRETE_DATA_SET + " IS NULL THEN "
1887                            + AccountsColumns.CONCRETE_ACCOUNT_TYPE
1888                        + " ELSE " + AccountsColumns.CONCRETE_ACCOUNT_TYPE + "||'/'||"
1889                            + AccountsColumns.CONCRETE_DATA_SET + " END) AS "
1890                                + RawContacts.ACCOUNT_TYPE_AND_DATA_SET + ","
1891                + RawContactsColumns.CONCRETE_SOURCE_ID + " AS " + RawContacts.SOURCE_ID + ","
1892                + RawContactsColumns.CONCRETE_BACKUP_ID + " AS " + RawContacts.BACKUP_ID + ","
1893                + RawContactsColumns.CONCRETE_VERSION + " AS " + RawContacts.VERSION + ","
1894                + RawContactsColumns.CONCRETE_DIRTY + " AS " + RawContacts.DIRTY + ","
1895                + RawContactsColumns.CONCRETE_SYNC1 + " AS " + RawContacts.SYNC1 + ","
1896                + RawContactsColumns.CONCRETE_SYNC2 + " AS " + RawContacts.SYNC2 + ","
1897                + RawContactsColumns.CONCRETE_SYNC3 + " AS " + RawContacts.SYNC3 + ","
1898                + RawContactsColumns.CONCRETE_SYNC4 + " AS " + RawContacts.SYNC4;
1899
1900        String baseContactColumns =
1901                Contacts.HAS_PHONE_NUMBER + ", "
1902                + Contacts.NAME_RAW_CONTACT_ID + ", "
1903                + Contacts.LOOKUP_KEY + ", "
1904                + Contacts.PHOTO_ID + ", "
1905                + Contacts.PHOTO_FILE_ID + ", "
1906                + "CAST(" + Clauses.CONTACT_VISIBLE + " AS INTEGER) AS "
1907                        + Contacts.IN_VISIBLE_GROUP + ", "
1908                + "CAST(" + Clauses.CONTACT_IN_DEFAULT_DIRECTORY + " AS INTEGER) AS "
1909                        + Contacts.IN_DEFAULT_DIRECTORY + ", "
1910                + ContactsColumns.LAST_STATUS_UPDATE_ID + ", "
1911                + ContactsColumns.CONCRETE_CONTACT_LAST_UPDATED_TIMESTAMP;
1912
1913        String contactOptionColumns =
1914                ContactsColumns.CONCRETE_CUSTOM_RINGTONE
1915                        + " AS " + RawContacts.CUSTOM_RINGTONE + ","
1916                + ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL
1917                        + " AS " + RawContacts.SEND_TO_VOICEMAIL + ","
1918                + ContactsColumns.CONCRETE_LAST_TIME_CONTACTED
1919                        + " AS " + RawContacts.LAST_TIME_CONTACTED + ","
1920                + ContactsColumns.CONCRETE_TIMES_CONTACTED
1921                        + " AS " + RawContacts.TIMES_CONTACTED + ","
1922                + ContactsColumns.CONCRETE_STARRED
1923                        + " AS " + RawContacts.STARRED + ","
1924                + ContactsColumns.CONCRETE_PINNED
1925                        + " AS " + RawContacts.PINNED;
1926
1927        String contactNameColumns =
1928                "name_raw_contact." + RawContacts.DISPLAY_NAME_SOURCE
1929                        + " AS " + Contacts.DISPLAY_NAME_SOURCE + ", "
1930                + "name_raw_contact." + RawContacts.DISPLAY_NAME_PRIMARY
1931                        + " AS " + Contacts.DISPLAY_NAME_PRIMARY + ", "
1932                + "name_raw_contact." + RawContacts.DISPLAY_NAME_ALTERNATIVE
1933                        + " AS " + Contacts.DISPLAY_NAME_ALTERNATIVE + ", "
1934                + "name_raw_contact." + RawContacts.PHONETIC_NAME
1935                        + " AS " + Contacts.PHONETIC_NAME + ", "
1936                + "name_raw_contact." + RawContacts.PHONETIC_NAME_STYLE
1937                        + " AS " + Contacts.PHONETIC_NAME_STYLE + ", "
1938                + "name_raw_contact." + RawContacts.SORT_KEY_PRIMARY
1939                        + " AS " + Contacts.SORT_KEY_PRIMARY + ", "
1940                + "name_raw_contact." + RawContactsColumns.PHONEBOOK_LABEL_PRIMARY
1941                        + " AS " + ContactsColumns.PHONEBOOK_LABEL_PRIMARY + ", "
1942                + "name_raw_contact." + RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY
1943                        + " AS " + ContactsColumns.PHONEBOOK_BUCKET_PRIMARY + ", "
1944                + "name_raw_contact." + RawContacts.SORT_KEY_ALTERNATIVE
1945                        + " AS " + Contacts.SORT_KEY_ALTERNATIVE + ", "
1946                + "name_raw_contact." + RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE
1947                        + " AS " + ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + ", "
1948                + "name_raw_contact." + RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE
1949                        + " AS " + ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE;
1950
1951        String dataSelect = "SELECT "
1952                + DataColumns.CONCRETE_ID + " AS " + Data._ID + ","
1953                + Data.HASH_ID + ", "
1954                + Data.RAW_CONTACT_ID + ", "
1955                + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", "
1956                + syncColumns + ", "
1957                + dataColumns + ", "
1958                + contactOptionColumns + ", "
1959                + contactNameColumns + ", "
1960                + baseContactColumns + ", "
1961                + buildDisplayPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
1962                        Contacts.PHOTO_URI) + ", "
1963                + buildThumbnailPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
1964                        Contacts.PHOTO_THUMBNAIL_URI) + ", "
1965                + dbForProfile() + " AS " + RawContacts.RAW_CONTACT_IS_USER_PROFILE + ", "
1966                + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID
1967                + " FROM " + Tables.DATA
1968                + " JOIN " + Tables.MIMETYPES + " ON ("
1969                +   DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")"
1970                + " JOIN " + Tables.RAW_CONTACTS + " ON ("
1971                +   DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")"
1972                + " JOIN " + Tables.ACCOUNTS + " ON ("
1973                +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
1974                    + ")"
1975                + " JOIN " + Tables.CONTACTS + " ON ("
1976                +   RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")"
1977                + " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON("
1978                +   Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")"
1979                + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON ("
1980                +   DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")"
1981                + " LEFT OUTER JOIN " + Tables.GROUPS + " ON ("
1982                +   MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
1983                +   "' AND " + GroupsColumns.CONCRETE_ID + "="
1984                        + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")";
1985
1986        db.execSQL("CREATE VIEW " + Views.DATA + " AS " + dataSelect);
1987
1988        String rawContactOptionColumns =
1989                RawContacts.CUSTOM_RINGTONE + ","
1990                + RawContacts.SEND_TO_VOICEMAIL + ","
1991                + RawContacts.LAST_TIME_CONTACTED + ","
1992                + RawContacts.TIMES_CONTACTED + ","
1993                + RawContacts.STARRED + ","
1994                + RawContacts.PINNED;
1995
1996        String rawContactsSelect = "SELECT "
1997                + RawContactsColumns.CONCRETE_ID + " AS " + RawContacts._ID + ","
1998                + RawContacts.CONTACT_ID + ", "
1999                + RawContacts.AGGREGATION_MODE + ", "
2000                + RawContacts.RAW_CONTACT_IS_READ_ONLY + ", "
2001                + RawContacts.DELETED + ", "
2002                + RawContacts.DISPLAY_NAME_SOURCE  + ", "
2003                + RawContacts.DISPLAY_NAME_PRIMARY  + ", "
2004                + RawContacts.DISPLAY_NAME_ALTERNATIVE  + ", "
2005                + RawContacts.PHONETIC_NAME  + ", "
2006                + RawContacts.PHONETIC_NAME_STYLE  + ", "
2007                + RawContacts.SORT_KEY_PRIMARY  + ", "
2008                + RawContactsColumns.PHONEBOOK_LABEL_PRIMARY  + ", "
2009                + RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY  + ", "
2010                + RawContacts.SORT_KEY_ALTERNATIVE + ", "
2011                + RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE  + ", "
2012                + RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE  + ", "
2013                + dbForProfile() + " AS " + RawContacts.RAW_CONTACT_IS_USER_PROFILE + ", "
2014                + rawContactOptionColumns + ", "
2015                + syncColumns
2016                + " FROM " + Tables.RAW_CONTACTS
2017                + " JOIN " + Tables.ACCOUNTS + " ON ("
2018                +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
2019                    + ")";
2020
2021        db.execSQL("CREATE VIEW " + Views.RAW_CONTACTS + " AS " + rawContactsSelect);
2022
2023        String contactsColumns =
2024                ContactsColumns.CONCRETE_CUSTOM_RINGTONE
2025                        + " AS " + Contacts.CUSTOM_RINGTONE + ", "
2026                + contactNameColumns + ", "
2027                + baseContactColumns + ", "
2028                + ContactsColumns.CONCRETE_LAST_TIME_CONTACTED
2029                        + " AS " + Contacts.LAST_TIME_CONTACTED + ", "
2030                + ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL
2031                        + " AS " + Contacts.SEND_TO_VOICEMAIL + ", "
2032                + ContactsColumns.CONCRETE_STARRED
2033                        + " AS " + Contacts.STARRED + ", "
2034                + ContactsColumns.CONCRETE_PINNED
2035                + " AS " + Contacts.PINNED + ", "
2036                + ContactsColumns.CONCRETE_TIMES_CONTACTED
2037                        + " AS " + Contacts.TIMES_CONTACTED;
2038
2039        String contactsSelect = "SELECT "
2040                + ContactsColumns.CONCRETE_ID + " AS " + Contacts._ID + ","
2041                + contactsColumns + ", "
2042                + buildDisplayPhotoUriAlias(ContactsColumns.CONCRETE_ID, Contacts.PHOTO_URI) + ", "
2043                + buildThumbnailPhotoUriAlias(ContactsColumns.CONCRETE_ID,
2044                        Contacts.PHOTO_THUMBNAIL_URI) + ", "
2045                + dbForProfile() + " AS " + Contacts.IS_USER_PROFILE
2046                + " FROM " + Tables.CONTACTS
2047                + " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON("
2048                +   Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")";
2049
2050        db.execSQL("CREATE VIEW " + Views.CONTACTS + " AS " + contactsSelect);
2051
2052        String rawEntitiesSelect = "SELECT "
2053                + RawContacts.CONTACT_ID + ", "
2054                + RawContactsColumns.CONCRETE_DELETED + " AS " + RawContacts.DELETED + ","
2055                + dataColumns + ", "
2056                + syncColumns + ", "
2057                + Data.SYNC1 + ", "
2058                + Data.SYNC2 + ", "
2059                + Data.SYNC3 + ", "
2060                + Data.SYNC4 + ", "
2061                + RawContactsColumns.CONCRETE_ID + " AS " + RawContacts._ID + ", "
2062                + DataColumns.CONCRETE_ID + " AS " + RawContacts.Entity.DATA_ID + ","
2063                + RawContactsColumns.CONCRETE_STARRED + " AS " + RawContacts.STARRED + ","
2064                + dbForProfile() + " AS " + RawContacts.RAW_CONTACT_IS_USER_PROFILE + ","
2065                + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID
2066                + " FROM " + Tables.RAW_CONTACTS
2067                + " JOIN " + Tables.ACCOUNTS + " ON ("
2068                +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
2069                    + ")"
2070                + " LEFT OUTER JOIN " + Tables.DATA + " ON ("
2071                +   DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")"
2072                + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON ("
2073                +   DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")"
2074                + " LEFT OUTER JOIN " + Tables.MIMETYPES + " ON ("
2075                +   DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")"
2076                + " LEFT OUTER JOIN " + Tables.GROUPS + " ON ("
2077                +   MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
2078                +   "' AND " + GroupsColumns.CONCRETE_ID + "="
2079                + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")";
2080
2081        db.execSQL("CREATE VIEW " + Views.RAW_ENTITIES + " AS "
2082                + rawEntitiesSelect);
2083
2084        String entitiesSelect = "SELECT "
2085                + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + Contacts._ID + ", "
2086                + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", "
2087                + RawContactsColumns.CONCRETE_DELETED + " AS " + RawContacts.DELETED + ","
2088                + dataColumns + ", "
2089                + syncColumns + ", "
2090                + contactsColumns + ", "
2091                + buildDisplayPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
2092                        Contacts.PHOTO_URI) + ", "
2093                + buildThumbnailPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
2094                        Contacts.PHOTO_THUMBNAIL_URI) + ", "
2095                + dbForProfile() + " AS " + Contacts.IS_USER_PROFILE + ", "
2096                + Data.SYNC1 + ", "
2097                + Data.SYNC2 + ", "
2098                + Data.SYNC3 + ", "
2099                + Data.SYNC4 + ", "
2100                + RawContactsColumns.CONCRETE_ID + " AS " + Contacts.Entity.RAW_CONTACT_ID + ", "
2101                + DataColumns.CONCRETE_ID + " AS " + Contacts.Entity.DATA_ID + ","
2102                + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID
2103                + " FROM " + Tables.RAW_CONTACTS
2104                + " JOIN " + Tables.ACCOUNTS + " ON ("
2105                +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
2106                    + ")"
2107                + " JOIN " + Tables.CONTACTS + " ON ("
2108                +   RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")"
2109                + " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON("
2110                +   Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")"
2111                + " LEFT OUTER JOIN " + Tables.DATA + " ON ("
2112                +   DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")"
2113                + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON ("
2114                +   DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")"
2115                + " LEFT OUTER JOIN " + Tables.MIMETYPES + " ON ("
2116                +   DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")"
2117                + " LEFT OUTER JOIN " + Tables.GROUPS + " ON ("
2118                +   MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
2119                +   "' AND " + GroupsColumns.CONCRETE_ID + "="
2120                + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")";
2121
2122        db.execSQL("CREATE VIEW " + Views.ENTITIES + " AS "
2123                + entitiesSelect);
2124
2125        String dataUsageStatSelect = "SELECT "
2126                + DataUsageStatColumns.CONCRETE_ID + " AS " + DataUsageStatColumns._ID + ", "
2127                + DataUsageStatColumns.DATA_ID + ", "
2128                + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", "
2129                + MimetypesColumns.CONCRETE_MIMETYPE + " AS " + Data.MIMETYPE + ", "
2130                + DataUsageStatColumns.USAGE_TYPE_INT + ", "
2131                + DataUsageStatColumns.TIMES_USED + ", "
2132                + DataUsageStatColumns.LAST_TIME_USED
2133                + " FROM " + Tables.DATA_USAGE_STAT
2134                + " JOIN " + Tables.DATA + " ON ("
2135                +   DataColumns.CONCRETE_ID + "=" + DataUsageStatColumns.CONCRETE_DATA_ID + ")"
2136                + " JOIN " + Tables.RAW_CONTACTS + " ON ("
2137                +   RawContactsColumns.CONCRETE_ID + "=" + DataColumns.CONCRETE_RAW_CONTACT_ID
2138                    + " )"
2139                + " JOIN " + Tables.MIMETYPES + " ON ("
2140                +   MimetypesColumns.CONCRETE_ID + "=" + DataColumns.CONCRETE_MIMETYPE_ID + ")";
2141
2142        db.execSQL("CREATE VIEW " + Views.DATA_USAGE_STAT + " AS " + dataUsageStatSelect);
2143
2144        String streamItemSelect = "SELECT " +
2145                StreamItemsColumns.CONCRETE_ID + ", " +
2146                ContactsColumns.CONCRETE_ID + " AS " + StreamItems.CONTACT_ID + ", " +
2147                ContactsColumns.CONCRETE_LOOKUP_KEY +
2148                        " AS " + StreamItems.CONTACT_LOOKUP_KEY + ", " +
2149                AccountsColumns.CONCRETE_ACCOUNT_NAME + ", " +
2150                AccountsColumns.CONCRETE_ACCOUNT_TYPE + ", " +
2151                AccountsColumns.CONCRETE_DATA_SET + ", " +
2152                StreamItemsColumns.CONCRETE_RAW_CONTACT_ID +
2153                        " as " + StreamItems.RAW_CONTACT_ID + ", " +
2154                RawContactsColumns.CONCRETE_SOURCE_ID +
2155                        " as " + StreamItems.RAW_CONTACT_SOURCE_ID + ", " +
2156                StreamItemsColumns.CONCRETE_PACKAGE + ", " +
2157                StreamItemsColumns.CONCRETE_ICON + ", " +
2158                StreamItemsColumns.CONCRETE_LABEL + ", " +
2159                StreamItemsColumns.CONCRETE_TEXT + ", " +
2160                StreamItemsColumns.CONCRETE_TIMESTAMP + ", " +
2161                StreamItemsColumns.CONCRETE_COMMENTS + ", " +
2162                StreamItemsColumns.CONCRETE_SYNC1 + ", " +
2163                StreamItemsColumns.CONCRETE_SYNC2 + ", " +
2164                StreamItemsColumns.CONCRETE_SYNC3 + ", " +
2165                StreamItemsColumns.CONCRETE_SYNC4 +
2166                " FROM " + Tables.STREAM_ITEMS
2167                + " JOIN " + Tables.RAW_CONTACTS + " ON ("
2168                + StreamItemsColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID
2169                    + ")"
2170                + " JOIN " + Tables.ACCOUNTS + " ON ("
2171                +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
2172                    + ")"
2173                + " JOIN " + Tables.CONTACTS + " ON ("
2174                + RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")";
2175
2176        db.execSQL("CREATE VIEW " + Views.STREAM_ITEMS + " AS " + streamItemSelect);
2177
2178        String metadataSyncSelect = "SELECT " +
2179                MetadataSyncColumns.CONCRETE_ID + ", " +
2180                MetadataSync.RAW_CONTACT_BACKUP_ID + ", " +
2181                AccountsColumns.ACCOUNT_NAME + ", " +
2182                AccountsColumns.ACCOUNT_TYPE + ", " +
2183                AccountsColumns.DATA_SET + ", " +
2184                MetadataSync.DATA + ", " +
2185                MetadataSync.DELETED +
2186                " FROM " + Tables.METADATA_SYNC
2187                + " JOIN " + Tables.ACCOUNTS + " ON ("
2188                +   MetadataSyncColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
2189                + ")";
2190
2191        db.execSQL("CREATE VIEW " + Views.METADATA_SYNC + " AS " + metadataSyncSelect);
2192    }
2193
2194    private static String buildDisplayPhotoUriAlias(String contactIdColumn, String alias) {
2195        return "(CASE WHEN " + Contacts.PHOTO_FILE_ID + " IS NULL THEN (CASE WHEN "
2196                + Contacts.PHOTO_ID + " IS NULL"
2197                + " OR " + Contacts.PHOTO_ID + "=0"
2198                + " THEN NULL"
2199                + " ELSE '" + Contacts.CONTENT_URI + "/'||"
2200                        + contactIdColumn + "|| '/" + Photo.CONTENT_DIRECTORY + "'"
2201                + " END) ELSE '" + DisplayPhoto.CONTENT_URI + "/'||"
2202                        + Contacts.PHOTO_FILE_ID + " END)"
2203                + " AS " + alias;
2204    }
2205
2206    private static String buildThumbnailPhotoUriAlias(String contactIdColumn, String alias) {
2207        return "(CASE WHEN "
2208                + Contacts.PHOTO_ID + " IS NULL"
2209                + " OR " + Contacts.PHOTO_ID + "=0"
2210                + " THEN NULL"
2211                + " ELSE '" + Contacts.CONTENT_URI + "/'||"
2212                        + contactIdColumn + "|| '/" + Photo.CONTENT_DIRECTORY + "'"
2213                + " END)"
2214                + " AS " + alias;
2215    }
2216
2217    /**
2218     * Returns the value to be returned when querying the column indicating that the contact
2219     * or raw contact belongs to the user's personal profile.  Overridden in the profile
2220     * DB helper subclass.
2221     */
2222    protected int dbForProfile() {
2223        return 0;
2224    }
2225
2226    private void createGroupsView(SQLiteDatabase db) {
2227        db.execSQL("DROP VIEW IF EXISTS " + Views.GROUPS + ";");
2228
2229        String groupsColumns =
2230                GroupsColumns.CONCRETE_ACCOUNT_ID + " AS " + GroupsColumns.ACCOUNT_ID + ","
2231                + AccountsColumns.CONCRETE_ACCOUNT_NAME + " AS " + Groups.ACCOUNT_NAME + ","
2232                + AccountsColumns.CONCRETE_ACCOUNT_TYPE + " AS " + Groups.ACCOUNT_TYPE + ","
2233                + AccountsColumns.CONCRETE_DATA_SET + " AS " + Groups.DATA_SET + ","
2234                + "(CASE WHEN " + AccountsColumns.CONCRETE_DATA_SET
2235                    + " IS NULL THEN " + AccountsColumns.CONCRETE_ACCOUNT_TYPE
2236                    + " ELSE " + AccountsColumns.CONCRETE_ACCOUNT_TYPE
2237                        + "||'/'||" + AccountsColumns.CONCRETE_DATA_SET + " END) AS "
2238                            + Groups.ACCOUNT_TYPE_AND_DATA_SET + ","
2239                + Groups.SOURCE_ID + ","
2240                + Groups.VERSION + ","
2241                + Groups.DIRTY + ","
2242                + Groups.TITLE + ","
2243                + Groups.TITLE_RES + ","
2244                + Groups.NOTES + ","
2245                + Groups.SYSTEM_ID + ","
2246                + Groups.DELETED + ","
2247                + Groups.GROUP_VISIBLE + ","
2248                + Groups.SHOULD_SYNC + ","
2249                + Groups.AUTO_ADD + ","
2250                + Groups.FAVORITES + ","
2251                + Groups.GROUP_IS_READ_ONLY + ","
2252                + Groups.SYNC1 + ","
2253                + Groups.SYNC2 + ","
2254                + Groups.SYNC3 + ","
2255                + Groups.SYNC4 + ","
2256                + PackagesColumns.PACKAGE + " AS " + Groups.RES_PACKAGE;
2257
2258        String groupsSelect = "SELECT "
2259                + GroupsColumns.CONCRETE_ID + " AS " + Groups._ID + ","
2260                + groupsColumns
2261                + " FROM " + Tables.GROUPS
2262                + " JOIN " + Tables.ACCOUNTS + " ON ("
2263                    + GroupsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID + ")"
2264                + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON ("
2265                    + GroupsColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")";
2266
2267        db.execSQL("CREATE VIEW " + Views.GROUPS + " AS " + groupsSelect);
2268    }
2269
2270    @Override
2271    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
2272        Log.i(TAG, "ContactsProvider cannot proceed because downgrading your database is not " +
2273                "supported. To continue, please either re-upgrade to your previous Android " +
2274                "version, or clear all application data in Contacts Storage (this will result " +
2275                "in the loss of all local contacts that are not synced). To avoid data loss, " +
2276                "your contacts database will not be wiped automatically.");
2277        super.onDowngrade(db, oldVersion, newVersion);
2278    }
2279
2280    @Override
2281    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
2282        if (oldVersion < 99) {
2283            Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion
2284                    + ", data will be lost!");
2285
2286            db.execSQL("DROP TABLE IF EXISTS " + Tables.CONTACTS + ";");
2287            db.execSQL("DROP TABLE IF EXISTS " + Tables.RAW_CONTACTS + ";");
2288            db.execSQL("DROP TABLE IF EXISTS " + Tables.PACKAGES + ";");
2289            db.execSQL("DROP TABLE IF EXISTS " + Tables.MIMETYPES + ";");
2290            db.execSQL("DROP TABLE IF EXISTS " + Tables.DATA + ";");
2291            db.execSQL("DROP TABLE IF EXISTS " + Tables.PHONE_LOOKUP + ";");
2292            db.execSQL("DROP TABLE IF EXISTS " + Tables.NAME_LOOKUP + ";");
2293            db.execSQL("DROP TABLE IF EXISTS " + Tables.NICKNAME_LOOKUP + ";");
2294            db.execSQL("DROP TABLE IF EXISTS " + Tables.GROUPS + ";");
2295            db.execSQL("DROP TABLE IF EXISTS activities;");
2296            db.execSQL("DROP TABLE IF EXISTS " + Tables.CALLS + ";");
2297            db.execSQL("DROP TABLE IF EXISTS " + Tables.SETTINGS + ";");
2298            db.execSQL("DROP TABLE IF EXISTS " + Tables.STATUS_UPDATES + ";");
2299
2300            // TODO: we should not be dropping agg_exceptions and contact_options. In case that
2301            // table's schema changes, we should try to preserve the data, because it was entered
2302            // by the user and has never been synched to the server.
2303            db.execSQL("DROP TABLE IF EXISTS " + Tables.AGGREGATION_EXCEPTIONS + ";");
2304
2305            onCreate(db);
2306            return;
2307        }
2308
2309        Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion);
2310
2311        boolean upgradeViewsAndTriggers = false;
2312        boolean upgradeNameLookup = false;
2313        boolean upgradeLegacyApiSupport = false;
2314        boolean upgradeSearchIndex = false;
2315        boolean rescanDirectories = false;
2316        boolean rebuildSqliteStats = false;
2317        boolean upgradeLocaleSpecificData = false;
2318
2319        if (oldVersion == 99) {
2320            upgradeViewsAndTriggers = true;
2321            oldVersion++;
2322        }
2323
2324        if (oldVersion == 100) {
2325            db.execSQL("CREATE INDEX IF NOT EXISTS mimetypes_mimetype_index ON "
2326                    + Tables.MIMETYPES + " ("
2327                            + MimetypesColumns.MIMETYPE + ","
2328                            + MimetypesColumns._ID + ");");
2329            updateIndexStats(db, Tables.MIMETYPES,
2330                    "mimetypes_mimetype_index", "50 1 1");
2331
2332            upgradeViewsAndTriggers = true;
2333            oldVersion++;
2334        }
2335
2336        if (oldVersion == 101) {
2337            upgradeViewsAndTriggers = true;
2338            oldVersion++;
2339        }
2340
2341        if (oldVersion == 102) {
2342            upgradeViewsAndTriggers = true;
2343            oldVersion++;
2344        }
2345
2346        if (oldVersion == 103) {
2347            upgradeViewsAndTriggers = true;
2348            oldVersion++;
2349        }
2350
2351        if (oldVersion == 104 || oldVersion == 201) {
2352            LegacyApiSupport.createSettingsTable(db);
2353            upgradeViewsAndTriggers = true;
2354            oldVersion++;
2355        }
2356
2357        if (oldVersion == 105) {
2358            upgradeToVersion202(db);
2359            upgradeNameLookup = true;
2360            oldVersion = 202;
2361        }
2362
2363        if (oldVersion == 202) {
2364            upgradeToVersion203(db);
2365            upgradeViewsAndTriggers = true;
2366            oldVersion++;
2367        }
2368
2369        if (oldVersion == 203) {
2370            upgradeViewsAndTriggers = true;
2371            oldVersion++;
2372        }
2373
2374        if (oldVersion == 204) {
2375            upgradeToVersion205(db);
2376            upgradeViewsAndTriggers = true;
2377            oldVersion++;
2378        }
2379
2380        if (oldVersion == 205) {
2381            upgrateToVersion206(db);
2382            upgradeViewsAndTriggers = true;
2383            oldVersion++;
2384        }
2385
2386        if (oldVersion == 206) {
2387            // Fix for the bug where name lookup records for organizations would get removed by
2388            // unrelated updates of the data rows. No longer needed.
2389            oldVersion = 300;
2390        }
2391
2392        if (oldVersion == 300) {
2393            upgradeViewsAndTriggers = true;
2394            oldVersion = 301;
2395        }
2396
2397        if (oldVersion == 301) {
2398            upgradeViewsAndTriggers = true;
2399            oldVersion = 302;
2400        }
2401
2402        if (oldVersion == 302) {
2403            upgradeEmailToVersion303(db);
2404            upgradeNicknameToVersion303(db);
2405            oldVersion = 303;
2406        }
2407
2408        if (oldVersion == 303) {
2409            upgradeToVersion304(db);
2410            oldVersion = 304;
2411        }
2412
2413        if (oldVersion == 304) {
2414            upgradeNameLookup = true;
2415            oldVersion = 305;
2416        }
2417
2418        if (oldVersion == 305) {
2419            upgradeToVersion306(db);
2420            oldVersion = 306;
2421        }
2422
2423        if (oldVersion == 306) {
2424            upgradeToVersion307(db);
2425            oldVersion = 307;
2426        }
2427
2428        if (oldVersion == 307) {
2429            upgradeToVersion308(db);
2430            oldVersion = 308;
2431        }
2432
2433        // Gingerbread upgrades.
2434        if (oldVersion < 350) {
2435            upgradeViewsAndTriggers = true;
2436            oldVersion = 351;
2437        }
2438
2439        if (oldVersion == 351) {
2440            upgradeNameLookup = true;
2441            oldVersion = 352;
2442        }
2443
2444        if (oldVersion == 352) {
2445            upgradeToVersion353(db);
2446            oldVersion = 353;
2447        }
2448
2449        // Honeycomb upgrades.
2450        if (oldVersion < 400) {
2451            upgradeViewsAndTriggers = true;
2452            upgradeToVersion400(db);
2453            oldVersion = 400;
2454        }
2455
2456        if (oldVersion == 400) {
2457            upgradeViewsAndTriggers = true;
2458            upgradeToVersion401(db);
2459            oldVersion = 401;
2460        }
2461
2462        if (oldVersion == 401) {
2463            upgradeToVersion402(db);
2464            oldVersion = 402;
2465        }
2466
2467        if (oldVersion == 402) {
2468            upgradeViewsAndTriggers = true;
2469            upgradeToVersion403(db);
2470            oldVersion = 403;
2471        }
2472
2473        if (oldVersion == 403) {
2474            upgradeViewsAndTriggers = true;
2475            oldVersion = 404;
2476        }
2477
2478        if (oldVersion == 404) {
2479            upgradeViewsAndTriggers = true;
2480            upgradeToVersion405(db);
2481            oldVersion = 405;
2482        }
2483
2484        if (oldVersion == 405) {
2485            upgradeViewsAndTriggers = true;
2486            upgradeToVersion406(db);
2487            oldVersion = 406;
2488        }
2489
2490        if (oldVersion == 406) {
2491            upgradeViewsAndTriggers = true;
2492            oldVersion = 407;
2493        }
2494
2495        if (oldVersion == 407) {
2496            oldVersion = 408;  // Obsolete.
2497        }
2498
2499        if (oldVersion == 408) {
2500            upgradeViewsAndTriggers = true;
2501            upgradeToVersion409(db);
2502            oldVersion = 409;
2503        }
2504
2505        if (oldVersion == 409) {
2506            upgradeViewsAndTriggers = true;
2507            oldVersion = 410;
2508        }
2509
2510        if (oldVersion == 410) {
2511            upgradeToVersion411(db);
2512            oldVersion = 411;
2513        }
2514
2515        if (oldVersion == 411) {
2516            // Same upgrade as 353, only on Honeycomb devices.
2517            upgradeToVersion353(db);
2518            oldVersion = 412;
2519        }
2520
2521        if (oldVersion == 412) {
2522            upgradeToVersion413(db);
2523            oldVersion = 413;
2524        }
2525
2526        if (oldVersion == 413) {
2527            upgradeNameLookup = true;
2528            oldVersion = 414;
2529        }
2530
2531        if (oldVersion == 414) {
2532            upgradeToVersion415(db);
2533            upgradeViewsAndTriggers = true;
2534            oldVersion = 415;
2535        }
2536
2537        if (oldVersion == 415) {
2538            upgradeToVersion416(db);
2539            oldVersion = 416;
2540        }
2541
2542        if (oldVersion == 416) {
2543            upgradeLegacyApiSupport = true;
2544            oldVersion = 417;
2545        }
2546
2547        // Honeycomb-MR1 upgrades.
2548        if (oldVersion < 500) {
2549            upgradeSearchIndex = true;
2550        }
2551
2552        if (oldVersion < 501) {
2553            upgradeSearchIndex = true;
2554            upgradeToVersion501(db);
2555            oldVersion = 501;
2556        }
2557
2558        if (oldVersion < 502) {
2559            upgradeSearchIndex = true;
2560            upgradeToVersion502(db);
2561            oldVersion = 502;
2562        }
2563
2564        if (oldVersion < 503) {
2565            upgradeSearchIndex = true;
2566            oldVersion = 503;
2567        }
2568
2569        if (oldVersion < 504) {
2570            upgradeToVersion504(db);
2571            oldVersion = 504;
2572        }
2573
2574        if (oldVersion < 600) {
2575            // This change used to add the profile raw contact ID to the Accounts table.  That
2576            // column is no longer needed (as of version 614) since the profile records are stored in
2577            // a separate copy of the database for security reasons.  So this change is now a no-op.
2578            upgradeViewsAndTriggers = true;
2579            oldVersion = 600;
2580        }
2581
2582        if (oldVersion < 601) {
2583            upgradeToVersion601(db);
2584            oldVersion = 601;
2585        }
2586
2587        if (oldVersion < 602) {
2588            upgradeToVersion602(db);
2589            oldVersion = 602;
2590        }
2591
2592        if (oldVersion < 603) {
2593            upgradeViewsAndTriggers = true;
2594            oldVersion = 603;
2595        }
2596
2597        if (oldVersion < 604) {
2598            upgradeToVersion604(db);
2599            oldVersion = 604;
2600        }
2601
2602        if (oldVersion < 605) {
2603            upgradeViewsAndTriggers = true;
2604            // This version used to create the stream item and stream item photos tables, but
2605            // a newer version of those tables is created in version 609 below. So omitting the
2606            // creation in this upgrade step to avoid a create->drop->create.
2607            oldVersion = 605;
2608        }
2609
2610        if (oldVersion < 606) {
2611            upgradeViewsAndTriggers = true;
2612            upgradeLegacyApiSupport = true;
2613            upgradeToVersion606(db);
2614            oldVersion = 606;
2615        }
2616
2617        if (oldVersion < 607) {
2618            upgradeViewsAndTriggers = true;
2619            // We added "action" and "action_uri" to groups here, but realized this was not a smart
2620            // move. This upgrade step has been removed (all dogfood phones that executed this step
2621            // will have those columns, but that shouldn't hurt. Unfortunately, SQLite makes it
2622            // hard to remove columns).
2623            oldVersion = 607;
2624        }
2625
2626        if (oldVersion < 608) {
2627            upgradeViewsAndTriggers = true;
2628            upgradeToVersion608(db);
2629            oldVersion = 608;
2630        }
2631
2632        if (oldVersion < 609) {
2633            // This version used to create the stream item and stream item photos tables, but a
2634            // newer version of those tables is created in version 613 below.  So omitting the
2635            // creation in this upgrade step to avoid a create->drop->create.
2636            oldVersion = 609;
2637        }
2638
2639        if (oldVersion < 610) {
2640            upgradeToVersion610(db);
2641            oldVersion = 610;
2642        }
2643
2644        if (oldVersion < 611) {
2645            upgradeViewsAndTriggers = true;
2646            upgradeToVersion611(db);
2647            oldVersion = 611;
2648        }
2649
2650        if (oldVersion < 612) {
2651            upgradeViewsAndTriggers = true;
2652            upgradeToVersion612(db);
2653            oldVersion = 612;
2654        }
2655
2656        if (oldVersion < 613) {
2657            upgradeToVersion613(db);
2658            oldVersion = 613;
2659        }
2660
2661        if (oldVersion < 614) {
2662            // This creates the "view_stream_items" view.
2663            upgradeViewsAndTriggers = true;
2664            oldVersion = 614;
2665        }
2666
2667        if (oldVersion < 615) {
2668            upgradeToVersion615(db);
2669            oldVersion = 615;
2670        }
2671
2672        if (oldVersion < 616) {
2673            // This updates the "view_stream_items" view.
2674            upgradeViewsAndTriggers = true;
2675            oldVersion = 616;
2676        }
2677
2678        if (oldVersion < 617) {
2679            // This version upgrade obsoleted the profile_raw_contact_id field of the Accounts
2680            // table, but we aren't removing the column because it is very little data (and not
2681            // referenced anymore).  We do need to upgrade the views to handle the simplified
2682            // per-database "is profile" columns.
2683            upgradeViewsAndTriggers = true;
2684            oldVersion = 617;
2685        }
2686
2687        if (oldVersion < 618) {
2688            upgradeToVersion618(db);
2689            oldVersion = 618;
2690        }
2691
2692        if (oldVersion < 619) {
2693            upgradeViewsAndTriggers = true;
2694            oldVersion = 619;
2695        }
2696
2697        if (oldVersion < 620) {
2698            upgradeViewsAndTriggers = true;
2699            oldVersion = 620;
2700        }
2701
2702        if (oldVersion < 621) {
2703            upgradeSearchIndex = true;
2704            oldVersion = 621;
2705        }
2706
2707        if (oldVersion < 622) {
2708            upgradeToVersion622(db);
2709            oldVersion = 622;
2710        }
2711
2712        if (oldVersion < 623) {
2713            // Change FTS to normalize names using collation key.
2714            upgradeSearchIndex = true;
2715            oldVersion = 623;
2716        }
2717
2718        if (oldVersion < 624) {
2719            // Upgraded the SQLite index stats.
2720            upgradeViewsAndTriggers = true;
2721            oldVersion = 624;
2722        }
2723
2724        if (oldVersion < 625) {
2725            // Fix for search for hyphenated names
2726            upgradeSearchIndex = true;
2727            oldVersion = 625;
2728        }
2729
2730        if (oldVersion < 626) {
2731            upgradeToVersion626(db);
2732            upgradeViewsAndTriggers = true;
2733            oldVersion = 626;
2734        }
2735
2736        if (oldVersion < 700) {
2737            rescanDirectories = true;
2738            oldVersion = 700;
2739        }
2740
2741        if (oldVersion < 701) {
2742            upgradeToVersion701(db);
2743            oldVersion = 701;
2744        }
2745
2746        if (oldVersion < 702) {
2747            upgradeToVersion702(db);
2748            oldVersion = 702;
2749        }
2750
2751        if (oldVersion < 703) {
2752            // Now names like "L'Image" will be searchable.
2753            upgradeSearchIndex = true;
2754            oldVersion = 703;
2755        }
2756
2757        if (oldVersion < 704) {
2758            db.execSQL("DROP TABLE IF EXISTS activities;");
2759            oldVersion = 704;
2760        }
2761
2762        if (oldVersion < 705) {
2763            // Before this version, we didn't rebuild the search index on locale changes, so
2764            // if the locale has changed after sync, the index contains gets stale.
2765            // To correct the issue we have to rebuild the index here.
2766            upgradeSearchIndex = true;
2767            oldVersion = 705;
2768        }
2769
2770        if (oldVersion < 706) {
2771            // Prior to this version, we didn't rebuild the stats table after drop operations,
2772            // which resulted in losing some of the rows from the stats table.
2773            rebuildSqliteStats = true;
2774            oldVersion = 706;
2775        }
2776
2777        if (oldVersion < 707) {
2778            upgradeToVersion707(db);
2779            upgradeViewsAndTriggers = true;
2780            oldVersion = 707;
2781        }
2782
2783        if (oldVersion < 708) {
2784            // Sort keys, phonebook labels and buckets, and search keys have
2785            // changed so force a rebuild.
2786            upgradeLocaleSpecificData = true;
2787            oldVersion = 708;
2788        }
2789        if (oldVersion < 709) {
2790            // Added secondary locale phonebook labels; changed Japanese
2791            // and Chinese sort keys.
2792            upgradeLocaleSpecificData = true;
2793            oldVersion = 709;
2794        }
2795
2796        if (oldVersion < 710) {
2797            upgradeToVersion710(db);
2798            upgradeViewsAndTriggers = true;
2799            oldVersion = 710;
2800        }
2801
2802        if (oldVersion < 800) {
2803            upgradeToVersion800(db);
2804            oldVersion = 800;
2805        }
2806
2807        if (oldVersion < 801) {
2808            setProperty(db, DbProperties.DATABASE_TIME_CREATED, String.valueOf(
2809                    System.currentTimeMillis()));
2810            oldVersion = 801;
2811        }
2812
2813        if (oldVersion < 802) {
2814            upgradeToVersion802(db);
2815            upgradeViewsAndTriggers = true;
2816            oldVersion = 802;
2817        }
2818
2819        if (oldVersion < 803) {
2820            // Rebuild the search index so that names, organizations and nicknames are
2821            // now indexed as names.
2822            upgradeSearchIndex = true;
2823            oldVersion = 803;
2824        }
2825
2826        if (oldVersion < 804) {
2827            // Reserved.
2828            oldVersion = 804;
2829        }
2830
2831        if (oldVersion < 900) {
2832            upgradeViewsAndTriggers = true;
2833            oldVersion = 900;
2834        }
2835
2836        if (oldVersion < 901) {
2837            // Rebuild the search index to fix any search index that was previously in a
2838            // broken state due to b/11059351
2839            upgradeSearchIndex = true;
2840            oldVersion = 901;
2841        }
2842
2843        if (oldVersion < 902) {
2844            upgradeToVersion902(db);
2845            oldVersion = 902;
2846        }
2847
2848        if (oldVersion < 903) {
2849            upgradeToVersion903(db);
2850            oldVersion = 903;
2851        }
2852
2853        if (oldVersion < 904) {
2854            upgradeToVersion904(db);
2855            oldVersion = 904;
2856        }
2857
2858        if (oldVersion < 905) {
2859            upgradeToVersion905(db);
2860            oldVersion = 905;
2861        }
2862
2863        if (oldVersion < 906) {
2864            upgradeToVersion906(db);
2865            oldVersion = 906;
2866        }
2867
2868        if (oldVersion < 907) {
2869            // Rebuild NAME_LOOKUP.
2870            upgradeNameLookup = true;
2871            oldVersion = 907;
2872        }
2873
2874        if (oldVersion < 908) {
2875            upgradeToVersion908(db);
2876            oldVersion = 908;
2877        }
2878
2879        if (oldVersion < 909) {
2880            upgradeToVersion909(db);
2881            oldVersion = 909;
2882        }
2883
2884        if (oldVersion < 910) {
2885            upgradeToVersion910(db);
2886            oldVersion = 910;
2887        }
2888        if (oldVersion < 1000) {
2889            upgradeToVersion1000(db);
2890            upgradeViewsAndTriggers = true;
2891            oldVersion = 1000;
2892        }
2893        if (oldVersion < 1001) {
2894            upgradeToVersion1001(db);
2895            rebuildSqliteStats = true;
2896            oldVersion = 1001;
2897        }
2898
2899        if (oldVersion < 1002) {
2900            rebuildSqliteStats = true;
2901            upgradeToVersion1002(db);
2902            oldVersion = 1002;
2903        }
2904
2905        if (oldVersion < 1003) {
2906            upgradeToVersion1003(db);
2907            oldVersion = 1003;
2908        }
2909
2910        if (oldVersion < 1004) {
2911            upgradeToVersion1004(db);
2912            oldVersion = 1004;
2913        }
2914
2915        if (oldVersion < 1005) {
2916            upgradeToVersion1005(db);
2917            oldVersion = 1005;
2918        }
2919
2920        if (oldVersion < 1006) {
2921            upgradeViewsAndTriggers = true;
2922            oldVersion = 1006;
2923        }
2924
2925        if (oldVersion < 1007) {
2926            upgradeToVersion1007(db);
2927            oldVersion = 1007;
2928        }
2929
2930        if (oldVersion < 1008) {
2931            upgradeToVersion1008(db);
2932            upgradeViewsAndTriggers = true;
2933            oldVersion = 1008;
2934        }
2935
2936        if (oldVersion < 1009) {
2937            upgradeToVersion1009(db);
2938            oldVersion = 1009;
2939        }
2940
2941        if (oldVersion < 1010) {
2942            upgradeViewsAndTriggers = true;
2943            oldVersion = 1010;
2944        }
2945
2946        if (upgradeViewsAndTriggers) {
2947            createContactsViews(db);
2948            createGroupsView(db);
2949            createContactsTriggers(db);
2950            createContactsIndexes(db, false /* we build stats table later */);
2951            upgradeLegacyApiSupport = true;
2952            rebuildSqliteStats = true;
2953        }
2954
2955        if (upgradeLegacyApiSupport) {
2956            LegacyApiSupport.createViews(db);
2957        }
2958
2959        if (upgradeLocaleSpecificData) {
2960            upgradeLocaleData(db, false /* we build stats table later */);
2961            // Name lookups are rebuilt as part of the full locale rebuild
2962            upgradeNameLookup = false;
2963            upgradeSearchIndex = true;
2964            rebuildSqliteStats = true;
2965        }
2966
2967        if (upgradeNameLookup) {
2968            rebuildNameLookup(db, false /* we build stats table later */);
2969            rebuildSqliteStats = true;
2970        }
2971
2972        if (upgradeSearchIndex) {
2973            rebuildSearchIndex(db, false /* we build stats table later */);
2974            rebuildSqliteStats = true;
2975        }
2976
2977        if (rescanDirectories) {
2978            // Force the next ContactDirectoryManager.scanAllPackages() to rescan all packages.
2979            // (It's called from the BACKGROUND_TASK_UPDATE_ACCOUNTS background task.)
2980            setProperty(db, DbProperties.DIRECTORY_SCAN_COMPLETE, "0");
2981        }
2982
2983        if (rebuildSqliteStats) {
2984            updateSqliteStats(db);
2985        }
2986
2987        if (oldVersion != newVersion) {
2988            throw new IllegalStateException(
2989                    "error upgrading the database to version " + newVersion);
2990        }
2991    }
2992
2993    private void upgradeToVersion202(SQLiteDatabase db) {
2994        db.execSQL(
2995                "ALTER TABLE " + Tables.PHONE_LOOKUP +
2996                " ADD " + PhoneLookupColumns.MIN_MATCH + " TEXT;");
2997
2998        db.execSQL("CREATE INDEX phone_lookup_min_match_index ON " + Tables.PHONE_LOOKUP + " (" +
2999                PhoneLookupColumns.MIN_MATCH + "," +
3000                PhoneLookupColumns.RAW_CONTACT_ID + "," +
3001                PhoneLookupColumns.DATA_ID +
3002        ");");
3003
3004        updateIndexStats(db, Tables.PHONE_LOOKUP,
3005                "phone_lookup_min_match_index", "10000 2 2 1");
3006
3007        SQLiteStatement update = db.compileStatement(
3008                "UPDATE " + Tables.PHONE_LOOKUP +
3009                " SET " + PhoneLookupColumns.MIN_MATCH + "=?" +
3010                " WHERE " + PhoneLookupColumns.DATA_ID + "=?");
3011
3012        // Populate the new column
3013        Cursor c = db.query(Tables.PHONE_LOOKUP + " JOIN " + Tables.DATA +
3014                " ON (" + PhoneLookupColumns.DATA_ID + "=" + DataColumns.CONCRETE_ID + ")",
3015                new String[] {Data._ID, Phone.NUMBER}, null, null, null, null, null);
3016        try {
3017            while (c.moveToNext()) {
3018                long dataId = c.getLong(0);
3019                String number = c.getString(1);
3020                if (!TextUtils.isEmpty(number)) {
3021                    update.bindString(1, PhoneNumberUtils.toCallerIDMinMatch(number));
3022                    update.bindLong(2, dataId);
3023                    update.execute();
3024                }
3025            }
3026        } finally {
3027            c.close();
3028        }
3029    }
3030
3031    private void upgradeToVersion203(SQLiteDatabase db) {
3032        // Garbage-collect first. A bug in Eclair was sometimes leaving
3033        // raw_contacts in the database that no longer had contacts associated
3034        // with them.  To avoid failures during this database upgrade, drop
3035        // the orphaned raw_contacts.
3036        db.execSQL(
3037                "DELETE FROM raw_contacts" +
3038                " WHERE contact_id NOT NULL" +
3039                " AND contact_id NOT IN (SELECT _id FROM contacts)");
3040
3041        db.execSQL(
3042                "ALTER TABLE " + Tables.CONTACTS +
3043                " ADD " + Contacts.NAME_RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)");
3044        db.execSQL(
3045                "ALTER TABLE " + Tables.RAW_CONTACTS +
3046                " ADD contact_in_visible_group INTEGER NOT NULL DEFAULT 0");
3047
3048        // For each Contact, find the RawContact that contributed the display name
3049        db.execSQL(
3050                "UPDATE " + Tables.CONTACTS +
3051                        " SET " + Contacts.NAME_RAW_CONTACT_ID + "=(" +
3052                        " SELECT " + RawContacts._ID +
3053                        " FROM " + Tables.RAW_CONTACTS +
3054                        " WHERE " + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID +
3055                        " AND " + RawContactsColumns.CONCRETE_DISPLAY_NAME + "=" +
3056                        Tables.CONTACTS + "." + Contacts.DISPLAY_NAME +
3057                        " ORDER BY " + RawContacts._ID +
3058                        " LIMIT 1)"
3059        );
3060
3061        db.execSQL("CREATE INDEX contacts_name_raw_contact_id_index ON " + Tables.CONTACTS + " (" +
3062                Contacts.NAME_RAW_CONTACT_ID +
3063        ");");
3064
3065        // If for some unknown reason we missed some names, let's make sure there are
3066        // no contacts without a name, picking a raw contact "at random".
3067        db.execSQL(
3068                "UPDATE " + Tables.CONTACTS +
3069                " SET " + Contacts.NAME_RAW_CONTACT_ID + "=(" +
3070                        " SELECT " + RawContacts._ID +
3071                        " FROM " + Tables.RAW_CONTACTS +
3072                        " WHERE " + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID +
3073                        " ORDER BY " + RawContacts._ID +
3074                        " LIMIT 1)" +
3075                " WHERE " + Contacts.NAME_RAW_CONTACT_ID + " IS NULL"
3076        );
3077
3078        // Wipe out DISPLAY_NAME on the Contacts table as it is no longer in use.
3079        db.execSQL(
3080                "UPDATE " + Tables.CONTACTS +
3081                " SET " + Contacts.DISPLAY_NAME + "=NULL"
3082        );
3083
3084        // Copy the IN_VISIBLE_GROUP flag down to all raw contacts to allow
3085        // indexing on (display_name, in_visible_group)
3086        db.execSQL(
3087                "UPDATE " + Tables.RAW_CONTACTS +
3088                " SET contact_in_visible_group=(" +
3089                        "SELECT " + Contacts.IN_VISIBLE_GROUP +
3090                        " FROM " + Tables.CONTACTS +
3091                        " WHERE " + Contacts._ID + "=" + RawContacts.CONTACT_ID + ")" +
3092                " WHERE " + RawContacts.CONTACT_ID + " NOT NULL"
3093        );
3094
3095        db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" +
3096                "contact_in_visible_group" + "," +
3097                RawContactsColumns.DISPLAY_NAME + " COLLATE LOCALIZED ASC" +
3098        ");");
3099
3100        db.execSQL("DROP INDEX contacts_visible_index");
3101        db.execSQL("CREATE INDEX contacts_visible_index ON " + Tables.CONTACTS + " (" +
3102                Contacts.IN_VISIBLE_GROUP +
3103        ");");
3104    }
3105
3106    private void upgradeToVersion205(SQLiteDatabase db) {
3107        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
3108                + " ADD " + RawContacts.DISPLAY_NAME_ALTERNATIVE + " TEXT;");
3109        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
3110                + " ADD " + RawContacts.PHONETIC_NAME + " TEXT;");
3111        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
3112                + " ADD " + RawContacts.PHONETIC_NAME_STYLE + " INTEGER;");
3113        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
3114                + " ADD " + RawContacts.SORT_KEY_PRIMARY
3115                + " TEXT COLLATE " + ContactsProvider2.PHONEBOOK_COLLATOR_NAME + ";");
3116        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
3117                + " ADD " + RawContacts.SORT_KEY_ALTERNATIVE
3118                + " TEXT COLLATE " + ContactsProvider2.PHONEBOOK_COLLATOR_NAME + ";");
3119
3120        NameSplitter splitter = createNameSplitter();
3121
3122        SQLiteStatement rawContactUpdate = db.compileStatement(
3123                "UPDATE " + Tables.RAW_CONTACTS +
3124                " SET " +
3125                        RawContacts.DISPLAY_NAME_PRIMARY + "=?," +
3126                        RawContacts.DISPLAY_NAME_ALTERNATIVE + "=?," +
3127                        RawContacts.PHONETIC_NAME + "=?," +
3128                        RawContacts.PHONETIC_NAME_STYLE + "=?," +
3129                        RawContacts.SORT_KEY_PRIMARY + "=?," +
3130                        RawContacts.SORT_KEY_ALTERNATIVE + "=?" +
3131                " WHERE " + RawContacts._ID + "=?");
3132
3133        upgradeStructuredNamesToVersion205(db, rawContactUpdate, splitter);
3134        upgradeOrganizationsToVersion205(db, rawContactUpdate, splitter);
3135
3136        db.execSQL("DROP INDEX raw_contact_sort_key1_index");
3137        db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" +
3138                "contact_in_visible_group" + "," +
3139                RawContacts.SORT_KEY_PRIMARY +
3140        ");");
3141
3142        db.execSQL("CREATE INDEX raw_contact_sort_key2_index ON " + Tables.RAW_CONTACTS + " (" +
3143                "contact_in_visible_group" + "," +
3144                RawContacts.SORT_KEY_ALTERNATIVE +
3145        ");");
3146    }
3147
3148    private void upgradeStructuredNamesToVersion205(
3149            SQLiteDatabase db, SQLiteStatement rawContactUpdate, NameSplitter splitter) {
3150
3151        // Process structured names to detect the style of the full name and phonetic name.
3152        long mMimeType;
3153        try {
3154            mMimeType = DatabaseUtils.longForQuery(db,
3155                    "SELECT " + MimetypesColumns._ID +
3156                    " FROM " + Tables.MIMETYPES +
3157                    " WHERE " + MimetypesColumns.MIMETYPE
3158                            + "='" + StructuredName.CONTENT_ITEM_TYPE + "'", null);
3159
3160        } catch (SQLiteDoneException e) {
3161            // No structured names in the database.
3162            return;
3163        }
3164
3165        SQLiteStatement structuredNameUpdate = db.compileStatement(
3166                "UPDATE " + Tables.DATA +
3167                        " SET " +
3168                        StructuredName.FULL_NAME_STYLE + "=?," +
3169                        StructuredName.DISPLAY_NAME + "=?," +
3170                        StructuredName.PHONETIC_NAME_STYLE + "=?" +
3171                        " WHERE " + Data._ID + "=?");
3172
3173        NameSplitter.Name name = new NameSplitter.Name();
3174        Cursor cursor = db.query(StructName205Query.TABLE,
3175                StructName205Query.COLUMNS,
3176                DataColumns.MIMETYPE_ID + "=" + mMimeType, null, null, null, null);
3177        try {
3178            while (cursor.moveToNext()) {
3179                long dataId = cursor.getLong(StructName205Query.ID);
3180                long rawContactId = cursor.getLong(StructName205Query.RAW_CONTACT_ID);
3181                int displayNameSource = cursor.getInt(StructName205Query.DISPLAY_NAME_SOURCE);
3182
3183                name.clear();
3184                name.prefix = cursor.getString(StructName205Query.PREFIX);
3185                name.givenNames = cursor.getString(StructName205Query.GIVEN_NAME);
3186                name.middleName = cursor.getString(StructName205Query.MIDDLE_NAME);
3187                name.familyName = cursor.getString(StructName205Query.FAMILY_NAME);
3188                name.suffix = cursor.getString(StructName205Query.SUFFIX);
3189                name.phoneticFamilyName = cursor.getString(StructName205Query.PHONETIC_FAMILY_NAME);
3190                name.phoneticMiddleName = cursor.getString(StructName205Query.PHONETIC_MIDDLE_NAME);
3191                name.phoneticGivenName = cursor.getString(StructName205Query.PHONETIC_GIVEN_NAME);
3192
3193                upgradeNameToVersion205(dataId, rawContactId, displayNameSource, name,
3194                        structuredNameUpdate, rawContactUpdate, splitter);
3195            }
3196        } finally {
3197            cursor.close();
3198        }
3199    }
3200
3201    private void upgradeNameToVersion205(
3202            long dataId,
3203            long rawContactId,
3204            int displayNameSource,
3205            NameSplitter.Name name,
3206            SQLiteStatement structuredNameUpdate,
3207            SQLiteStatement rawContactUpdate,
3208            NameSplitter splitter) {
3209
3210        splitter.guessNameStyle(name);
3211        int unadjustedFullNameStyle = name.fullNameStyle;
3212        name.fullNameStyle = splitter.getAdjustedFullNameStyle(name.fullNameStyle);
3213        String displayName = splitter.join(name, true, true);
3214
3215        // Don't update database with the adjusted fullNameStyle as it is locale
3216        // related
3217        structuredNameUpdate.bindLong(1, unadjustedFullNameStyle);
3218        DatabaseUtils.bindObjectToProgram(structuredNameUpdate, 2, displayName);
3219        structuredNameUpdate.bindLong(3, name.phoneticNameStyle);
3220        structuredNameUpdate.bindLong(4, dataId);
3221        structuredNameUpdate.execute();
3222
3223        if (displayNameSource == DisplayNameSources.STRUCTURED_NAME) {
3224            String displayNameAlternative = splitter.join(name, false, false);
3225            String phoneticName = splitter.joinPhoneticName(name);
3226            String sortKey = null;
3227            String sortKeyAlternative = null;
3228
3229            if (phoneticName != null) {
3230                sortKey = sortKeyAlternative = phoneticName;
3231            } else if (name.fullNameStyle == FullNameStyle.CHINESE ||
3232                    name.fullNameStyle == FullNameStyle.CJK) {
3233                sortKey = sortKeyAlternative = displayName;
3234            }
3235
3236            if (sortKey == null) {
3237                sortKey = displayName;
3238                sortKeyAlternative = displayNameAlternative;
3239            }
3240
3241            updateRawContact205(rawContactUpdate, rawContactId, displayName,
3242                    displayNameAlternative, name.phoneticNameStyle, phoneticName, sortKey,
3243                    sortKeyAlternative);
3244        }
3245    }
3246
3247    private void upgradeOrganizationsToVersion205(
3248            SQLiteDatabase db, SQLiteStatement rawContactUpdate, NameSplitter splitter) {
3249
3250        final long mimeType = lookupMimeTypeId(db, Organization.CONTENT_ITEM_TYPE);
3251        SQLiteStatement organizationUpdate = db.compileStatement(
3252                "UPDATE " + Tables.DATA +
3253                " SET " +
3254                        Organization.PHONETIC_NAME_STYLE + "=?" +
3255                " WHERE " + Data._ID + "=?");
3256
3257        Cursor cursor = db.query(Organization205Query.TABLE, Organization205Query.COLUMNS,
3258                DataColumns.MIMETYPE_ID + "=" + mimeType + " AND "
3259                        + RawContacts.DISPLAY_NAME_SOURCE + "=" + DisplayNameSources.ORGANIZATION,
3260                null, null, null, null);
3261        try {
3262            while (cursor.moveToNext()) {
3263                long dataId = cursor.getLong(Organization205Query.ID);
3264                long rawContactId = cursor.getLong(Organization205Query.RAW_CONTACT_ID);
3265                String company = cursor.getString(Organization205Query.COMPANY);
3266                String phoneticName = cursor.getString(Organization205Query.PHONETIC_NAME);
3267
3268                int phoneticNameStyle = splitter.guessPhoneticNameStyle(phoneticName);
3269
3270                organizationUpdate.bindLong(1, phoneticNameStyle);
3271                organizationUpdate.bindLong(2, dataId);
3272                organizationUpdate.execute();
3273
3274                String sortKey = company;
3275
3276                updateRawContact205(rawContactUpdate, rawContactId, company,
3277                        company, phoneticNameStyle, phoneticName, sortKey, sortKey);
3278            }
3279        } finally {
3280            cursor.close();
3281        }
3282    }
3283
3284    private void updateRawContact205(SQLiteStatement rawContactUpdate, long rawContactId,
3285            String displayName, String displayNameAlternative, int phoneticNameStyle,
3286            String phoneticName, String sortKeyPrimary, String sortKeyAlternative) {
3287        bindString(rawContactUpdate, 1, displayName);
3288        bindString(rawContactUpdate, 2, displayNameAlternative);
3289        bindString(rawContactUpdate, 3, phoneticName);
3290        rawContactUpdate.bindLong(4, phoneticNameStyle);
3291        bindString(rawContactUpdate, 5, sortKeyPrimary);
3292        bindString(rawContactUpdate, 6, sortKeyAlternative);
3293        rawContactUpdate.bindLong(7, rawContactId);
3294        rawContactUpdate.execute();
3295    }
3296
3297    private void upgrateToVersion206(SQLiteDatabase db) {
3298        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
3299                + " ADD name_verified INTEGER NOT NULL DEFAULT 0;");
3300    }
3301
3302    /**
3303     * The {@link ContactsProvider2#update} method was deleting name lookup for new
3304     * emails during the sync.  We need to restore the lost name lookup rows.
3305     */
3306    private void upgradeEmailToVersion303(SQLiteDatabase db) {
3307        final long mimeTypeId = lookupMimeTypeId(db, Email.CONTENT_ITEM_TYPE);
3308        if (mimeTypeId == -1) {
3309            return;
3310        }
3311
3312        ContentValues values = new ContentValues();
3313
3314        // Find all data rows with the mime type "email" that are missing name lookup
3315        Cursor cursor = db.query(Upgrade303Query.TABLE, Upgrade303Query.COLUMNS,
3316                Upgrade303Query.SELECTION, new String[] {String.valueOf(mimeTypeId)},
3317                null, null, null);
3318        try {
3319            while (cursor.moveToNext()) {
3320                long dataId = cursor.getLong(Upgrade303Query.ID);
3321                long rawContactId = cursor.getLong(Upgrade303Query.RAW_CONTACT_ID);
3322                String value = cursor.getString(Upgrade303Query.DATA1);
3323                value = extractHandleFromEmailAddress(value);
3324
3325                if (value != null) {
3326                    values.put(NameLookupColumns.DATA_ID, dataId);
3327                    values.put(NameLookupColumns.RAW_CONTACT_ID, rawContactId);
3328                    values.put(NameLookupColumns.NAME_TYPE, NameLookupType.EMAIL_BASED_NICKNAME);
3329                    values.put(NameLookupColumns.NORMALIZED_NAME, NameNormalizer.normalize(value));
3330                    db.insert(Tables.NAME_LOOKUP, null, values);
3331                }
3332            }
3333        } finally {
3334            cursor.close();
3335        }
3336    }
3337
3338    /**
3339     * The {@link ContactsProvider2#update} method was deleting name lookup for new
3340     * nicknames during the sync.  We need to restore the lost name lookup rows.
3341     */
3342    private void upgradeNicknameToVersion303(SQLiteDatabase db) {
3343        final long mimeTypeId = lookupMimeTypeId(db, Nickname.CONTENT_ITEM_TYPE);
3344        if (mimeTypeId == -1) {
3345            return;
3346        }
3347
3348        ContentValues values = new ContentValues();
3349
3350        // Find all data rows with the mime type "nickname" that are missing name lookup
3351        Cursor cursor = db.query(Upgrade303Query.TABLE, Upgrade303Query.COLUMNS,
3352                Upgrade303Query.SELECTION, new String[] {String.valueOf(mimeTypeId)},
3353                null, null, null);
3354        try {
3355            while (cursor.moveToNext()) {
3356                long dataId = cursor.getLong(Upgrade303Query.ID);
3357                long rawContactId = cursor.getLong(Upgrade303Query.RAW_CONTACT_ID);
3358                String value = cursor.getString(Upgrade303Query.DATA1);
3359
3360                values.put(NameLookupColumns.DATA_ID, dataId);
3361                values.put(NameLookupColumns.RAW_CONTACT_ID, rawContactId);
3362                values.put(NameLookupColumns.NAME_TYPE, NameLookupType.NICKNAME);
3363                values.put(NameLookupColumns.NORMALIZED_NAME, NameNormalizer.normalize(value));
3364                db.insert(Tables.NAME_LOOKUP, null, values);
3365            }
3366        } finally {
3367            cursor.close();
3368        }
3369    }
3370
3371    private void upgradeToVersion304(SQLiteDatabase db) {
3372        // Mimetype table requires an index on mime type.
3373        db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS mime_type ON " + Tables.MIMETYPES + " (" +
3374                MimetypesColumns.MIMETYPE +
3375        ");");
3376    }
3377
3378    private void upgradeToVersion306(SQLiteDatabase db) {
3379        // Fix invalid lookup that was used for Exchange contacts (it was not escaped)
3380        // It happened when a new contact was created AND synchronized
3381        final StringBuilder lookupKeyBuilder = new StringBuilder();
3382        final SQLiteStatement updateStatement = db.compileStatement(
3383                "UPDATE contacts " +
3384                "SET lookup=? " +
3385                "WHERE _id=?");
3386        final Cursor contactIdCursor = db.rawQuery(
3387                "SELECT DISTINCT contact_id " +
3388                "FROM raw_contacts " +
3389                "WHERE deleted=0 AND account_type='com.android.exchange'",
3390                null);
3391        try {
3392            while (contactIdCursor.moveToNext()) {
3393                final long contactId = contactIdCursor.getLong(0);
3394                lookupKeyBuilder.setLength(0);
3395                final Cursor c = db.rawQuery(
3396                        "SELECT account_type, account_name, _id, sourceid, display_name " +
3397                        "FROM raw_contacts " +
3398                        "WHERE contact_id=? " +
3399                        "ORDER BY _id",
3400                        new String[] {String.valueOf(contactId)});
3401                try {
3402                    while (c.moveToNext()) {
3403                        ContactLookupKey.appendToLookupKey(lookupKeyBuilder,
3404                                c.getString(0),
3405                                c.getString(1),
3406                                c.getLong(2),
3407                                c.getString(3),
3408                                c.getString(4));
3409                    }
3410                } finally {
3411                    c.close();
3412                }
3413
3414                if (lookupKeyBuilder.length() == 0) {
3415                    updateStatement.bindNull(1);
3416                } else {
3417                    updateStatement.bindString(1, Uri.encode(lookupKeyBuilder.toString()));
3418                }
3419                updateStatement.bindLong(2, contactId);
3420
3421                updateStatement.execute();
3422            }
3423        } finally {
3424            updateStatement.close();
3425            contactIdCursor.close();
3426        }
3427    }
3428
3429    private void upgradeToVersion307(SQLiteDatabase db) {
3430        db.execSQL("CREATE TABLE properties (" +
3431                "property_key TEXT PRIMARY_KEY, " +
3432                "property_value TEXT" +
3433        ");");
3434    }
3435
3436    private void upgradeToVersion308(SQLiteDatabase db) {
3437        db.execSQL("CREATE TABLE accounts (" +
3438                "account_name TEXT, " +
3439                "account_type TEXT " +
3440        ");");
3441
3442        db.execSQL("INSERT INTO accounts " +
3443                "SELECT DISTINCT account_name, account_type FROM raw_contacts");
3444    }
3445
3446    private void upgradeToVersion400(SQLiteDatabase db) {
3447        db.execSQL("ALTER TABLE " + Tables.GROUPS
3448                + " ADD " + Groups.FAVORITES + " INTEGER NOT NULL DEFAULT 0;");
3449        db.execSQL("ALTER TABLE " + Tables.GROUPS
3450                + " ADD " + Groups.AUTO_ADD + " INTEGER NOT NULL DEFAULT 0;");
3451    }
3452
3453    private void upgradeToVersion353(SQLiteDatabase db) {
3454        db.execSQL("DELETE FROM contacts " +
3455                "WHERE NOT EXISTS (SELECT 1 FROM raw_contacts WHERE contact_id=contacts._id)");
3456    }
3457
3458    private void rebuildNameLookup(SQLiteDatabase db, boolean rebuildSqliteStats) {
3459        db.execSQL("DROP INDEX IF EXISTS name_lookup_index");
3460        insertNameLookup(db);
3461        createContactsIndexes(db, rebuildSqliteStats);
3462    }
3463
3464    protected void rebuildSearchIndex() {
3465        rebuildSearchIndex(getWritableDatabase(), true);
3466    }
3467
3468    private void rebuildSearchIndex(SQLiteDatabase db, boolean rebuildSqliteStats) {
3469        createSearchIndexTable(db, rebuildSqliteStats);
3470        setProperty(db, SearchIndexManager.PROPERTY_SEARCH_INDEX_VERSION, "0");
3471    }
3472
3473    /**
3474     * Checks whether the current ICU code version matches that used to build
3475     * the locale specific data in the ContactsDB.
3476     */
3477    public boolean needsToUpdateLocaleData(LocaleSet locales) {
3478        final String dbLocale = getProperty(DbProperties.LOCALE, "");
3479        if (!dbLocale.equals(locales.toString())) {
3480            return true;
3481        }
3482        final String curICUVersion = ICU.getIcuVersion();
3483        final String dbICUVersion = getProperty(DbProperties.ICU_VERSION,
3484                "(unknown)");
3485        if (!curICUVersion.equals(dbICUVersion)) {
3486            Log.i(TAG, "ICU version has changed. Current version is "
3487                    + curICUVersion + "; DB was built with " + dbICUVersion);
3488            return true;
3489        }
3490        return false;
3491    }
3492
3493    private void upgradeLocaleData(SQLiteDatabase db, boolean rebuildSqliteStats) {
3494        final LocaleSet locales = LocaleSet.getDefault();
3495        Log.i(TAG, "Upgrading locale data for " + locales
3496                + " (ICU v" + ICU.getIcuVersion() + ")");
3497        final long start = SystemClock.elapsedRealtime();
3498        initializeCache(db);
3499        rebuildLocaleData(db, locales, rebuildSqliteStats);
3500        Log.i(TAG, "Locale update completed in " + (SystemClock.elapsedRealtime() - start) + "ms");
3501    }
3502
3503    private void rebuildLocaleData(SQLiteDatabase db, LocaleSet locales, boolean rebuildSqliteStats) {
3504        db.execSQL("DROP INDEX raw_contact_sort_key1_index");
3505        db.execSQL("DROP INDEX raw_contact_sort_key2_index");
3506        db.execSQL("DROP INDEX IF EXISTS name_lookup_index");
3507
3508        loadNicknameLookupTable(db);
3509        insertNameLookup(db);
3510        rebuildSortKeys(db);
3511        createContactsIndexes(db, rebuildSqliteStats);
3512
3513        FastScrollingIndexCache.getInstance(mContext).invalidate();
3514        // Update the ICU version used to generate the locale derived data
3515        // so we can tell when we need to rebuild with new ICU versions.
3516        setProperty(db, DbProperties.ICU_VERSION, ICU.getIcuVersion());
3517        setProperty(db, DbProperties.LOCALE, locales.toString());
3518    }
3519
3520    /**
3521     * Regenerates all locale-sensitive data if needed:
3522     * nickname_lookup, name_lookup and sort keys. Invalidates the fast
3523     * scrolling index cache.
3524     */
3525    public void setLocale(LocaleSet locales) {
3526        if (!needsToUpdateLocaleData(locales)) {
3527            return;
3528        }
3529        Log.i(TAG, "Switching to locale " + locales
3530                + " (ICU v" + ICU.getIcuVersion() + ")");
3531
3532        final long start = SystemClock.elapsedRealtime();
3533        SQLiteDatabase db = getWritableDatabase();
3534        db.setLocale(locales.getPrimaryLocale());
3535        db.beginTransaction();
3536        try {
3537            rebuildLocaleData(db, locales, true);
3538            db.setTransactionSuccessful();
3539        } finally {
3540            db.endTransaction();
3541        }
3542
3543        Log.i(TAG, "Locale change completed in " + (SystemClock.elapsedRealtime() - start) + "ms");
3544    }
3545
3546    /**
3547     * Regenerates sort keys for all contacts.
3548     */
3549    private void rebuildSortKeys(SQLiteDatabase db) {
3550        Cursor cursor = db.query(Tables.RAW_CONTACTS, new String[] {RawContacts._ID},
3551                null, null, null, null, null);
3552        try {
3553            while (cursor.moveToNext()) {
3554                long rawContactId = cursor.getLong(0);
3555                updateRawContactDisplayName(db, rawContactId);
3556            }
3557        } finally {
3558            cursor.close();
3559        }
3560    }
3561
3562    private void insertNameLookup(SQLiteDatabase db) {
3563        db.execSQL("DELETE FROM " + Tables.NAME_LOOKUP);
3564
3565        SQLiteStatement nameLookupInsert = db.compileStatement(
3566                "INSERT OR IGNORE INTO " + Tables.NAME_LOOKUP + "("
3567                        + NameLookupColumns.RAW_CONTACT_ID + ","
3568                        + NameLookupColumns.DATA_ID + ","
3569                        + NameLookupColumns.NAME_TYPE + ","
3570                        + NameLookupColumns.NORMALIZED_NAME +
3571                ") VALUES (?,?,?,?)");
3572
3573        try {
3574            insertStructuredNameLookup(db, nameLookupInsert);
3575            insertEmailLookup(db, nameLookupInsert);
3576            insertNicknameLookup(db, nameLookupInsert);
3577        } finally {
3578            nameLookupInsert.close();
3579        }
3580    }
3581
3582    /**
3583     * Inserts name lookup rows for all structured names in the database.
3584     */
3585    private void insertStructuredNameLookup(SQLiteDatabase db, SQLiteStatement nameLookupInsert) {
3586        NameSplitter nameSplitter = createNameSplitter();
3587        NameLookupBuilder nameLookupBuilder = new StructuredNameLookupBuilder(nameSplitter,
3588                new CommonNicknameCache(db), nameLookupInsert);
3589        final long mimeTypeId = lookupMimeTypeId(db, StructuredName.CONTENT_ITEM_TYPE);
3590        Cursor cursor = db.query(StructuredNameQuery.TABLE, StructuredNameQuery.COLUMNS,
3591                StructuredNameQuery.SELECTION, new String[] {String.valueOf(mimeTypeId)},
3592                null, null, null);
3593        try {
3594            while (cursor.moveToNext()) {
3595                long dataId = cursor.getLong(StructuredNameQuery.ID);
3596                long rawContactId = cursor.getLong(StructuredNameQuery.RAW_CONTACT_ID);
3597                String name = cursor.getString(StructuredNameQuery.DISPLAY_NAME);
3598                int fullNameStyle = nameSplitter.guessFullNameStyle(name);
3599                fullNameStyle = nameSplitter.getAdjustedFullNameStyle(fullNameStyle);
3600                nameLookupBuilder.insertNameLookup(rawContactId, dataId, name, fullNameStyle);
3601            }
3602        } finally {
3603            cursor.close();
3604        }
3605    }
3606
3607    /**
3608     * Inserts name lookup rows for all email addresses in the database.
3609     */
3610    private void insertEmailLookup(SQLiteDatabase db, SQLiteStatement nameLookupInsert) {
3611        final long mimeTypeId = lookupMimeTypeId(db, Email.CONTENT_ITEM_TYPE);
3612        Cursor cursor = db.query(EmailQuery.TABLE, EmailQuery.COLUMNS,
3613                EmailQuery.SELECTION, new String[] {String.valueOf(mimeTypeId)},
3614                null, null, null);
3615        try {
3616            while (cursor.moveToNext()) {
3617                long dataId = cursor.getLong(EmailQuery.ID);
3618                long rawContactId = cursor.getLong(EmailQuery.RAW_CONTACT_ID);
3619                String address = cursor.getString(EmailQuery.ADDRESS);
3620                address = extractHandleFromEmailAddress(address);
3621                insertNameLookup(nameLookupInsert, rawContactId, dataId,
3622                        NameLookupType.EMAIL_BASED_NICKNAME, address);
3623            }
3624        } finally {
3625            cursor.close();
3626        }
3627    }
3628
3629    /**
3630     * Inserts name lookup rows for all nicknames in the database.
3631     */
3632    private void insertNicknameLookup(SQLiteDatabase db, SQLiteStatement nameLookupInsert) {
3633        final long mimeTypeId = lookupMimeTypeId(db, Nickname.CONTENT_ITEM_TYPE);
3634        Cursor cursor = db.query(NicknameQuery.TABLE, NicknameQuery.COLUMNS,
3635                NicknameQuery.SELECTION, new String[]{String.valueOf(mimeTypeId)},
3636                null, null, null);
3637        try {
3638            while (cursor.moveToNext()) {
3639                long dataId = cursor.getLong(NicknameQuery.ID);
3640                long rawContactId = cursor.getLong(NicknameQuery.RAW_CONTACT_ID);
3641                String nickname = cursor.getString(NicknameQuery.NAME);
3642                insertNameLookup(nameLookupInsert, rawContactId, dataId,
3643                        NameLookupType.NICKNAME, nickname);
3644            }
3645        } finally {
3646            cursor.close();
3647        }
3648    }
3649
3650    /**
3651     * Inserts a record in the {@link Tables#NAME_LOOKUP} table.
3652     */
3653    public void insertNameLookup(SQLiteStatement stmt, long rawContactId, long dataId,
3654            int lookupType, String name) {
3655        if (TextUtils.isEmpty(name)) {
3656            return;
3657        }
3658
3659        String normalized = NameNormalizer.normalize(name);
3660        if (TextUtils.isEmpty(normalized)) {
3661            return;
3662        }
3663
3664        insertNormalizedNameLookup(stmt, rawContactId, dataId, lookupType, normalized);
3665    }
3666
3667    private void insertNormalizedNameLookup(SQLiteStatement stmt, long rawContactId, long dataId,
3668            int lookupType, String normalizedName) {
3669        stmt.bindLong(1, rawContactId);
3670        stmt.bindLong(2, dataId);
3671        stmt.bindLong(3, lookupType);
3672        stmt.bindString(4, normalizedName);
3673        stmt.executeInsert();
3674    }
3675
3676    /**
3677     * Changing the VISIBLE bit from a field on both RawContacts and Contacts to a separate table.
3678     */
3679    private void upgradeToVersion401(SQLiteDatabase db) {
3680        db.execSQL("CREATE TABLE " + Tables.VISIBLE_CONTACTS + " (" +
3681                Contacts._ID + " INTEGER PRIMARY KEY" +
3682        ");");
3683        db.execSQL("INSERT INTO " + Tables.VISIBLE_CONTACTS +
3684                " SELECT " + Contacts._ID +
3685                " FROM " + Tables.CONTACTS +
3686                " WHERE " + Contacts.IN_VISIBLE_GROUP + "!=0");
3687        db.execSQL("DROP INDEX contacts_visible_index");
3688    }
3689
3690    /**
3691     * Introducing a new table: directories.
3692     */
3693    private void upgradeToVersion402(SQLiteDatabase db) {
3694        createDirectoriesTable(db);
3695    }
3696
3697    private void upgradeToVersion403(SQLiteDatabase db) {
3698        db.execSQL("DROP TABLE IF EXISTS directories;");
3699        createDirectoriesTable(db);
3700
3701        db.execSQL("ALTER TABLE raw_contacts"
3702                + " ADD raw_contact_is_read_only INTEGER NOT NULL DEFAULT 0;");
3703
3704        db.execSQL("ALTER TABLE data"
3705                + " ADD is_read_only INTEGER NOT NULL DEFAULT 0;");
3706    }
3707
3708    private void upgradeToVersion405(SQLiteDatabase db) {
3709        db.execSQL("DROP TABLE IF EXISTS phone_lookup;");
3710        // Private phone numbers table used for lookup
3711        db.execSQL("CREATE TABLE " + Tables.PHONE_LOOKUP + " (" +
3712                PhoneLookupColumns.DATA_ID
3713                + " INTEGER REFERENCES data(_id) NOT NULL," +
3714                PhoneLookupColumns.RAW_CONTACT_ID
3715                + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," +
3716                PhoneLookupColumns.NORMALIZED_NUMBER + " TEXT NOT NULL," +
3717                PhoneLookupColumns.MIN_MATCH + " TEXT NOT NULL" +
3718        ");");
3719
3720        db.execSQL("CREATE INDEX phone_lookup_index ON " + Tables.PHONE_LOOKUP + " (" +
3721                PhoneLookupColumns.NORMALIZED_NUMBER + "," +
3722                PhoneLookupColumns.RAW_CONTACT_ID + "," +
3723                PhoneLookupColumns.DATA_ID +
3724        ");");
3725
3726        db.execSQL("CREATE INDEX phone_lookup_min_match_index ON " + Tables.PHONE_LOOKUP + " (" +
3727                PhoneLookupColumns.MIN_MATCH + "," +
3728                PhoneLookupColumns.RAW_CONTACT_ID + "," +
3729                PhoneLookupColumns.DATA_ID +
3730        ");");
3731
3732        final long mimeTypeId = lookupMimeTypeId(db, Phone.CONTENT_ITEM_TYPE);
3733        if (mimeTypeId == -1) {
3734            return;
3735        }
3736
3737        Cursor cursor = db.rawQuery(
3738                    "SELECT _id, " + Phone.RAW_CONTACT_ID + ", " + Phone.NUMBER +
3739                    " FROM " + Tables.DATA +
3740                    " WHERE " + DataColumns.MIMETYPE_ID + "=" + mimeTypeId
3741                            + " AND " + Phone.NUMBER + " NOT NULL", null);
3742
3743        ContentValues phoneValues = new ContentValues();
3744        try {
3745            while (cursor.moveToNext()) {
3746                long dataID = cursor.getLong(0);
3747                long rawContactID = cursor.getLong(1);
3748                String number = cursor.getString(2);
3749                String normalizedNumber = PhoneNumberUtils.normalizeNumber(number);
3750                if (!TextUtils.isEmpty(normalizedNumber)) {
3751                    phoneValues.clear();
3752                    phoneValues.put(PhoneLookupColumns.RAW_CONTACT_ID, rawContactID);
3753                    phoneValues.put(PhoneLookupColumns.DATA_ID, dataID);
3754                    phoneValues.put(PhoneLookupColumns.NORMALIZED_NUMBER, normalizedNumber);
3755                    phoneValues.put(PhoneLookupColumns.MIN_MATCH,
3756                            PhoneNumberUtils.toCallerIDMinMatch(normalizedNumber));
3757                    db.insert(Tables.PHONE_LOOKUP, null, phoneValues);
3758                }
3759            }
3760        } finally {
3761            cursor.close();
3762        }
3763    }
3764
3765    private void upgradeToVersion406(SQLiteDatabase db) {
3766        db.execSQL("ALTER TABLE calls ADD countryiso TEXT;");
3767    }
3768
3769    private void upgradeToVersion409(SQLiteDatabase db) {
3770        db.execSQL("DROP TABLE IF EXISTS directories;");
3771        createDirectoriesTable(db);
3772    }
3773
3774    /**
3775     * Adding DEFAULT_DIRECTORY table.
3776     * DEFAULT_DIRECTORY should contain every contact which should be shown to users in default.
3777     * - if a contact doesn't belong to any account (local contact), it should be in
3778     *   default_directory
3779     * - if a contact belongs to an account that doesn't have a "default" group, it should be in
3780     *   default_directory
3781     * - if a contact belongs to an account that has a "default" group (like Google directory,
3782     *   which has "My contacts" group as default), it should be in default_directory.
3783     *
3784     * This logic assumes that accounts with the "default" group should have at least one
3785     * group with AUTO_ADD (implying it is the default group) flag in the groups table.
3786     */
3787    private void upgradeToVersion411(SQLiteDatabase db) {
3788        db.execSQL("DROP TABLE IF EXISTS " + Tables.DEFAULT_DIRECTORY);
3789        db.execSQL("CREATE TABLE default_directory (_id INTEGER PRIMARY KEY);");
3790
3791        // Process contacts without an account
3792        db.execSQL("INSERT OR IGNORE INTO default_directory " +
3793                " SELECT contact_id " +
3794                " FROM raw_contacts " +
3795                " WHERE raw_contacts.account_name IS NULL " +
3796                "   AND raw_contacts.account_type IS NULL ");
3797
3798        // Process accounts that don't have a default group (e.g. Exchange).
3799        db.execSQL("INSERT OR IGNORE INTO default_directory " +
3800                " SELECT contact_id " +
3801                " FROM raw_contacts " +
3802                " WHERE NOT EXISTS" +
3803                " (SELECT _id " +
3804                "  FROM groups " +
3805                "  WHERE raw_contacts.account_name = groups.account_name" +
3806                "    AND raw_contacts.account_type = groups.account_type" +
3807                "    AND groups.auto_add != 0)");
3808
3809        final long mimetype = lookupMimeTypeId(db, GroupMembership.CONTENT_ITEM_TYPE);
3810
3811        // Process accounts that do have a default group (e.g. Google)
3812        db.execSQL("INSERT OR IGNORE INTO default_directory " +
3813                " SELECT contact_id " +
3814                " FROM raw_contacts " +
3815                " JOIN data " +
3816                "   ON (raw_contacts._id=raw_contact_id)" +
3817                " WHERE mimetype_id=" + mimetype +
3818                " AND EXISTS" +
3819                " (SELECT _id" +
3820                "  FROM groups" +
3821                "  WHERE raw_contacts.account_name = groups.account_name" +
3822                "    AND raw_contacts.account_type = groups.account_type" +
3823                "    AND groups.auto_add != 0)");
3824    }
3825
3826    private void upgradeToVersion413(SQLiteDatabase db) {
3827        db.execSQL("DROP TABLE IF EXISTS directories;");
3828        createDirectoriesTable(db);
3829    }
3830
3831    private void upgradeToVersion415(SQLiteDatabase db) {
3832        db.execSQL(
3833                "ALTER TABLE " + Tables.GROUPS +
3834                " ADD " + Groups.GROUP_IS_READ_ONLY + " INTEGER NOT NULL DEFAULT 0");
3835        db.execSQL(
3836                "UPDATE " + Tables.GROUPS +
3837                "   SET " + Groups.GROUP_IS_READ_ONLY + "=1" +
3838                " WHERE " + Groups.SYSTEM_ID + " NOT NULL");
3839    }
3840
3841    private void upgradeToVersion416(SQLiteDatabase db) {
3842        db.execSQL("CREATE INDEX phone_lookup_data_id_min_match_index ON " + Tables.PHONE_LOOKUP +
3843                " (" + PhoneLookupColumns.DATA_ID + ", " + PhoneLookupColumns.MIN_MATCH + ");");
3844    }
3845
3846    private void upgradeToVersion501(SQLiteDatabase db) {
3847        // Remove organization rows from the name lookup, we now use search index for that
3848        db.execSQL("DELETE FROM name_lookup WHERE name_type=5");
3849    }
3850
3851    private void upgradeToVersion502(SQLiteDatabase db) {
3852        // Remove Chinese and Korean name lookup - this data is now in the search index
3853        db.execSQL("DELETE FROM name_lookup WHERE name_type IN (6, 7)");
3854    }
3855
3856    private void upgradeToVersion504(SQLiteDatabase db) {
3857        initializeCache(db);
3858
3859        // Find all names with prefixes and recreate display name
3860        Cursor cursor = db.rawQuery(
3861                "SELECT " + StructuredName.RAW_CONTACT_ID +
3862                " FROM " + Tables.DATA +
3863                " WHERE " + DataColumns.MIMETYPE_ID + "=?"
3864                        + " AND " + StructuredName.PREFIX + " NOT NULL",
3865                new String[] {String.valueOf(mMimeTypeIdStructuredName)});
3866
3867        try {
3868            while(cursor.moveToNext()) {
3869                long rawContactId = cursor.getLong(0);
3870                updateRawContactDisplayName(db, rawContactId);
3871            }
3872
3873        } finally {
3874            cursor.close();
3875        }
3876    }
3877
3878    private void upgradeToVersion601(SQLiteDatabase db) {
3879        db.execSQL("CREATE TABLE data_usage_stat(" +
3880                "stat_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
3881                "data_id INTEGER NOT NULL, " +
3882                "usage_type INTEGER NOT NULL DEFAULT 0, " +
3883                "times_used INTEGER NOT NULL DEFAULT 0, " +
3884                "last_time_used INTERGER NOT NULL DEFAULT 0, " +
3885                "FOREIGN KEY(data_id) REFERENCES data(_id));");
3886        db.execSQL("CREATE UNIQUE INDEX data_usage_stat_index ON " +
3887                "data_usage_stat (data_id, usage_type)");
3888    }
3889
3890    private void upgradeToVersion602(SQLiteDatabase db) {
3891        db.execSQL("ALTER TABLE calls ADD voicemail_uri TEXT;");
3892        db.execSQL("ALTER TABLE calls ADD _data TEXT;");
3893        db.execSQL("ALTER TABLE calls ADD has_content INTEGER;");
3894        db.execSQL("ALTER TABLE calls ADD mime_type TEXT;");
3895        db.execSQL("ALTER TABLE calls ADD source_data TEXT;");
3896        db.execSQL("ALTER TABLE calls ADD source_package TEXT;");
3897        db.execSQL("ALTER TABLE calls ADD state INTEGER;");
3898    }
3899
3900    private void upgradeToVersion604(SQLiteDatabase db) {
3901        db.execSQL("CREATE TABLE voicemail_status (" +
3902                "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
3903                "source_package TEXT UNIQUE NOT NULL," +
3904                "settings_uri TEXT," +
3905                "voicemail_access_uri TEXT," +
3906                "configuration_state INTEGER," +
3907                "data_channel_state INTEGER," +
3908                "notification_channel_state INTEGER" +
3909        ");");
3910    }
3911
3912    private void upgradeToVersion606(SQLiteDatabase db) {
3913        db.execSQL("DROP VIEW IF EXISTS view_contacts_restricted;");
3914        db.execSQL("DROP VIEW IF EXISTS view_data_restricted;");
3915        db.execSQL("DROP VIEW IF EXISTS view_raw_contacts_restricted;");
3916        db.execSQL("DROP VIEW IF EXISTS view_raw_entities_restricted;");
3917        db.execSQL("DROP VIEW IF EXISTS view_entities_restricted;");
3918        db.execSQL("DROP VIEW IF EXISTS view_data_usage_stat_restricted;");
3919        db.execSQL("DROP INDEX IF EXISTS contacts_restricted_index");
3920
3921        // We should remove the restricted columns here as well, but unfortunately SQLite doesn't
3922        // provide ALTER TABLE DROP COLUMN. As they have DEFAULT 0, we can keep but ignore them
3923    }
3924
3925    private void upgradeToVersion608(SQLiteDatabase db) {
3926        db.execSQL("ALTER TABLE contacts ADD photo_file_id INTEGER REFERENCES photo_files(_id);");
3927
3928        db.execSQL("CREATE TABLE photo_files(" +
3929                "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
3930                "height INTEGER NOT NULL, " +
3931                "width INTEGER NOT NULL, " +
3932                "filesize INTEGER NOT NULL);");
3933    }
3934
3935    private void upgradeToVersion610(SQLiteDatabase db) {
3936        db.execSQL("ALTER TABLE calls ADD is_read INTEGER;");
3937    }
3938
3939    private void upgradeToVersion611(SQLiteDatabase db) {
3940        db.execSQL("ALTER TABLE raw_contacts ADD data_set TEXT DEFAULT NULL;");
3941        db.execSQL("ALTER TABLE groups ADD data_set TEXT DEFAULT NULL;");
3942        db.execSQL("ALTER TABLE accounts ADD data_set TEXT DEFAULT NULL;");
3943
3944        db.execSQL("CREATE INDEX raw_contacts_source_id_data_set_index ON raw_contacts " +
3945                "(sourceid, account_type, account_name, data_set);");
3946
3947        db.execSQL("CREATE INDEX groups_source_id_data_set_index ON groups " +
3948                "(sourceid, account_type, account_name, data_set);");
3949    }
3950
3951    private void upgradeToVersion612(SQLiteDatabase db) {
3952        db.execSQL("ALTER TABLE calls ADD geocoded_location TEXT DEFAULT NULL;");
3953        // Old calls will not have a geocoded location; new calls will get it when inserted.
3954    }
3955
3956    private void upgradeToVersion613(SQLiteDatabase db) {
3957        // The stream item and stream item photos APIs were not in-use by anyone in the time
3958        // between their initial creation (in v609) and this update.  So we're just dropping
3959        // and re-creating them to get appropriate columns.  The delta is as follows:
3960        // - In stream_items, package_id was replaced by res_package.
3961        // - In stream_item_photos, picture was replaced by photo_file_id.
3962        // - Instead of resource IDs for icon and label, we use resource name strings now
3963        // - Added sync columns
3964        // - Removed action and action_uri
3965        // - Text and comments are now nullable
3966
3967        db.execSQL("DROP TABLE IF EXISTS stream_items");
3968        db.execSQL("DROP TABLE IF EXISTS stream_item_photos");
3969
3970        db.execSQL("CREATE TABLE stream_items(" +
3971                "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
3972                "raw_contact_id INTEGER NOT NULL, " +
3973                "res_package TEXT, " +
3974                "icon TEXT, " +
3975                "label TEXT, " +
3976                "text TEXT, " +
3977                "timestamp INTEGER NOT NULL, " +
3978                "comments TEXT, " +
3979                "stream_item_sync1 TEXT, " +
3980                "stream_item_sync2 TEXT, " +
3981                "stream_item_sync3 TEXT, " +
3982                "stream_item_sync4 TEXT, " +
3983                "FOREIGN KEY(raw_contact_id) REFERENCES raw_contacts(_id));");
3984
3985        db.execSQL("CREATE TABLE stream_item_photos(" +
3986                "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
3987                "stream_item_id INTEGER NOT NULL, " +
3988                "sort_index INTEGER, " +
3989                "photo_file_id INTEGER NOT NULL, " +
3990                "stream_item_photo_sync1 TEXT, " +
3991                "stream_item_photo_sync2 TEXT, " +
3992                "stream_item_photo_sync3 TEXT, " +
3993                "stream_item_photo_sync4 TEXT, " +
3994                "FOREIGN KEY(stream_item_id) REFERENCES stream_items(_id));");
3995    }
3996
3997    private void upgradeToVersion615(SQLiteDatabase db) {
3998        // Old calls will not have up to date values for these columns, they will be filled in
3999        // as needed.
4000        db.execSQL("ALTER TABLE calls ADD lookup_uri TEXT DEFAULT NULL;");
4001        db.execSQL("ALTER TABLE calls ADD matched_number TEXT DEFAULT NULL;");
4002        db.execSQL("ALTER TABLE calls ADD normalized_number TEXT DEFAULT NULL;");
4003        db.execSQL("ALTER TABLE calls ADD photo_id INTEGER NOT NULL DEFAULT 0;");
4004    }
4005
4006    private void upgradeToVersion618(SQLiteDatabase db) {
4007        // The Settings table needs a data_set column which technically should be part of the
4008        // primary key but can't be because it may be null.  Since SQLite doesn't support nuking
4009        // the primary key, we'll drop the old table, re-create it, and copy the settings back in.
4010        db.execSQL("CREATE TEMPORARY TABLE settings_backup(" +
4011                "account_name STRING NOT NULL," +
4012                "account_type STRING NOT NULL," +
4013                "ungrouped_visible INTEGER NOT NULL DEFAULT 0," +
4014                "should_sync INTEGER NOT NULL DEFAULT 1" +
4015        ");");
4016        db.execSQL("INSERT INTO settings_backup " +
4017                "SELECT account_name, account_type, ungrouped_visible, should_sync" +
4018                " FROM settings");
4019        db.execSQL("DROP TABLE settings");
4020        db.execSQL("CREATE TABLE settings (" +
4021                "account_name STRING NOT NULL," +
4022                "account_type STRING NOT NULL," +
4023                "data_set STRING," +
4024                "ungrouped_visible INTEGER NOT NULL DEFAULT 0," +
4025                "should_sync INTEGER NOT NULL DEFAULT 1" +
4026        ");");
4027        db.execSQL("INSERT INTO settings " +
4028                "SELECT account_name, account_type, NULL, ungrouped_visible, should_sync " +
4029                "FROM settings_backup");
4030        db.execSQL("DROP TABLE settings_backup");
4031    }
4032
4033    private void upgradeToVersion622(SQLiteDatabase db) {
4034        db.execSQL("ALTER TABLE calls ADD formatted_number TEXT DEFAULT NULL;");
4035    }
4036
4037    private void upgradeToVersion626(SQLiteDatabase db) {
4038        db.execSQL("DROP TABLE IF EXISTS accounts");
4039
4040        db.execSQL("CREATE TABLE accounts (" +
4041                "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
4042                "account_name TEXT, " +
4043                "account_type TEXT, " +
4044                "data_set TEXT" +
4045        ");");
4046
4047        // Add "account_id" column to groups and raw_contacts
4048        db.execSQL("ALTER TABLE raw_contacts ADD " +
4049                "account_id INTEGER REFERENCES accounts(_id)");
4050        db.execSQL("ALTER TABLE groups ADD " +
4051                "account_id INTEGER REFERENCES accounts(_id)");
4052
4053        // Update indexes.
4054        db.execSQL("DROP INDEX IF EXISTS raw_contacts_source_id_index");
4055        db.execSQL("DROP INDEX IF EXISTS raw_contacts_source_id_data_set_index");
4056        db.execSQL("DROP INDEX IF EXISTS groups_source_id_index");
4057        db.execSQL("DROP INDEX IF EXISTS groups_source_id_data_set_index");
4058
4059        db.execSQL("CREATE INDEX raw_contacts_source_id_account_id_index ON raw_contacts ("
4060                + "sourceid, account_id);");
4061        db.execSQL("CREATE INDEX groups_source_id_account_id_index ON groups ("
4062                + "sourceid, account_id);");
4063
4064        // Migrate account_name/account_type/data_set to accounts table
4065
4066        final Set<AccountWithDataSet> accountsWithDataSets = Sets.newHashSet();
4067        upgradeToVersion626_findAccountsWithDataSets(accountsWithDataSets, db, "raw_contacts");
4068        upgradeToVersion626_findAccountsWithDataSets(accountsWithDataSets, db, "groups");
4069
4070        for (AccountWithDataSet accountWithDataSet : accountsWithDataSets) {
4071            db.execSQL("INSERT INTO accounts (account_name,account_type,data_set)VALUES(?, ?, ?)",
4072                    new String[] {
4073                            accountWithDataSet.getAccountName(),
4074                            accountWithDataSet.getAccountType(),
4075                            accountWithDataSet.getDataSet()
4076                    });
4077        }
4078        upgradeToVersion626_fillAccountId(db, "raw_contacts");
4079        upgradeToVersion626_fillAccountId(db, "groups");
4080    }
4081
4082    private static void upgradeToVersion626_findAccountsWithDataSets(
4083            Set<AccountWithDataSet> result, SQLiteDatabase db, String table) {
4084        Cursor c = db.rawQuery(
4085                "SELECT DISTINCT account_name, account_type, data_set FROM " + table, null);
4086        try {
4087            while (c.moveToNext()) {
4088                result.add(AccountWithDataSet.get(c.getString(0), c.getString(1), c.getString(2)));
4089            }
4090        } finally {
4091            c.close();
4092        }
4093    }
4094
4095    private static void upgradeToVersion626_fillAccountId(SQLiteDatabase db, String table) {
4096        StringBuilder sb = new StringBuilder();
4097
4098        // Set account_id and null out account_name, account_type and data_set
4099
4100        sb.append("UPDATE " + table + " SET account_id = (SELECT _id FROM accounts WHERE ");
4101
4102        addJoinExpressionAllowingNull(sb, table + ".account_name", "accounts.account_name");
4103        sb.append("AND");
4104        addJoinExpressionAllowingNull(sb, table + ".account_type", "accounts.account_type");
4105        sb.append("AND");
4106        addJoinExpressionAllowingNull(sb, table + ".data_set", "accounts.data_set");
4107
4108        sb.append("), account_name = null, account_type = null, data_set = null");
4109        db.execSQL(sb.toString());
4110    }
4111
4112    private void upgradeToVersion701(SQLiteDatabase db) {
4113        db.execSQL("UPDATE raw_contacts SET last_time_contacted =" +
4114                " max(ifnull(last_time_contacted, 0), " +
4115                " ifnull((SELECT max(last_time_used) " +
4116                    " FROM data JOIN data_usage_stat ON (data._id = data_usage_stat.data_id)" +
4117                    " WHERE data.raw_contact_id = raw_contacts._id), 0))");
4118        // Replace 0 with null.  This isn't really necessary, but we do this anyway for consistency.
4119        db.execSQL("UPDATE raw_contacts SET last_time_contacted = null" +
4120                " where last_time_contacted = 0");
4121    }
4122
4123    /**
4124     * Pre-HC devices don't have correct "NORMALIZED_NUMBERS".  Clear them up.
4125     */
4126    private void upgradeToVersion702(SQLiteDatabase db) {
4127        // All the "correct" Phone.NORMALIZED_NUMBERS should begin with "+".  The upgraded data
4128        // don't.  Find all Phone.NORMALIZED_NUMBERS that don't begin with "+".
4129        final int count;
4130        final long[] dataIds;
4131        final long[] rawContactIds;
4132        final String[] phoneNumbers;
4133        final StringBuilder sbDataIds;
4134        final Cursor c = db.rawQuery(
4135                "SELECT _id, raw_contact_id, data1 FROM data " +
4136                " WHERE mimetype_id=" +
4137                    "(SELECT _id FROM mimetypes" +
4138                    " WHERE mimetype='vnd.android.cursor.item/phone_v2')" +
4139                " AND data4 not like '+%'", // "Not like" will exclude nulls too.
4140                null);
4141        try {
4142            count = c.getCount();
4143            if (count == 0) {
4144                return;
4145            }
4146            dataIds = new long[count];
4147            rawContactIds = new long[count];
4148            phoneNumbers = new String[count];
4149            sbDataIds = new StringBuilder();
4150
4151            c.moveToPosition(-1);
4152            while (c.moveToNext()) {
4153                final int i = c.getPosition();
4154                dataIds[i] = c.getLong(0);
4155                rawContactIds[i] = c.getLong(1);
4156                phoneNumbers[i] = c.getString(2);
4157
4158                if (sbDataIds.length() > 0) {
4159                    sbDataIds.append(",");
4160                }
4161                sbDataIds.append(dataIds[i]);
4162            }
4163        } finally {
4164            c.close();
4165        }
4166
4167        final String dataIdList = sbDataIds.toString();
4168
4169        // Then, update the Data and PhoneLookup tables.
4170
4171        // First, just null out all Phone.NORMALIZED_NUMBERS for those.
4172        db.execSQL("UPDATE data SET data4 = null" +
4173                " WHERE _id IN (" + dataIdList + ")");
4174
4175        // Then, re-create phone_lookup for them.
4176        db.execSQL("DELETE FROM phone_lookup" +
4177                " WHERE data_id IN (" + dataIdList + ")");
4178
4179        for (int i = 0; i < count; i++) {
4180            // Mimic how DataRowHandlerForPhoneNumber.insert() works when it can't normalize
4181            // numbers.
4182            final String phoneNumber = phoneNumbers[i];
4183            if (TextUtils.isEmpty(phoneNumber)) continue;
4184
4185            final String normalized = PhoneNumberUtils.normalizeNumber(phoneNumber);
4186            if (!TextUtils.isEmpty(normalized)) {
4187                db.execSQL("INSERT INTO phone_lookup" +
4188                        "(data_id, raw_contact_id, normalized_number, min_match)" +
4189                        " VALUES(?,?,?,?)",
4190                        new String[] {
4191                            String.valueOf(dataIds[i]),
4192                            String.valueOf(rawContactIds[i]),
4193                            normalized,
4194                            PhoneNumberUtils.toCallerIDMinMatch(normalized)});
4195            }
4196        }
4197    }
4198
4199    private void upgradeToVersion707(SQLiteDatabase db) {
4200        db.execSQL("ALTER TABLE raw_contacts ADD phonebook_label TEXT;");
4201        db.execSQL("ALTER TABLE raw_contacts ADD phonebook_bucket INTEGER;");
4202        db.execSQL("ALTER TABLE raw_contacts ADD phonebook_label_alt TEXT;");
4203        db.execSQL("ALTER TABLE raw_contacts ADD phonebook_bucket_alt INTEGER;");
4204    }
4205
4206    private void upgradeToVersion710(SQLiteDatabase db) {
4207
4208        // Adding timestamp to contacts table.
4209        db.execSQL("ALTER TABLE contacts"
4210                + " ADD contact_last_updated_timestamp INTEGER;");
4211
4212        db.execSQL("UPDATE contacts"
4213                + " SET contact_last_updated_timestamp"
4214                + " = " + System.currentTimeMillis());
4215
4216        db.execSQL("CREATE INDEX contacts_contact_last_updated_timestamp_index "
4217                + "ON contacts(contact_last_updated_timestamp)");
4218
4219        // New deleted contacts table.
4220        db.execSQL("CREATE TABLE deleted_contacts (" +
4221                "contact_id INTEGER PRIMARY KEY," +
4222                "contact_deleted_timestamp INTEGER NOT NULL default 0"
4223                + ");");
4224
4225        db.execSQL("CREATE INDEX deleted_contacts_contact_deleted_timestamp_index "
4226                + "ON deleted_contacts(contact_deleted_timestamp)");
4227    }
4228
4229    private void upgradeToVersion800(SQLiteDatabase db) {
4230        // Default Calls.PRESENTATION_ALLOWED=1
4231        db.execSQL("ALTER TABLE calls ADD presentation INTEGER NOT NULL DEFAULT 1;");
4232
4233        // Re-map CallerInfo.{..}_NUMBER strings to Calls.PRESENTATION_{..} ints
4234        //  PRIVATE_NUMBER="-2" -> PRESENTATION_RESTRICTED=2
4235        //  UNKNOWN_NUMBER="-1" -> PRESENTATION_UNKNOWN   =3
4236        // PAYPHONE_NUMBER="-3" -> PRESENTATION_PAYPHONE  =4
4237        db.execSQL("UPDATE calls SET presentation=2, number='' WHERE number='-2';");
4238        db.execSQL("UPDATE calls SET presentation=3, number='' WHERE number='-1';");
4239        db.execSQL("UPDATE calls SET presentation=4, number='' WHERE number='-3';");
4240    }
4241
4242    private void upgradeToVersion802(SQLiteDatabase db) {
4243        db.execSQL("ALTER TABLE contacts ADD pinned INTEGER NOT NULL DEFAULT " +
4244                ContactsContract.PinnedPositions.UNPINNED + ";");
4245        db.execSQL("ALTER TABLE raw_contacts ADD pinned INTEGER NOT NULL DEFAULT  " +
4246                ContactsContract.PinnedPositions.UNPINNED + ";");
4247    }
4248
4249    private void upgradeToVersion902(SQLiteDatabase db) {
4250        // adding account identifier to call log table
4251        db.execSQL("ALTER TABLE calls ADD subscription_component_name TEXT;");
4252        db.execSQL("ALTER TABLE calls ADD subscription_id TEXT;");
4253    }
4254
4255    /**
4256     * Searches for any calls in the call log with no normalized phone number and attempts to add
4257     * one if the number can be normalized.
4258     *
4259     * @param db The database.
4260     */
4261    private void upgradeToVersion903(SQLiteDatabase db) {
4262        // Find the calls in the call log with no normalized phone number.
4263        final Cursor c = db.rawQuery(
4264                "SELECT _id, number, countryiso FROM calls " +
4265                        " WHERE (normalized_number is null OR normalized_number = '') " +
4266                        " AND countryiso != '' AND countryiso is not null " +
4267                        " AND number != '' AND number is not null;",
4268                null
4269        );
4270
4271        try {
4272            if (c.getCount() == 0) {
4273                return;
4274            }
4275
4276            db.beginTransaction();
4277            try {
4278                c.moveToPosition(-1);
4279                while (c.moveToNext()) {
4280                    final long callId = c.getLong(0);
4281                    final String unNormalizedNumber = c.getString(1);
4282                    final String countryIso = c.getString(2);
4283
4284                    // Attempt to get normalized number.
4285                    String normalizedNumber = PhoneNumberUtils
4286                            .formatNumberToE164(unNormalizedNumber, countryIso);
4287
4288                    if (!TextUtils.isEmpty(normalizedNumber)) {
4289                        db.execSQL("UPDATE calls set normalized_number = ? " +
4290                                        "where _id = ?;",
4291                                new String[]{
4292                                        normalizedNumber,
4293                                        String.valueOf(callId),
4294                                }
4295                        );
4296                    }
4297                }
4298
4299                db.setTransactionSuccessful();
4300            } finally {
4301                db.endTransaction();
4302            }
4303        } finally {
4304            c.close();
4305        }
4306    }
4307
4308    /**
4309     * Updates the calls table in the database to include the call_duration and features columns.
4310     * @param db The database to update.
4311     */
4312    private void upgradeToVersion904(SQLiteDatabase db) {
4313        db.execSQL("ALTER TABLE calls ADD features INTEGER NOT NULL DEFAULT 0;");
4314        db.execSQL("ALTER TABLE calls ADD data_usage INTEGER;");
4315    }
4316
4317    /**
4318     * Adds the voicemail transcription to the Table.Calls
4319     */
4320    private void upgradeToVersion905(SQLiteDatabase db) {
4321        db.execSQL("ALTER TABLE calls ADD transcription TEXT;");
4322    }
4323
4324    /**
4325     * Upgrades the database with the new value for {@link PinnedPositions#UNPINNED}. In this
4326     * database version upgrade, the value is changed from 2147483647 (Integer.MAX_VALUE) to 0.
4327     *
4328     * The first pinned contact now starts from position 1.
4329     */
4330    @VisibleForTesting
4331    public void upgradeToVersion906(SQLiteDatabase db) {
4332        db.execSQL("UPDATE contacts SET pinned = pinned + 1"
4333                + " WHERE pinned >= 0 AND pinned < 2147483647;");
4334        db.execSQL("UPDATE raw_contacts SET pinned = pinned + 1"
4335                + " WHERE pinned >= 0 AND pinned < 2147483647;");
4336
4337        db.execSQL("UPDATE contacts SET pinned = 0"
4338                + " WHERE pinned = 2147483647;");
4339        db.execSQL("UPDATE raw_contacts SET pinned = 0"
4340                + " WHERE pinned = 2147483647;");
4341    }
4342
4343    private void upgradeToVersion908(SQLiteDatabase db) {
4344        db.execSQL("UPDATE contacts SET pinned = 0 WHERE pinned = 2147483647;");
4345        db.execSQL("UPDATE raw_contacts SET pinned = 0 WHERE pinned = 2147483647;");
4346    }
4347
4348    private void upgradeToVersion909(SQLiteDatabase db) {
4349        try {
4350            db.execSQL("ALTER TABLE calls ADD sub_id INTEGER DEFAULT -1;");
4351        } catch (SQLiteException e) {
4352            // The column already exists--copy over data
4353            db.execSQL("UPDATE calls SET subscription_component_name='com.android.phone/"
4354                    + "com.android.services.telephony.TelephonyConnectionService';");
4355            db.execSQL("UPDATE calls SET subscription_id=sub_id;");
4356        }
4357    }
4358
4359    /**
4360     * Delete any remaining rows in the calls table if the user is a profile of another user.
4361     * b/17096027
4362     */
4363    @VisibleForTesting
4364    public void upgradeToVersion910(SQLiteDatabase db) {
4365        final UserManager userManager = (UserManager) mContext.getSystemService(
4366                Context.USER_SERVICE);
4367        final UserInfo user = userManager.getUserInfo(userManager.getUserHandle());
4368        if (user.isManagedProfile()) {
4369            db.execSQL("DELETE FROM calls;");
4370        }
4371    }
4372
4373    /**
4374     * Add backup_id column to raw_contacts table and hash_id column to data table.
4375     */
4376    private void upgradeToVersion1000(SQLiteDatabase db) {
4377        db.execSQL("ALTER TABLE raw_contacts ADD backup_id TEXT;");
4378        db.execSQL("ALTER TABLE data ADD hash_id TEXT;");
4379        db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS raw_contacts_backup_id_account_id_index ON " +
4380                "raw_contacts (backup_id, account_id);");
4381        db.execSQL("CREATE INDEX IF NOT EXISTS data_hash_id_index ON data (hash_id);");
4382    }
4383
4384    /**
4385     * Add new metadata_sync table to cache the meta data on raw contacts level from server before
4386     * they are merged into other CP2 tables. The data column is the blob column containing all
4387     * the backed up metadata for this raw_contact. This table should only be used by metadata
4388     * sync adapter.
4389     */
4390    private void upgradeToVersion1001(SQLiteDatabase db) {
4391        db.execSQL("CREATE TABLE metadata_sync (" +
4392                "_id INTEGER PRIMARY KEY AUTOINCREMENT, raw_contact_backup_id TEXT NOT NULL, " +
4393                "account_id INTEGER NOT NULL, data TEXT, " +
4394                "FOREIGN KEY(account_id) REFERENCES accounts(_id));");
4395        db.execSQL("CREATE UNIQUE INDEX metadata_sync_index ON metadata_sync (" +
4396                "raw_contact_backup_id, account_id);");
4397    }
4398
4399    @VisibleForTesting
4400    public void upgradeToVersion1002(SQLiteDatabase db) {
4401        db.execSQL("DROP TABLE IF EXISTS pre_authorized_uris;");
4402        db.execSQL("CREATE TABLE pre_authorized_uris ("+
4403                "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
4404                "uri STRING NOT NULL, " +
4405                "expiration INTEGER NOT NULL DEFAULT 0);");
4406    }
4407
4408    public void upgradeToVersion1003(SQLiteDatabase db) {
4409        db.execSQL("ALTER TABLE calls ADD phone_account_address TEXT;");
4410
4411        // After version 1003, we are using the ICC ID as the phone-account ID. This code updates
4412        // any existing telephony connection-service calllog entries to the ICC ID from the
4413        // previously used subscription ID.
4414        // TODO: This is inconsistent, depending on the initialization state of SubscriptionManager.
4415        //       Sometimes it returns zero subscriptions. May want to move this upgrade to run after
4416        //       ON_BOOT_COMPLETE instead of PRE_BOOT_COMPLETE.
4417        SubscriptionManager sm = SubscriptionManager.from(mContext);
4418        if (sm != null) {
4419            Log.i(TAG, "count: " + sm.getAllSubscriptionInfoCount());
4420            for (SubscriptionInfo info : sm.getAllSubscriptionInfoList()) {
4421                String iccId = info.getIccId();
4422                int subId = info.getSubscriptionId();
4423                if (!TextUtils.isEmpty(iccId) &&
4424                        subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
4425                    StringBuilder sb = new StringBuilder();
4426                    sb.append("UPDATE calls SET subscription_id=");
4427                    DatabaseUtils.appendEscapedSQLString(sb, iccId);
4428                    sb.append(" WHERE subscription_id=");
4429                    sb.append(subId);
4430                    sb.append(" AND subscription_component_name='com.android.phone/"
4431                            + "com.android.services.telephony.TelephonyConnectionService';");
4432
4433                    db.execSQL(sb.toString());
4434                }
4435            }
4436        }
4437    }
4438
4439    /**
4440     * Add a "hidden" column for call log entries we want to hide after an upgrade until the user
4441     * adds the right phone account to the device.
4442     */
4443    public void upgradeToVersion1004(SQLiteDatabase db) {
4444        db.execSQL("ALTER TABLE calls ADD phone_account_hidden INTEGER NOT NULL DEFAULT 0;");
4445    }
4446
4447    public void upgradeToVersion1005(SQLiteDatabase db) {
4448        db.execSQL("ALTER TABLE calls ADD photo_uri TEXT;");
4449    }
4450
4451    /**
4452     * The try/catch pattern exists because some devices have the upgrade and some do not. This is
4453     * because the below updates were merged into version 1005 after some devices had already
4454     * upgraded to version 1005 and hence did not receive the below upgrades.
4455     */
4456    public void upgradeToVersion1007(SQLiteDatabase db) {
4457        try {
4458            // Add multi-sim fields
4459            db.execSQL("ALTER TABLE voicemail_status ADD phone_account_component_name TEXT;");
4460            db.execSQL("ALTER TABLE voicemail_status ADD phone_account_id TEXT;");
4461
4462            // For use by the sync adapter
4463            db.execSQL("ALTER TABLE calls ADD dirty INTEGER NOT NULL DEFAULT 0;");
4464            db.execSQL("ALTER TABLE calls ADD deleted INTEGER NOT NULL DEFAULT 0;");
4465        } catch (SQLiteException e) {
4466            // These columns already exist. Do nothing.
4467            // Log verbose because this should be the majority case.
4468            Log.v(TAG, "Version 1007: Columns already exist, skipping upgrade steps.");
4469        }
4470  }
4471
4472    /**
4473     * The metadata_sync APIs were not in-use by anyone in the time
4474     * between their initial creation (in v1001) and this update.  So we're just dropping
4475     * and re-creating it to get appropriate columns. The delta is as follows:
4476     * - drop foreign key constraint on account_id
4477     * - add deleted column
4478     */
4479    public void upgradeToVersion1008(SQLiteDatabase db) {
4480        db.execSQL("DROP TABLE IF EXISTS metadata_sync");
4481        db.execSQL("CREATE TABLE metadata_sync (" +
4482                "_id INTEGER PRIMARY KEY AUTOINCREMENT, raw_contact_backup_id TEXT NOT NULL, " +
4483                "account_id INTEGER NOT NULL, data TEXT, deleted INTEGER NOT NULL DEFAULT 0);");
4484        db.execSQL("CREATE UNIQUE INDEX metadata_sync_index ON metadata_sync (" +
4485                "raw_contact_backup_id, account_id);");
4486    }
4487
4488    public void upgradeToVersion1009(SQLiteDatabase db) {
4489        db.execSQL("ALTER TABLE data ADD carrier_presence INTEGER NOT NULL DEFAULT 0");
4490    }
4491
4492    public String extractHandleFromEmailAddress(String email) {
4493        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email);
4494        if (tokens.length == 0) {
4495            return null;
4496        }
4497
4498        String address = tokens[0].getAddress();
4499        int index = address.indexOf('@');
4500        if (index != -1) {
4501            return address.substring(0, index);
4502        }
4503        return null;
4504    }
4505
4506    public String extractAddressFromEmailAddress(String email) {
4507        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email);
4508        if (tokens.length == 0) {
4509            return null;
4510        }
4511        return tokens[0].getAddress().trim();
4512    }
4513
4514    private static long lookupMimeTypeId(SQLiteDatabase db, String mimeType) {
4515        try {
4516            return DatabaseUtils.longForQuery(db,
4517                    "SELECT " + MimetypesColumns._ID +
4518                    " FROM " + Tables.MIMETYPES +
4519                    " WHERE " + MimetypesColumns.MIMETYPE
4520                            + "='" + mimeType + "'", null);
4521        } catch (SQLiteDoneException e) {
4522            // No rows of this type in the database.
4523            return -1;
4524        }
4525    }
4526
4527    private void bindString(SQLiteStatement stmt, int index, String value) {
4528        if (value == null) {
4529            stmt.bindNull(index);
4530        } else {
4531            stmt.bindString(index, value);
4532        }
4533    }
4534
4535    private void bindLong(SQLiteStatement stmt, int index, Number value) {
4536        if (value == null) {
4537            stmt.bindNull(index);
4538        } else {
4539            stmt.bindLong(index, value.longValue());
4540        }
4541    }
4542
4543    /**
4544     * Add a string like "(((column1) = (column2)) OR ((column1) IS NULL AND (column2) IS NULL))"
4545     */
4546    private static StringBuilder addJoinExpressionAllowingNull(
4547            StringBuilder sb, String column1, String column2) {
4548
4549        sb.append("(((").append(column1).append(")=(").append(column2);
4550        sb.append("))OR((");
4551        sb.append(column1).append(") IS NULL AND (").append(column2).append(") IS NULL))");
4552        return sb;
4553    }
4554
4555    /**
4556     * Adds index stats into the SQLite database to force it to always use the lookup indexes.
4557     *
4558     * Note if you drop a table or an index, the corresponding row will be removed from this table.
4559     * Make sure to call this method after such operations.
4560     */
4561    private void updateSqliteStats(SQLiteDatabase db) {
4562        if (!mDatabaseOptimizationEnabled) {
4563            return;  // We don't use sqlite_stat1 during tests.
4564        }
4565
4566        // Specific stats strings are based on an actual large database after running ANALYZE
4567        // Important here are relative sizes. Raw-Contacts is slightly bigger than Contacts
4568        // Warning: Missing tables in here will make SQLite assume to contain 1000000 rows,
4569        // which can lead to catastrophic query plans for small tables
4570
4571        // What these numbers mean is described in this file.
4572        // http://www.sqlite.org/cgi/src/finfo?name=src/analyze.c
4573
4574        // Excerpt:
4575        /*
4576        ** Format of sqlite_stat1:
4577        **
4578        ** There is normally one row per index, with the index identified by the
4579        ** name in the idx column.  The tbl column is the name of the table to
4580        ** which the index belongs.  In each such row, the stat column will be
4581        ** a string consisting of a list of integers.  The first integer in this
4582        ** list is the number of rows in the index and in the table.  The second
4583        ** integer is the average number of rows in the index that have the same
4584        ** value in the first column of the index.  The third integer is the average
4585        ** number of rows in the index that have the same value for the first two
4586        ** columns.  The N-th integer (for N>1) is the average number of rows in
4587        ** the index which have the same value for the first N-1 columns.  For
4588        ** a K-column index, there will be K+1 integers in the stat column.  If
4589        ** the index is unique, then the last integer will be 1.
4590        **
4591        ** The list of integers in the stat column can optionally be followed
4592        ** by the keyword "unordered".  The "unordered" keyword, if it is present,
4593        ** must be separated from the last integer by a single space.  If the
4594        ** "unordered" keyword is present, then the query planner assumes that
4595        ** the index is unordered and will not use the index for a range query.
4596        **
4597        ** If the sqlite_stat1.idx column is NULL, then the sqlite_stat1.stat
4598        ** column contains a single integer which is the (estimated) number of
4599        ** rows in the table identified by sqlite_stat1.tbl.
4600        */
4601
4602        try {
4603            db.execSQL("DELETE FROM sqlite_stat1");
4604            updateIndexStats(db, Tables.CONTACTS,
4605                    "contacts_has_phone_index", "9000 500");
4606            updateIndexStats(db, Tables.CONTACTS,
4607                    "contacts_name_raw_contact_id_index", "9000 1");
4608            updateIndexStats(db, Tables.CONTACTS, MoreDatabaseUtils.buildIndexName(Tables.CONTACTS,
4609                    Contacts.CONTACT_LAST_UPDATED_TIMESTAMP), "9000 10");
4610
4611            updateIndexStats(db, Tables.RAW_CONTACTS,
4612                    "raw_contacts_contact_id_index", "10000 2");
4613            updateIndexStats(db, Tables.RAW_CONTACTS,
4614                    "raw_contact_sort_key2_index", "10000 2");
4615            updateIndexStats(db, Tables.RAW_CONTACTS,
4616                    "raw_contact_sort_key1_index", "10000 2");
4617            updateIndexStats(db, Tables.RAW_CONTACTS,
4618                    "raw_contacts_source_id_account_id_index", "10000 1 1");
4619
4620            updateIndexStats(db, Tables.NAME_LOOKUP,
4621                    "name_lookup_raw_contact_id_index", "35000 4");
4622            updateIndexStats(db, Tables.NAME_LOOKUP,
4623                    "name_lookup_index", "35000 2 2 2 1");
4624            updateIndexStats(db, Tables.NAME_LOOKUP,
4625                    "sqlite_autoindex_name_lookup_1", "35000 3 2 1");
4626
4627            updateIndexStats(db, Tables.PHONE_LOOKUP,
4628                    "phone_lookup_index", "3500 3 2 1");
4629            updateIndexStats(db, Tables.PHONE_LOOKUP,
4630                    "phone_lookup_min_match_index", "3500 3 2 2");
4631            updateIndexStats(db, Tables.PHONE_LOOKUP,
4632                    "phone_lookup_data_id_min_match_index", "3500 2 2");
4633
4634            updateIndexStats(db, Tables.DATA,
4635                    "data_mimetype_data1_index", "60000 5000 2");
4636            updateIndexStats(db, Tables.DATA,
4637                    "data_raw_contact_id", "60000 10");
4638
4639            updateIndexStats(db, Tables.GROUPS,
4640                    "groups_source_id_account_id_index", "50 2 2 1 1");
4641
4642            updateIndexStats(db, Tables.NICKNAME_LOOKUP,
4643                    "nickname_lookup_index", "500 2 1");
4644
4645            updateIndexStats(db, Tables.CALLS,
4646                    null, "250");
4647
4648            updateIndexStats(db, Tables.STATUS_UPDATES,
4649                    null, "100");
4650
4651            updateIndexStats(db, Tables.STREAM_ITEMS,
4652                    null, "500");
4653            updateIndexStats(db, Tables.STREAM_ITEM_PHOTOS,
4654                    null, "50");
4655
4656            updateIndexStats(db, Tables.VOICEMAIL_STATUS,
4657                    null, "5");
4658
4659            updateIndexStats(db, Tables.ACCOUNTS,
4660                    null, "3");
4661
4662            updateIndexStats(db, Tables.PRE_AUTHORIZED_URIS,
4663                    null, "1");
4664
4665            updateIndexStats(db, Tables.VISIBLE_CONTACTS,
4666                    null, "2000");
4667
4668            updateIndexStats(db, Tables.PHOTO_FILES,
4669                    null, "50");
4670
4671            updateIndexStats(db, Tables.DEFAULT_DIRECTORY,
4672                    null, "1500");
4673
4674            updateIndexStats(db, Tables.MIMETYPES,
4675                    "mime_type", "18 1");
4676
4677            updateIndexStats(db, Tables.DATA_USAGE_STAT,
4678                    "data_usage_stat_index", "20 2 1");
4679
4680            updateIndexStats(db, Tables.METADATA_SYNC,
4681                    "metadata_sync_index", "10000 1 1");
4682
4683            // Tiny tables
4684            updateIndexStats(db, Tables.AGGREGATION_EXCEPTIONS,
4685                    null, "10");
4686            updateIndexStats(db, Tables.SETTINGS,
4687                    null, "10");
4688            updateIndexStats(db, Tables.PACKAGES,
4689                    null, "0");
4690            updateIndexStats(db, Tables.DIRECTORIES,
4691                    null, "3");
4692            updateIndexStats(db, LegacyApiSupport.LegacyTables.SETTINGS,
4693                    null, "0");
4694            updateIndexStats(db, "android_metadata",
4695                    null, "1");
4696            updateIndexStats(db, "_sync_state",
4697                    "sqlite_autoindex__sync_state_1", "2 1 1");
4698            updateIndexStats(db, "_sync_state_metadata",
4699                    null, "1");
4700            updateIndexStats(db, "properties",
4701                    "sqlite_autoindex_properties_1", "4 1");
4702
4703            // Search index
4704            updateIndexStats(db, "search_index_docsize",
4705                    null, "9000");
4706            updateIndexStats(db, "search_index_content",
4707                    null, "9000");
4708            updateIndexStats(db, "search_index_stat",
4709                    null, "1");
4710            updateIndexStats(db, "search_index_segments",
4711                    null, "450");
4712            updateIndexStats(db, "search_index_segdir",
4713                    "sqlite_autoindex_search_index_segdir_1", "9 5 1");
4714
4715            // Force SQLite to reload sqlite_stat1.
4716            db.execSQL("ANALYZE sqlite_master;");
4717        } catch (SQLException e) {
4718            Log.e(TAG, "Could not update index stats", e);
4719        }
4720    }
4721
4722    /**
4723     * Stores statistics for a given index.
4724     *
4725     * @param stats has the following structure: the first index is the expected size of
4726     * the table.  The following integer(s) are the expected number of records selected with the
4727     * index.  There should be one integer per indexed column.
4728     */
4729    private void updateIndexStats(SQLiteDatabase db, String table, String index, String stats) {
4730        if (index == null) {
4731            db.execSQL("DELETE FROM sqlite_stat1 WHERE tbl=? AND idx IS NULL",
4732                    new String[] {table});
4733        } else {
4734            db.execSQL("DELETE FROM sqlite_stat1 WHERE tbl=? AND idx=?",
4735                    new String[] {table, index});
4736        }
4737        db.execSQL("INSERT INTO sqlite_stat1 (tbl,idx,stat) VALUES (?,?,?)",
4738                new String[] {table, index, stats});
4739    }
4740
4741    /**
4742     * Wipes all data except mime type and package lookup tables.
4743     */
4744    public void wipeData() {
4745        SQLiteDatabase db = getWritableDatabase();
4746
4747        db.execSQL("DELETE FROM " + Tables.ACCOUNTS + ";");
4748        db.execSQL("DELETE FROM " + Tables.CONTACTS + ";");
4749        db.execSQL("DELETE FROM " + Tables.RAW_CONTACTS + ";");
4750        db.execSQL("DELETE FROM " + Tables.STREAM_ITEMS + ";");
4751        db.execSQL("DELETE FROM " + Tables.STREAM_ITEM_PHOTOS + ";");
4752        db.execSQL("DELETE FROM " + Tables.PHOTO_FILES + ";");
4753        db.execSQL("DELETE FROM " + Tables.DATA + ";");
4754        db.execSQL("DELETE FROM " + Tables.PHONE_LOOKUP + ";");
4755        db.execSQL("DELETE FROM " + Tables.NAME_LOOKUP + ";");
4756        db.execSQL("DELETE FROM " + Tables.GROUPS + ";");
4757        db.execSQL("DELETE FROM " + Tables.AGGREGATION_EXCEPTIONS + ";");
4758        db.execSQL("DELETE FROM " + Tables.SETTINGS + ";");
4759        db.execSQL("DELETE FROM " + Tables.CALLS + ";");
4760        db.execSQL("DELETE FROM " + Tables.DIRECTORIES + ";");
4761        db.execSQL("DELETE FROM " + Tables.SEARCH_INDEX + ";");
4762        db.execSQL("DELETE FROM " + Tables.DELETED_CONTACTS + ";");
4763        db.execSQL("DELETE FROM " + Tables.MIMETYPES + ";");
4764        db.execSQL("DELETE FROM " + Tables.PACKAGES + ";");
4765
4766        initializeCache(db);
4767
4768        // Note: we are not removing reference data from Tables.NICKNAME_LOOKUP
4769    }
4770
4771    public NameSplitter createNameSplitter() {
4772        return createNameSplitter(Locale.getDefault());
4773    }
4774
4775    public NameSplitter createNameSplitter(Locale locale) {
4776        mNameSplitter = new NameSplitter(
4777                mContext.getString(com.android.internal.R.string.common_name_prefixes),
4778                mContext.getString(com.android.internal.R.string.common_last_name_prefixes),
4779                mContext.getString(com.android.internal.R.string.common_name_suffixes),
4780                mContext.getString(com.android.internal.R.string.common_name_conjunctions),
4781                locale);
4782        return mNameSplitter;
4783    }
4784
4785    /**
4786     * Return the {@link ApplicationInfo#uid} for the given package name.
4787     */
4788    public static int getUidForPackageName(PackageManager pm, String packageName) {
4789        try {
4790            ApplicationInfo clientInfo = pm.getApplicationInfo(packageName, 0 /* no flags */);
4791            return clientInfo.uid;
4792        } catch (NameNotFoundException e) {
4793            throw new RuntimeException(e);
4794        }
4795    }
4796
4797    /**
4798     * Internal method used by {@link #getPackageId} and {@link #getMimeTypeId}.
4799     *
4800     * Note in the contacts provider we avoid using synchronization because it could risk deadlocks.
4801     * So here, instead of using locks, we use ConcurrentHashMap + retry.
4802     *
4803     * Note we can't use a transaction here becuause this method is called from
4804     * onCommitTransaction() too, unfortunately.
4805     */
4806    private static long getIdCached(SQLiteDatabase db, ConcurrentHashMap<String, Long> cache,
4807            String querySql, String insertSql, String value) {
4808        // First, try the in-memory cache.
4809        if (cache.containsKey(value)) {
4810            return cache.get(value);
4811        }
4812
4813        // Then, try the database.
4814        long id = queryIdWithOneArg(db, querySql, value);
4815        if (id >= 0) {
4816            cache.put(value, id);
4817            return id;
4818        }
4819
4820        // Not found in the database.  Try inserting.
4821        id = insertWithOneArgAndReturnId(db, insertSql, value);
4822        if (id >= 0) {
4823            cache.put(value, id);
4824            return id;
4825        }
4826
4827        // Insert failed, which means a race.  Let's retry...
4828
4829        // We log here to detect an infinity loop (which shouldn't happen).
4830        // Conflicts should be pretty rare, so it shouldn't spam logcat.
4831        Log.i(TAG, "Cache conflict detected: value=" + value);
4832        try {
4833            Thread.sleep(1); // Just wait a little bit before retry.
4834        } catch (InterruptedException ignore) {
4835        }
4836        return getIdCached(db, cache, querySql, insertSql, value);
4837    }
4838
4839    @VisibleForTesting
4840    static long queryIdWithOneArg(SQLiteDatabase db, String sql, String sqlArgument) {
4841        final SQLiteStatement query = db.compileStatement(sql);
4842        try {
4843            DatabaseUtils.bindObjectToProgram(query, 1, sqlArgument);
4844            try {
4845                return query.simpleQueryForLong();
4846            } catch (SQLiteDoneException notFound) {
4847                return -1;
4848            }
4849        } finally {
4850            query.close();
4851        }
4852    }
4853
4854    @VisibleForTesting
4855    static long insertWithOneArgAndReturnId(SQLiteDatabase db, String sql, String sqlArgument) {
4856        final SQLiteStatement insert = db.compileStatement(sql);
4857        try {
4858            DatabaseUtils.bindObjectToProgram(insert, 1, sqlArgument);
4859            try {
4860                return insert.executeInsert();
4861            } catch (SQLiteConstraintException conflict) {
4862                return -1;
4863            }
4864        } finally {
4865            insert.close();
4866        }
4867    }
4868
4869    /**
4870     * Convert a package name into an integer, using {@link Tables#PACKAGES} for
4871     * lookups and possible allocation of new IDs as needed.
4872     */
4873    public long getPackageId(String packageName) {
4874        final String query =
4875                "SELECT " + PackagesColumns._ID +
4876                " FROM " + Tables.PACKAGES +
4877                " WHERE " + PackagesColumns.PACKAGE + "=?";
4878
4879        final String insert =
4880                "INSERT INTO " + Tables.PACKAGES + "("
4881                        + PackagesColumns.PACKAGE +
4882                ") VALUES (?)";
4883        return getIdCached(getWritableDatabase(), mPackageCache, query, insert, packageName);
4884    }
4885
4886    /**
4887     * Convert a mimetype into an integer, using {@link Tables#MIMETYPES} for
4888     * lookups and possible allocation of new IDs as needed.
4889     */
4890    public long getMimeTypeId(String mimetype) {
4891        return lookupMimeTypeId(mimetype, getWritableDatabase());
4892    }
4893
4894    private long lookupMimeTypeId(String mimetype, SQLiteDatabase db) {
4895        final String query =
4896                "SELECT " + MimetypesColumns._ID +
4897                " FROM " + Tables.MIMETYPES +
4898                " WHERE " + MimetypesColumns.MIMETYPE + "=?";
4899
4900        final String insert =
4901                "INSERT INTO " + Tables.MIMETYPES + "("
4902                        + MimetypesColumns.MIMETYPE +
4903                ") VALUES (?)";
4904
4905        return getIdCached(db, mMimetypeCache, query, insert, mimetype);
4906    }
4907
4908    public long getMimeTypeIdForStructuredName() {
4909        return mMimeTypeIdStructuredName;
4910    }
4911
4912    public long getMimeTypeIdForStructuredPostal() {
4913        return mMimeTypeIdStructuredPostal;
4914    }
4915
4916    public long getMimeTypeIdForOrganization() {
4917        return mMimeTypeIdOrganization;
4918    }
4919
4920    public long getMimeTypeIdForIm() {
4921        return mMimeTypeIdIm;
4922    }
4923
4924    public long getMimeTypeIdForEmail() {
4925        return mMimeTypeIdEmail;
4926    }
4927
4928    public long getMimeTypeIdForPhone() {
4929        return mMimeTypeIdPhone;
4930    }
4931
4932    public long getMimeTypeIdForSip() {
4933        return mMimeTypeIdSip;
4934    }
4935
4936    /**
4937     * Returns a {@link ContactsContract.DisplayNameSources} value based on {@param mimeTypeId}.
4938     * This does not return {@link ContactsContract.DisplayNameSources#STRUCTURED_PHONETIC_NAME}.
4939     * The calling client needs to inspect the structured name itself to distinguish between
4940     * {@link ContactsContract.DisplayNameSources#STRUCTURED_NAME} and
4941     * {@code STRUCTURED_PHONETIC_NAME}.
4942     */
4943    private int getDisplayNameSourceForMimeTypeId(int mimeTypeId) {
4944        if (mimeTypeId == mMimeTypeIdStructuredName) {
4945            return DisplayNameSources.STRUCTURED_NAME;
4946        }
4947        if (mimeTypeId == mMimeTypeIdEmail) {
4948            return DisplayNameSources.EMAIL;
4949        }
4950        if (mimeTypeId == mMimeTypeIdPhone) {
4951            return DisplayNameSources.PHONE;
4952        }
4953        if (mimeTypeId == mMimeTypeIdOrganization) {
4954            return DisplayNameSources.ORGANIZATION;
4955        }
4956        if (mimeTypeId == mMimeTypeIdNickname) {
4957            return DisplayNameSources.NICKNAME;
4958        }
4959        return DisplayNameSources.UNDEFINED;
4960    }
4961
4962    /**
4963     * Find the mimetype for the given {@link Data#_ID}.
4964     */
4965    public String getDataMimeType(long dataId) {
4966        if (mDataMimetypeQuery == null) {
4967            mDataMimetypeQuery = getWritableDatabase().compileStatement(
4968                    "SELECT " + MimetypesColumns.MIMETYPE +
4969                    " FROM " + Tables.DATA_JOIN_MIMETYPES +
4970                    " WHERE " + Tables.DATA + "." + Data._ID + "=?");
4971        }
4972        try {
4973            // Try database query to find mimetype
4974            DatabaseUtils.bindObjectToProgram(mDataMimetypeQuery, 1, dataId);
4975            String mimetype = mDataMimetypeQuery.simpleQueryForString();
4976            return mimetype;
4977        } catch (SQLiteDoneException e) {
4978            // No valid mapping found, so return null
4979            return null;
4980        }
4981    }
4982
4983    public void invalidateAllCache() {
4984        Log.w(TAG, "invalidateAllCache: [" + getClass().getSimpleName() + "]");
4985
4986        mMimetypeCache.clear();
4987        mPackageCache.clear();
4988    }
4989
4990    /**
4991     * Gets all accounts in the accounts table.
4992     */
4993    public Set<AccountWithDataSet> getAllAccountsWithDataSets() {
4994        final Set<AccountWithDataSet> result = Sets.newHashSet();
4995        Cursor c = getReadableDatabase().rawQuery(
4996                "SELECT DISTINCT " +  AccountsColumns._ID + "," + AccountsColumns.ACCOUNT_NAME +
4997                "," + AccountsColumns.ACCOUNT_TYPE + "," + AccountsColumns.DATA_SET +
4998                " FROM " + Tables.ACCOUNTS, null);
4999        try {
5000            while (c.moveToNext()) {
5001                result.add(AccountWithDataSet.get(c.getString(1), c.getString(2), c.getString(3)));
5002            }
5003        } finally {
5004            c.close();
5005        }
5006        return result;
5007    }
5008
5009    /**
5010     * @return ID of the specified account, or null if the account doesn't exist.
5011     */
5012    public Long getAccountIdOrNull(AccountWithDataSet accountWithDataSet) {
5013        if (accountWithDataSet == null) {
5014            accountWithDataSet = AccountWithDataSet.LOCAL;
5015        }
5016        final SQLiteStatement select = getWritableDatabase().compileStatement(
5017                "SELECT " + AccountsColumns._ID +
5018                " FROM " + Tables.ACCOUNTS +
5019                " WHERE " +
5020                "((?1 IS NULL AND " + AccountsColumns.ACCOUNT_NAME + " IS NULL) OR " +
5021                "(" + AccountsColumns.ACCOUNT_NAME + "=?1)) AND " +
5022                "((?2 IS NULL AND " + AccountsColumns.ACCOUNT_TYPE + " IS NULL) OR " +
5023                "(" + AccountsColumns.ACCOUNT_TYPE + "=?2)) AND " +
5024                "((?3 IS NULL AND " + AccountsColumns.DATA_SET + " IS NULL) OR " +
5025                "(" + AccountsColumns.DATA_SET + "=?3))");
5026        try {
5027            DatabaseUtils.bindObjectToProgram(select, 1, accountWithDataSet.getAccountName());
5028            DatabaseUtils.bindObjectToProgram(select, 2, accountWithDataSet.getAccountType());
5029            DatabaseUtils.bindObjectToProgram(select, 3, accountWithDataSet.getDataSet());
5030            try {
5031                return select.simpleQueryForLong();
5032            } catch (SQLiteDoneException notFound) {
5033                return null;
5034            }
5035        } finally {
5036            select.close();
5037        }
5038    }
5039
5040    /**
5041     * @return ID of the specified account.  This method will create a record in the accounts table
5042     *     if the account doesn't exist in the accounts table.
5043     *
5044     * This must be used in a transaction, so there's no need for synchronization.
5045     */
5046    public long getOrCreateAccountIdInTransaction(AccountWithDataSet accountWithDataSet) {
5047        if (accountWithDataSet == null) {
5048            accountWithDataSet = AccountWithDataSet.LOCAL;
5049        }
5050        Long id = getAccountIdOrNull(accountWithDataSet);
5051        if (id != null) {
5052            return id;
5053        }
5054        final SQLiteStatement insert = getWritableDatabase().compileStatement(
5055                "INSERT INTO " + Tables.ACCOUNTS +
5056                " (" + AccountsColumns.ACCOUNT_NAME + ", " +
5057                AccountsColumns.ACCOUNT_TYPE + ", " +
5058                AccountsColumns.DATA_SET + ") VALUES (?, ?, ?)");
5059        try {
5060            DatabaseUtils.bindObjectToProgram(insert, 1, accountWithDataSet.getAccountName());
5061            DatabaseUtils.bindObjectToProgram(insert, 2, accountWithDataSet.getAccountType());
5062            DatabaseUtils.bindObjectToProgram(insert, 3, accountWithDataSet.getDataSet());
5063            id = insert.executeInsert();
5064        } finally {
5065            insert.close();
5066        }
5067
5068        return id;
5069    }
5070
5071    /**
5072     * Update {@link Contacts#IN_VISIBLE_GROUP} for all contacts.
5073     */
5074    public void updateAllVisible() {
5075        updateCustomContactVisibility(getWritableDatabase(), -1);
5076    }
5077
5078    /**
5079     * Updates contact visibility and return true iff the visibility was actually changed.
5080     */
5081    public boolean updateContactVisibleOnlyIfChanged(TransactionContext txContext, long contactId) {
5082        return updateContactVisible(txContext, contactId, true);
5083    }
5084
5085    /**
5086     * Update {@link Contacts#IN_VISIBLE_GROUP} and
5087     * {@link Tables#DEFAULT_DIRECTORY} for a specific contact.
5088     */
5089    public void updateContactVisible(TransactionContext txContext, long contactId) {
5090        updateContactVisible(txContext, contactId, false);
5091    }
5092
5093    public boolean updateContactVisible(
5094            TransactionContext txContext, long contactId, boolean onlyIfChanged) {
5095        SQLiteDatabase db = getWritableDatabase();
5096        updateCustomContactVisibility(db, contactId);
5097
5098        String contactIdAsString = String.valueOf(contactId);
5099        long mimetype = getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
5100
5101        // The contact will be included in the default directory if contains a raw contact that is
5102        // in any group or in an account that does not have any AUTO_ADD groups.
5103        boolean newVisibility = DatabaseUtils.longForQuery(db,
5104                "SELECT EXISTS (" +
5105                    "SELECT " + RawContacts.CONTACT_ID +
5106                    " FROM " + Tables.RAW_CONTACTS +
5107                    " JOIN " + Tables.DATA +
5108                    "   ON (" + RawContactsColumns.CONCRETE_ID + "="
5109                            + Data.RAW_CONTACT_ID + ")" +
5110                    " WHERE " + RawContacts.CONTACT_ID + "=?1" +
5111                    "   AND " + DataColumns.MIMETYPE_ID + "=?2" +
5112                ") OR EXISTS (" +
5113                    "SELECT " + RawContacts._ID +
5114                    " FROM " + Tables.RAW_CONTACTS +
5115                    " WHERE " + RawContacts.CONTACT_ID + "=?1" +
5116                    "   AND NOT EXISTS" +
5117                        " (SELECT " + Groups._ID +
5118                        "  FROM " + Tables.GROUPS +
5119                        "  WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID + " = "
5120                                + GroupsColumns.CONCRETE_ACCOUNT_ID +
5121                        "  AND " + Groups.AUTO_ADD + " != 0" +
5122                        ")" +
5123                ") OR EXISTS (" +
5124                    "SELECT " + RawContacts._ID +
5125                    " FROM " + Tables.RAW_CONTACTS +
5126                    " WHERE " + RawContacts.CONTACT_ID + "=?1" +
5127                    "   AND " + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" +
5128                        Clauses.LOCAL_ACCOUNT_ID +
5129                ")",
5130                new String[] {
5131                    contactIdAsString,
5132                    String.valueOf(mimetype)
5133                }) != 0;
5134
5135        if (onlyIfChanged) {
5136            boolean oldVisibility = isContactInDefaultDirectory(db, contactId);
5137            if (oldVisibility == newVisibility) {
5138                return false;
5139            }
5140        }
5141
5142        if (newVisibility) {
5143            db.execSQL("INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY + " VALUES(?)",
5144                    new String[] {contactIdAsString});
5145            txContext.invalidateSearchIndexForContact(contactId);
5146        } else {
5147            db.execSQL("DELETE FROM " + Tables.DEFAULT_DIRECTORY +
5148                        " WHERE " + Contacts._ID + "=?",
5149                    new String[] {contactIdAsString});
5150            db.execSQL("DELETE FROM " + Tables.SEARCH_INDEX +
5151                        " WHERE " + SearchIndexColumns.CONTACT_ID + "=CAST(? AS int)",
5152                    new String[] {contactIdAsString});
5153        }
5154        return true;
5155    }
5156
5157    public boolean isContactInDefaultDirectory(SQLiteDatabase db, long contactId) {
5158        if (mContactInDefaultDirectoryQuery == null) {
5159            mContactInDefaultDirectoryQuery = db.compileStatement(
5160                    "SELECT EXISTS (" +
5161                            "SELECT 1 FROM " + Tables.DEFAULT_DIRECTORY +
5162                            " WHERE " + Contacts._ID + "=?)");
5163        }
5164        mContactInDefaultDirectoryQuery.bindLong(1, contactId);
5165        return mContactInDefaultDirectoryQuery.simpleQueryForLong() != 0;
5166    }
5167
5168    /**
5169     * Update the visible_contacts table according to the current visibility of contacts, which
5170     * is defined by {@link Clauses#CONTACT_IS_VISIBLE}.
5171     *
5172     * If {@code optionalContactId} is non-negative, it'll update only for the specified contact.
5173     */
5174    private void updateCustomContactVisibility(SQLiteDatabase db, long optionalContactId) {
5175        final long groupMembershipMimetypeId = getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
5176        String[] selectionArgs = new String[] {String.valueOf(groupMembershipMimetypeId)};
5177
5178        final String contactIdSelect = (optionalContactId < 0) ? "" :
5179                (Contacts._ID + "=" + optionalContactId + " AND ");
5180
5181        // First delete what needs to be deleted, then insert what needs to be added.
5182        // Since flash writes are very expensive, this approach is much better than
5183        // delete-all-insert-all.
5184        db.execSQL(
5185                "DELETE FROM " + Tables.VISIBLE_CONTACTS +
5186                " WHERE " + Contacts._ID + " IN" +
5187                    "(SELECT " + Contacts._ID +
5188                    " FROM " + Tables.CONTACTS +
5189                    " WHERE " + contactIdSelect + "(" + Clauses.CONTACT_IS_VISIBLE + ")=0) ",
5190                selectionArgs);
5191
5192        db.execSQL(
5193                "INSERT INTO " + Tables.VISIBLE_CONTACTS +
5194                " SELECT " + Contacts._ID +
5195                " FROM " + Tables.CONTACTS +
5196                " WHERE " +
5197                    contactIdSelect +
5198                    Contacts._ID + " NOT IN " + Tables.VISIBLE_CONTACTS +
5199                    " AND (" + Clauses.CONTACT_IS_VISIBLE + ")=1 ",
5200                selectionArgs);
5201    }
5202
5203    /**
5204     * Returns contact ID for the given contact or zero if it is NULL.
5205     */
5206    public long getContactId(long rawContactId) {
5207        if (mContactIdQuery == null) {
5208            mContactIdQuery = getWritableDatabase().compileStatement(
5209                    "SELECT " + RawContacts.CONTACT_ID +
5210                    " FROM " + Tables.RAW_CONTACTS +
5211                    " WHERE " + RawContacts._ID + "=?");
5212        }
5213        try {
5214            DatabaseUtils.bindObjectToProgram(mContactIdQuery, 1, rawContactId);
5215            return mContactIdQuery.simpleQueryForLong();
5216        } catch (SQLiteDoneException e) {
5217            return 0;  // No valid mapping found.
5218        }
5219    }
5220
5221    public int getAggregationMode(long rawContactId) {
5222        if (mAggregationModeQuery == null) {
5223            mAggregationModeQuery = getWritableDatabase().compileStatement(
5224                    "SELECT " + RawContacts.AGGREGATION_MODE +
5225                    " FROM " + Tables.RAW_CONTACTS +
5226                    " WHERE " + RawContacts._ID + "=?");
5227        }
5228        try {
5229            DatabaseUtils.bindObjectToProgram(mAggregationModeQuery, 1, rawContactId);
5230            return (int)mAggregationModeQuery.simpleQueryForLong();
5231        } catch (SQLiteDoneException e) {
5232            return RawContacts.AGGREGATION_MODE_DISABLED;  // No valid row found.
5233        }
5234    }
5235
5236    public void buildPhoneLookupAndContactQuery(
5237            SQLiteQueryBuilder qb, String normalizedNumber, String numberE164) {
5238
5239        String minMatch = PhoneNumberUtils.toCallerIDMinMatch(normalizedNumber);
5240        StringBuilder sb = new StringBuilder();
5241        appendPhoneLookupTables(sb, minMatch, true);
5242        qb.setTables(sb.toString());
5243
5244        sb = new StringBuilder();
5245        appendPhoneLookupSelection(sb, normalizedNumber, numberE164);
5246        qb.appendWhere(sb.toString());
5247    }
5248
5249    /**
5250     * Phone lookup method that uses the custom SQLite function phone_number_compare_loose
5251     * that serves as a fallback in case the regular lookup does not return any results.
5252     * @param qb The query builder.
5253     * @param number The phone number to search for.
5254     */
5255    public void buildFallbackPhoneLookupAndContactQuery(SQLiteQueryBuilder qb, String number) {
5256        final String minMatch = PhoneNumberUtils.toCallerIDMinMatch(number);
5257        final StringBuilder sb = new StringBuilder();
5258        // Append lookup tables.
5259        sb.append(Tables.RAW_CONTACTS);
5260        sb.append(" JOIN " + Views.CONTACTS + " as contacts_view"
5261                + " ON (contacts_view._id = " + Tables.RAW_CONTACTS
5262                + "." + RawContacts.CONTACT_ID + ")" +
5263                " JOIN (SELECT " + PhoneLookupColumns.DATA_ID + "," +
5264                PhoneLookupColumns.NORMALIZED_NUMBER + " FROM "+ Tables.PHONE_LOOKUP + " "
5265                + "WHERE (" + Tables.PHONE_LOOKUP + "." + PhoneLookupColumns.MIN_MATCH + " = '");
5266        sb.append(minMatch);
5267        sb.append("')) AS lookup " +
5268                "ON lookup." + PhoneLookupColumns.DATA_ID + "=" + Tables.DATA + "." + Data._ID
5269                + " JOIN " + Tables.DATA + " "
5270                + "ON " + Tables.DATA + "." + Data.RAW_CONTACT_ID + "=" + Tables.RAW_CONTACTS + "."
5271                + RawContacts._ID);
5272
5273        qb.setTables(sb.toString());
5274
5275        sb.setLength(0);
5276        sb.append("PHONE_NUMBERS_EQUAL(" + Tables.DATA + "." + Phone.NUMBER + ", ");
5277        DatabaseUtils.appendEscapedSQLString(sb, number);
5278        sb.append(mUseStrictPhoneNumberComparison ? ", 1)" : ", 0)");
5279        qb.appendWhere(sb.toString());
5280    }
5281
5282    /**
5283     * Adds query for selecting the contact with the given {@code sipAddress} to the given
5284     * {@link StringBuilder}.
5285     *
5286     * @return the query arguments to be passed in with the query
5287     */
5288    public String[] buildSipContactQuery(StringBuilder sb, String sipAddress) {
5289        sb.append("upper(");
5290        sb.append(Data.DATA1);
5291        sb.append(")=upper(?) AND ");
5292        sb.append(DataColumns.MIMETYPE_ID);
5293        sb.append("=");
5294        sb.append(Long.toString(getMimeTypeIdForSip()));
5295        // Return the arguments to be passed to the query.
5296        return new String[] {sipAddress};
5297    }
5298
5299    public String buildPhoneLookupAsNestedQuery(String number) {
5300        StringBuilder sb = new StringBuilder();
5301        final String minMatch = PhoneNumberUtils.toCallerIDMinMatch(number);
5302        sb.append("(SELECT DISTINCT raw_contact_id" + " FROM ");
5303        appendPhoneLookupTables(sb, minMatch, false);
5304        sb.append(" WHERE ");
5305        appendPhoneLookupSelection(sb, number, null);
5306        sb.append(")");
5307        return sb.toString();
5308    }
5309
5310    private void appendPhoneLookupTables(
5311            StringBuilder sb, final String minMatch, boolean joinContacts) {
5312
5313        sb.append(Tables.RAW_CONTACTS);
5314        if (joinContacts) {
5315            sb.append(" JOIN " + Views.CONTACTS + " contacts_view"
5316                    + " ON (contacts_view._id = raw_contacts.contact_id)");
5317        }
5318        sb.append(", (SELECT data_id, normalized_number, length(normalized_number) as len "
5319                + " FROM phone_lookup " + " WHERE (" + Tables.PHONE_LOOKUP + "."
5320                + PhoneLookupColumns.MIN_MATCH + " = '");
5321        sb.append(minMatch);
5322        sb.append("')) AS lookup, " + Tables.DATA);
5323    }
5324
5325    private void appendPhoneLookupSelection(StringBuilder sb, String number, String numberE164) {
5326        sb.append("lookup.data_id=data._id AND data.raw_contact_id=raw_contacts._id");
5327        boolean hasNumberE164 = !TextUtils.isEmpty(numberE164);
5328        boolean hasNumber = !TextUtils.isEmpty(number);
5329        if (hasNumberE164 || hasNumber) {
5330            sb.append(" AND ( ");
5331            if (hasNumberE164) {
5332                sb.append(" lookup.normalized_number = ");
5333                DatabaseUtils.appendEscapedSQLString(sb, numberE164);
5334            }
5335            if (hasNumberE164 && hasNumber) {
5336                sb.append(" OR ");
5337            }
5338            if (hasNumber) {
5339                // Skip the suffix match entirely if we are using strict number comparison.
5340                if (!mUseStrictPhoneNumberComparison) {
5341                    int numberLen = number.length();
5342                    sb.append(" lookup.len <= ");
5343                    sb.append(numberLen);
5344                    sb.append(" AND substr(");
5345                    DatabaseUtils.appendEscapedSQLString(sb, number);
5346                    sb.append(',');
5347                    sb.append(numberLen);
5348                    sb.append(" - lookup.len + 1) = lookup.normalized_number");
5349
5350                    // Some countries (e.g. Brazil) can have incoming calls which contain only
5351                    // the local number (no country calling code and no area code).  This case
5352                    // is handled below, see b/5197612.
5353                    // This also handles a Gingerbread -> ICS upgrade issue; see b/5638376.
5354                    sb.append(" OR (");
5355                    sb.append(" lookup.len > ");
5356                    sb.append(numberLen);
5357                    sb.append(" AND substr(lookup.normalized_number,");
5358                    sb.append("lookup.len + 1 - ");
5359                    sb.append(numberLen);
5360                    sb.append(") = ");
5361                    DatabaseUtils.appendEscapedSQLString(sb, number);
5362                    sb.append(")");
5363                } else {
5364                    sb.append("0");
5365                }
5366            }
5367            sb.append(')');
5368        }
5369    }
5370
5371    public String getUseStrictPhoneNumberComparisonParameter() {
5372        return mUseStrictPhoneNumberComparison ? "1" : "0";
5373    }
5374
5375    /**
5376     * Loads common nickname mappings into the database.
5377     */
5378    private void loadNicknameLookupTable(SQLiteDatabase db) {
5379        db.execSQL("DELETE FROM " + Tables.NICKNAME_LOOKUP);
5380
5381        String[] strings = mContext.getResources().getStringArray(
5382                com.android.internal.R.array.common_nicknames);
5383        if (strings == null || strings.length == 0) {
5384            return;
5385        }
5386
5387        SQLiteStatement nicknameLookupInsert = db.compileStatement("INSERT INTO "
5388                + Tables.NICKNAME_LOOKUP + "(" + NicknameLookupColumns.NAME + ","
5389                + NicknameLookupColumns.CLUSTER + ") VALUES (?,?)");
5390
5391        try {
5392            for (int clusterId = 0; clusterId < strings.length; clusterId++) {
5393                String[] names = strings[clusterId].split(",");
5394                for (String name : names) {
5395                    String normalizedName = NameNormalizer.normalize(name);
5396                    try {
5397                        DatabaseUtils.bindObjectToProgram(nicknameLookupInsert, 1, normalizedName);
5398                        DatabaseUtils.bindObjectToProgram(
5399                                nicknameLookupInsert, 2, String.valueOf(clusterId));
5400                        nicknameLookupInsert.executeInsert();
5401                    } catch (SQLiteException e) {
5402                        // Print the exception and keep going (this is not a fatal error).
5403                        Log.e(TAG, "Cannot insert nickname: " + name, e);
5404                    }
5405                }
5406            }
5407        } finally {
5408            nicknameLookupInsert.close();
5409        }
5410    }
5411
5412    public static void copyStringValue(
5413            ContentValues toValues, String toKey, ContentValues fromValues, String fromKey) {
5414
5415        if (fromValues.containsKey(fromKey)) {
5416            toValues.put(toKey, fromValues.getAsString(fromKey));
5417        }
5418    }
5419
5420    public static void copyLongValue(
5421            ContentValues toValues, String toKey, ContentValues fromValues, String fromKey) {
5422
5423        if (fromValues.containsKey(fromKey)) {
5424            long longValue;
5425            Object value = fromValues.get(fromKey);
5426            if (value instanceof Boolean) {
5427                longValue = (Boolean) value ? 1 : 0;
5428            } else if (value instanceof String) {
5429                longValue = Long.parseLong((String)value);
5430            } else {
5431                longValue = ((Number)value).longValue();
5432            }
5433            toValues.put(toKey, longValue);
5434        }
5435    }
5436
5437    public SyncStateContentProviderHelper getSyncState() {
5438        return mSyncState;
5439    }
5440
5441    /**
5442     * Returns the value from the {@link Tables#PROPERTIES} table.
5443     */
5444    public String getProperty(String key, String defaultValue) {
5445        return getProperty(getReadableDatabase(), key, defaultValue);
5446    }
5447
5448    public String getProperty(SQLiteDatabase db, String key, String defaultValue) {
5449        Cursor cursor = db.query(Tables.PROPERTIES,
5450                new String[] {PropertiesColumns.PROPERTY_VALUE},
5451                PropertiesColumns.PROPERTY_KEY + "=?",
5452                new String[] {key}, null, null, null);
5453        String value = null;
5454        try {
5455            if (cursor.moveToFirst()) {
5456                value = cursor.getString(0);
5457            }
5458        } finally {
5459            cursor.close();
5460        }
5461
5462        return value != null ? value : defaultValue;
5463    }
5464
5465    /**
5466     * Stores a key-value pair in the {@link Tables#PROPERTIES} table.
5467     */
5468    public void setProperty(String key, String value) {
5469        setProperty(getWritableDatabase(), key, value);
5470    }
5471
5472    private void setProperty(SQLiteDatabase db, String key, String value) {
5473        ContentValues values = new ContentValues();
5474        values.put(PropertiesColumns.PROPERTY_KEY, key);
5475        values.put(PropertiesColumns.PROPERTY_VALUE, value);
5476        db.replace(Tables.PROPERTIES, null, values);
5477    }
5478
5479    /**
5480     * Test if the given column appears in the given projection.
5481     */
5482    public static boolean isInProjection(String[] projection, String column) {
5483        if (projection == null) {
5484            return true;  // Null means "all columns".  We can't really tell if it's in there.
5485        }
5486        for (String test : projection) {
5487            if (column.equals(test)) {
5488                return true;
5489            }
5490        }
5491        return false;
5492    }
5493
5494    /**
5495     * Tests if any of the columns appear in the given projection.
5496     */
5497    public static boolean isInProjection(String[] projection, String... columns) {
5498        if (projection == null) {
5499            return true;
5500        }
5501
5502        // Optimized for a single-column test
5503        if (columns.length == 1) {
5504            return isInProjection(projection, columns[0]);
5505        }
5506        for (String test : projection) {
5507            for (String column : columns) {
5508                if (column.equals(test)) {
5509                    return true;
5510                }
5511            }
5512        }
5513        return false;
5514    }
5515
5516    /**
5517     * Returns a detailed exception message for the supplied URI.  It includes the calling
5518     * user and calling package(s).
5519     */
5520    public String exceptionMessage(Uri uri) {
5521        return exceptionMessage(null, uri);
5522    }
5523
5524    /**
5525     * Returns a detailed exception message for the supplied URI.  It includes the calling
5526     * user and calling package(s).
5527     */
5528    public String exceptionMessage(String message, Uri uri) {
5529        StringBuilder sb = new StringBuilder();
5530        if (message != null) {
5531            sb.append(message).append("; ");
5532        }
5533        sb.append("URI: ").append(uri);
5534        final PackageManager pm = mContext.getPackageManager();
5535        int callingUid = Binder.getCallingUid();
5536        sb.append(", calling user: ");
5537        String userName = pm.getNameForUid(callingUid);
5538        sb.append(userName == null ? callingUid : userName);
5539
5540        final String[] callerPackages = pm.getPackagesForUid(callingUid);
5541        if (callerPackages != null && callerPackages.length > 0) {
5542            if (callerPackages.length == 1) {
5543                sb.append(", calling package:");
5544                sb.append(callerPackages[0]);
5545            } else {
5546                sb.append(", calling package is one of: [");
5547                for (int i = 0; i < callerPackages.length; i++) {
5548                    if (i != 0) {
5549                        sb.append(", ");
5550                    }
5551                    sb.append(callerPackages[i]);
5552                }
5553                sb.append("]");
5554            }
5555        }
5556        return sb.toString();
5557    }
5558
5559    public void deleteStatusUpdate(long dataId) {
5560        if (mStatusUpdateDelete == null) {
5561            mStatusUpdateDelete = getWritableDatabase().compileStatement(
5562                    "DELETE FROM " + Tables.STATUS_UPDATES +
5563                    " WHERE " + StatusUpdatesColumns.DATA_ID + "=?");
5564        }
5565        mStatusUpdateDelete.bindLong(1, dataId);
5566        mStatusUpdateDelete.execute();
5567    }
5568
5569    public void replaceStatusUpdate(Long dataId, long timestamp, String status, String resPackage,
5570            Integer iconResource, Integer labelResource) {
5571        if (mStatusUpdateReplace == null) {
5572            mStatusUpdateReplace = getWritableDatabase().compileStatement(
5573                    "INSERT OR REPLACE INTO " + Tables.STATUS_UPDATES + "("
5574                            + StatusUpdatesColumns.DATA_ID + ", "
5575                            + StatusUpdates.STATUS_TIMESTAMP + ","
5576                            + StatusUpdates.STATUS + ","
5577                            + StatusUpdates.STATUS_RES_PACKAGE + ","
5578                            + StatusUpdates.STATUS_ICON + ","
5579                            + StatusUpdates.STATUS_LABEL + ")" +
5580                    " VALUES (?,?,?,?,?,?)");
5581        }
5582        mStatusUpdateReplace.bindLong(1, dataId);
5583        mStatusUpdateReplace.bindLong(2, timestamp);
5584        bindString(mStatusUpdateReplace, 3, status);
5585        bindString(mStatusUpdateReplace, 4, resPackage);
5586        bindLong(mStatusUpdateReplace, 5, iconResource);
5587        bindLong(mStatusUpdateReplace, 6, labelResource);
5588        mStatusUpdateReplace.execute();
5589    }
5590
5591    public void insertStatusUpdate(Long dataId, String status, String resPackage,
5592            Integer iconResource, Integer labelResource) {
5593        if (mStatusUpdateInsert == null) {
5594            mStatusUpdateInsert = getWritableDatabase().compileStatement(
5595                    "INSERT INTO " + Tables.STATUS_UPDATES + "("
5596                            + StatusUpdatesColumns.DATA_ID + ", "
5597                            + StatusUpdates.STATUS + ","
5598                            + StatusUpdates.STATUS_RES_PACKAGE + ","
5599                            + StatusUpdates.STATUS_ICON + ","
5600                            + StatusUpdates.STATUS_LABEL + ")" +
5601                    " VALUES (?,?,?,?,?)");
5602        }
5603        try {
5604            mStatusUpdateInsert.bindLong(1, dataId);
5605            bindString(mStatusUpdateInsert, 2, status);
5606            bindString(mStatusUpdateInsert, 3, resPackage);
5607            bindLong(mStatusUpdateInsert, 4, iconResource);
5608            bindLong(mStatusUpdateInsert, 5, labelResource);
5609            mStatusUpdateInsert.executeInsert();
5610        } catch (SQLiteConstraintException e) {
5611            // The row already exists - update it
5612            if (mStatusUpdateAutoTimestamp == null) {
5613                mStatusUpdateAutoTimestamp = getWritableDatabase().compileStatement(
5614                        "UPDATE " + Tables.STATUS_UPDATES +
5615                        " SET " + StatusUpdates.STATUS_TIMESTAMP + "=?,"
5616                                + StatusUpdates.STATUS + "=?" +
5617                        " WHERE " + StatusUpdatesColumns.DATA_ID + "=?"
5618                                + " AND " + StatusUpdates.STATUS + "!=?");
5619            }
5620
5621            long timestamp = System.currentTimeMillis();
5622            mStatusUpdateAutoTimestamp.bindLong(1, timestamp);
5623            bindString(mStatusUpdateAutoTimestamp, 2, status);
5624            mStatusUpdateAutoTimestamp.bindLong(3, dataId);
5625            bindString(mStatusUpdateAutoTimestamp, 4, status);
5626            mStatusUpdateAutoTimestamp.execute();
5627
5628            if (mStatusAttributionUpdate == null) {
5629                mStatusAttributionUpdate = getWritableDatabase().compileStatement(
5630                        "UPDATE " + Tables.STATUS_UPDATES +
5631                        " SET " + StatusUpdates.STATUS_RES_PACKAGE + "=?,"
5632                                + StatusUpdates.STATUS_ICON + "=?,"
5633                                + StatusUpdates.STATUS_LABEL + "=?" +
5634                        " WHERE " + StatusUpdatesColumns.DATA_ID + "=?");
5635            }
5636            bindString(mStatusAttributionUpdate, 1, resPackage);
5637            bindLong(mStatusAttributionUpdate, 2, iconResource);
5638            bindLong(mStatusAttributionUpdate, 3, labelResource);
5639            mStatusAttributionUpdate.bindLong(4, dataId);
5640            mStatusAttributionUpdate.execute();
5641        }
5642    }
5643
5644    /**
5645     * Updates a raw contact display name based on data rows, e.g. structured name,
5646     * organization, email etc.
5647     */
5648    public void updateRawContactDisplayName(SQLiteDatabase db, long rawContactId) {
5649        if (mNameSplitter == null) {
5650            createNameSplitter();
5651        }
5652
5653        int bestDisplayNameSource = DisplayNameSources.UNDEFINED;
5654        NameSplitter.Name bestName = null;
5655        String bestDisplayName = null;
5656        String bestPhoneticName = null;
5657        int bestPhoneticNameStyle = PhoneticNameStyle.UNDEFINED;
5658
5659        mSelectionArgs1[0] = String.valueOf(rawContactId);
5660        Cursor c = db.rawQuery(RawContactNameQuery.RAW_SQL, mSelectionArgs1);
5661        try {
5662            while (c.moveToNext()) {
5663                int mimeType = c.getInt(RawContactNameQuery.MIMETYPE);
5664                int source = getDisplayNameSourceForMimeTypeId(mimeType);
5665
5666                if (source == DisplayNameSources.STRUCTURED_NAME) {
5667                    final String given = c.getString(RawContactNameQuery.GIVEN_NAME);
5668                    final String middle = c.getString(RawContactNameQuery.MIDDLE_NAME);
5669                    final String family = c.getString(RawContactNameQuery.FAMILY_NAME);
5670                    final String suffix = c.getString(RawContactNameQuery.SUFFIX);
5671                    final String prefix = c.getString(RawContactNameQuery.PREFIX);
5672                    if (TextUtils.isEmpty(given) && TextUtils.isEmpty(middle)
5673                            && TextUtils.isEmpty(family) && TextUtils.isEmpty(suffix)
5674                            && TextUtils.isEmpty(prefix)) {
5675                        // Every non-phonetic name component is empty. Therefore, lets lower the
5676                        // source score to STRUCTURED_PHONETIC_NAME.
5677                        source = DisplayNameSources.STRUCTURED_PHONETIC_NAME;
5678                    }
5679                }
5680
5681                if (source < bestDisplayNameSource || source == DisplayNameSources.UNDEFINED) {
5682                    continue;
5683                }
5684
5685                if (source == bestDisplayNameSource
5686                        && c.getInt(RawContactNameQuery.IS_PRIMARY) == 0) {
5687                    continue;
5688                }
5689
5690                if (mimeType == getMimeTypeIdForStructuredName()) {
5691                    NameSplitter.Name name;
5692                    if (bestName != null) {
5693                        name = new NameSplitter.Name();
5694                    } else {
5695                        name = mName;
5696                        name.clear();
5697                    }
5698                    name.prefix = c.getString(RawContactNameQuery.PREFIX);
5699                    name.givenNames = c.getString(RawContactNameQuery.GIVEN_NAME);
5700                    name.middleName = c.getString(RawContactNameQuery.MIDDLE_NAME);
5701                    name.familyName = c.getString(RawContactNameQuery.FAMILY_NAME);
5702                    name.suffix = c.getString(RawContactNameQuery.SUFFIX);
5703                    name.fullNameStyle = c.isNull(RawContactNameQuery.FULL_NAME_STYLE)
5704                            ? FullNameStyle.UNDEFINED
5705                            : c.getInt(RawContactNameQuery.FULL_NAME_STYLE);
5706                    name.phoneticFamilyName = c.getString(RawContactNameQuery.PHONETIC_FAMILY_NAME);
5707                    name.phoneticMiddleName = c.getString(RawContactNameQuery.PHONETIC_MIDDLE_NAME);
5708                    name.phoneticGivenName = c.getString(RawContactNameQuery.PHONETIC_GIVEN_NAME);
5709                    name.phoneticNameStyle = c.isNull(RawContactNameQuery.PHONETIC_NAME_STYLE)
5710                            ? PhoneticNameStyle.UNDEFINED
5711                            : c.getInt(RawContactNameQuery.PHONETIC_NAME_STYLE);
5712                    if (!name.isEmpty()) {
5713                        bestDisplayNameSource = source;
5714                        bestName = name;
5715                    }
5716                } else if (mimeType == getMimeTypeIdForOrganization()) {
5717                    mCharArrayBuffer.sizeCopied = 0;
5718                    c.copyStringToBuffer(RawContactNameQuery.DATA1, mCharArrayBuffer);
5719                    if (mCharArrayBuffer.sizeCopied != 0) {
5720                        bestDisplayNameSource = source;
5721                        bestDisplayName = new String(mCharArrayBuffer.data, 0,
5722                                mCharArrayBuffer.sizeCopied);
5723                        bestPhoneticName = c.getString(
5724                                RawContactNameQuery.ORGANIZATION_PHONETIC_NAME);
5725                        bestPhoneticNameStyle =
5726                                c.isNull(RawContactNameQuery.ORGANIZATION_PHONETIC_NAME_STYLE)
5727                                   ? PhoneticNameStyle.UNDEFINED
5728                                   : c.getInt(RawContactNameQuery.ORGANIZATION_PHONETIC_NAME_STYLE);
5729                    } else {
5730                        c.copyStringToBuffer(RawContactNameQuery.TITLE, mCharArrayBuffer);
5731                        if (mCharArrayBuffer.sizeCopied != 0) {
5732                            bestDisplayNameSource = source;
5733                            bestDisplayName = new String(mCharArrayBuffer.data, 0,
5734                                    mCharArrayBuffer.sizeCopied);
5735                            bestPhoneticName = null;
5736                            bestPhoneticNameStyle = PhoneticNameStyle.UNDEFINED;
5737                        }
5738                    }
5739                } else {
5740                    // Display name is at DATA1 in all other types.
5741                    // This is ensured in the constructor.
5742
5743                    mCharArrayBuffer.sizeCopied = 0;
5744                    c.copyStringToBuffer(RawContactNameQuery.DATA1, mCharArrayBuffer);
5745                    if (mCharArrayBuffer.sizeCopied != 0) {
5746                        bestDisplayNameSource = source;
5747                        bestDisplayName = new String(mCharArrayBuffer.data, 0,
5748                                mCharArrayBuffer.sizeCopied);
5749                        bestPhoneticName = null;
5750                        bestPhoneticNameStyle = PhoneticNameStyle.UNDEFINED;
5751                    }
5752                }
5753            }
5754
5755        } finally {
5756            c.close();
5757        }
5758
5759        String displayNamePrimary;
5760        String displayNameAlternative;
5761        String sortNamePrimary;
5762        String sortNameAlternative;
5763        String sortKeyPrimary = null;
5764        String sortKeyAlternative = null;
5765        int displayNameStyle = FullNameStyle.UNDEFINED;
5766
5767        if (bestDisplayNameSource == DisplayNameSources.STRUCTURED_NAME
5768                || bestDisplayNameSource == DisplayNameSources.STRUCTURED_PHONETIC_NAME) {
5769            displayNameStyle = bestName.fullNameStyle;
5770            if (displayNameStyle == FullNameStyle.CJK
5771                    || displayNameStyle == FullNameStyle.UNDEFINED) {
5772                displayNameStyle = mNameSplitter.getAdjustedFullNameStyle(displayNameStyle);
5773                bestName.fullNameStyle = displayNameStyle;
5774            }
5775
5776            displayNamePrimary = mNameSplitter.join(bestName, true, true);
5777            displayNameAlternative = mNameSplitter.join(bestName, false, true);
5778
5779            if (TextUtils.isEmpty(bestName.prefix)) {
5780                sortNamePrimary = displayNamePrimary;
5781                sortNameAlternative = displayNameAlternative;
5782            } else {
5783                sortNamePrimary = mNameSplitter.join(bestName, true, false);
5784                sortNameAlternative = mNameSplitter.join(bestName, false, false);
5785            }
5786
5787            bestPhoneticName = mNameSplitter.joinPhoneticName(bestName);
5788            bestPhoneticNameStyle = bestName.phoneticNameStyle;
5789        } else {
5790            displayNamePrimary = displayNameAlternative = bestDisplayName;
5791            sortNamePrimary = sortNameAlternative = bestDisplayName;
5792        }
5793
5794        if (bestPhoneticName != null) {
5795            if (displayNamePrimary == null) {
5796                displayNamePrimary = bestPhoneticName;
5797            }
5798            if (displayNameAlternative == null) {
5799                displayNameAlternative = bestPhoneticName;
5800            }
5801            // Phonetic names disregard name order so displayNamePrimary and displayNameAlternative
5802            // are the same.
5803            sortKeyPrimary = sortKeyAlternative = bestPhoneticName;
5804            if (bestPhoneticNameStyle == PhoneticNameStyle.UNDEFINED) {
5805                bestPhoneticNameStyle = mNameSplitter.guessPhoneticNameStyle(bestPhoneticName);
5806            }
5807        } else {
5808            bestPhoneticNameStyle = PhoneticNameStyle.UNDEFINED;
5809            if (displayNameStyle == FullNameStyle.UNDEFINED) {
5810                displayNameStyle = mNameSplitter.guessFullNameStyle(bestDisplayName);
5811                if (displayNameStyle == FullNameStyle.UNDEFINED
5812                        || displayNameStyle == FullNameStyle.CJK) {
5813                    displayNameStyle = mNameSplitter.getAdjustedNameStyleBasedOnPhoneticNameStyle(
5814                            displayNameStyle, bestPhoneticNameStyle);
5815                }
5816                displayNameStyle = mNameSplitter.getAdjustedFullNameStyle(displayNameStyle);
5817            }
5818            if (displayNameStyle == FullNameStyle.CHINESE ||
5819                    displayNameStyle == FullNameStyle.CJK) {
5820                sortKeyPrimary = sortKeyAlternative = sortNamePrimary;
5821            }
5822        }
5823
5824        if (sortKeyPrimary == null) {
5825            sortKeyPrimary = sortNamePrimary;
5826            sortKeyAlternative = sortNameAlternative;
5827        }
5828
5829        String phonebookLabelPrimary = "";
5830        String phonebookLabelAlternative = "";
5831        int phonebookBucketPrimary = 0;
5832        int phonebookBucketAlternative = 0;
5833        ContactLocaleUtils localeUtils = ContactLocaleUtils.getInstance();
5834
5835        if (sortKeyPrimary != null) {
5836            phonebookBucketPrimary = localeUtils.getBucketIndex(sortKeyPrimary);
5837            phonebookLabelPrimary = localeUtils.getBucketLabel(phonebookBucketPrimary);
5838        }
5839        if (sortKeyAlternative != null) {
5840            phonebookBucketAlternative = localeUtils.getBucketIndex(sortKeyAlternative);
5841            phonebookLabelAlternative = localeUtils.getBucketLabel(phonebookBucketAlternative);
5842        }
5843
5844        if (mRawContactDisplayNameUpdate == null) {
5845            mRawContactDisplayNameUpdate = db.compileStatement(
5846                    "UPDATE " + Tables.RAW_CONTACTS +
5847                    " SET " +
5848                            RawContacts.DISPLAY_NAME_SOURCE + "=?," +
5849                            RawContacts.DISPLAY_NAME_PRIMARY + "=?," +
5850                            RawContacts.DISPLAY_NAME_ALTERNATIVE + "=?," +
5851                            RawContacts.PHONETIC_NAME + "=?," +
5852                            RawContacts.PHONETIC_NAME_STYLE + "=?," +
5853                            RawContacts.SORT_KEY_PRIMARY + "=?," +
5854                            RawContactsColumns.PHONEBOOK_LABEL_PRIMARY + "=?," +
5855                            RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY + "=?," +
5856                            RawContacts.SORT_KEY_ALTERNATIVE + "=?," +
5857                            RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + "=?," +
5858                            RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE + "=?" +
5859                    " WHERE " + RawContacts._ID + "=?");
5860        }
5861
5862        mRawContactDisplayNameUpdate.bindLong(1, bestDisplayNameSource);
5863        bindString(mRawContactDisplayNameUpdate, 2, displayNamePrimary);
5864        bindString(mRawContactDisplayNameUpdate, 3, displayNameAlternative);
5865        bindString(mRawContactDisplayNameUpdate, 4, bestPhoneticName);
5866        mRawContactDisplayNameUpdate.bindLong(5, bestPhoneticNameStyle);
5867        bindString(mRawContactDisplayNameUpdate, 6, sortKeyPrimary);
5868        bindString(mRawContactDisplayNameUpdate, 7, phonebookLabelPrimary);
5869        mRawContactDisplayNameUpdate.bindLong(8, phonebookBucketPrimary);
5870        bindString(mRawContactDisplayNameUpdate, 9, sortKeyAlternative);
5871        bindString(mRawContactDisplayNameUpdate, 10, phonebookLabelAlternative);
5872        mRawContactDisplayNameUpdate.bindLong(11, phonebookBucketAlternative);
5873        mRawContactDisplayNameUpdate.bindLong(12, rawContactId);
5874        mRawContactDisplayNameUpdate.execute();
5875    }
5876
5877    /**
5878     * Sets the given dataId record in the "data" table to primary, and resets all data records of
5879     * the same mimetype and under the same contact to not be primary.
5880     *
5881     * @param dataId the id of the data record to be set to primary. Pass -1 to clear the primary
5882     * flag of all data items of this raw contacts
5883     */
5884    public void setIsPrimary(long rawContactId, long dataId, long mimeTypeId) {
5885        if (mSetPrimaryStatement == null) {
5886            mSetPrimaryStatement = getWritableDatabase().compileStatement(
5887                    "UPDATE " + Tables.DATA +
5888                    " SET " + Data.IS_PRIMARY + "=(_id=?)" +
5889                    " WHERE " + DataColumns.MIMETYPE_ID + "=?" +
5890                    "   AND " + Data.RAW_CONTACT_ID + "=?");
5891        }
5892        mSetPrimaryStatement.bindLong(1, dataId);
5893        mSetPrimaryStatement.bindLong(2, mimeTypeId);
5894        mSetPrimaryStatement.bindLong(3, rawContactId);
5895        mSetPrimaryStatement.execute();
5896    }
5897
5898    /**
5899     * Clears the super primary of all data items of the given raw contact. does not touch
5900     * other raw contacts of the same joined aggregate
5901     */
5902    public void clearSuperPrimary(long rawContactId, long mimeTypeId) {
5903        if (mClearSuperPrimaryStatement == null) {
5904            mClearSuperPrimaryStatement = getWritableDatabase().compileStatement(
5905                    "UPDATE " + Tables.DATA +
5906                    " SET " + Data.IS_SUPER_PRIMARY + "=0" +
5907                    " WHERE " + DataColumns.MIMETYPE_ID + "=?" +
5908                    "   AND " + Data.RAW_CONTACT_ID + "=?");
5909        }
5910        mClearSuperPrimaryStatement.bindLong(1, mimeTypeId);
5911        mClearSuperPrimaryStatement.bindLong(2, rawContactId);
5912        mClearSuperPrimaryStatement.execute();
5913    }
5914
5915    /**
5916     * Sets the given dataId record in the "data" table to "super primary", and resets all data
5917     * records of the same mimetype and under the same aggregate to not be "super primary".
5918     *
5919     * @param dataId the id of the data record to be set to primary.
5920     */
5921    public void setIsSuperPrimary(long rawContactId, long dataId, long mimeTypeId) {
5922        if (mSetSuperPrimaryStatement == null) {
5923            mSetSuperPrimaryStatement = getWritableDatabase().compileStatement(
5924                    "UPDATE " + Tables.DATA +
5925                    " SET " + Data.IS_SUPER_PRIMARY + "=(" + Data._ID + "=?)" +
5926                    " WHERE " + DataColumns.MIMETYPE_ID + "=?" +
5927                    "   AND " + Data.RAW_CONTACT_ID + " IN (" +
5928                            "SELECT " + RawContacts._ID +
5929                            " FROM " + Tables.RAW_CONTACTS +
5930                            " WHERE " + RawContacts.CONTACT_ID + " =(" +
5931                                    "SELECT " + RawContacts.CONTACT_ID +
5932                                    " FROM " + Tables.RAW_CONTACTS +
5933                                    " WHERE " + RawContacts._ID + "=?))");
5934        }
5935        mSetSuperPrimaryStatement.bindLong(1, dataId);
5936        mSetSuperPrimaryStatement.bindLong(2, mimeTypeId);
5937        mSetSuperPrimaryStatement.bindLong(3, rawContactId);
5938        mSetSuperPrimaryStatement.execute();
5939    }
5940
5941    /**
5942     * Inserts a record in the {@link Tables#NAME_LOOKUP} table.
5943     */
5944    public void insertNameLookup(long rawContactId, long dataId, int lookupType, String name) {
5945        if (TextUtils.isEmpty(name)) {
5946            return;
5947        }
5948
5949        if (mNameLookupInsert == null) {
5950            mNameLookupInsert = getWritableDatabase().compileStatement(
5951                    "INSERT OR IGNORE INTO " + Tables.NAME_LOOKUP + "("
5952                            + NameLookupColumns.RAW_CONTACT_ID + ","
5953                            + NameLookupColumns.DATA_ID + ","
5954                            + NameLookupColumns.NAME_TYPE + ","
5955                            + NameLookupColumns.NORMALIZED_NAME
5956                    + ") VALUES (?,?,?,?)");
5957        }
5958        mNameLookupInsert.bindLong(1, rawContactId);
5959        mNameLookupInsert.bindLong(2, dataId);
5960        mNameLookupInsert.bindLong(3, lookupType);
5961        bindString(mNameLookupInsert, 4, name);
5962        mNameLookupInsert.executeInsert();
5963    }
5964
5965    /**
5966     * Deletes all {@link Tables#NAME_LOOKUP} table rows associated with the specified data element.
5967     */
5968    public void deleteNameLookup(long dataId) {
5969        if (mNameLookupDelete == null) {
5970            mNameLookupDelete = getWritableDatabase().compileStatement(
5971                    "DELETE FROM " + Tables.NAME_LOOKUP +
5972                    " WHERE " + NameLookupColumns.DATA_ID + "=?");
5973        }
5974        mNameLookupDelete.bindLong(1, dataId);
5975        mNameLookupDelete.execute();
5976    }
5977
5978    public String insertNameLookupForEmail(long rawContactId, long dataId, String email) {
5979        if (TextUtils.isEmpty(email)) {
5980            return null;
5981        }
5982
5983        String address = extractHandleFromEmailAddress(email);
5984        if (address == null) {
5985            return null;
5986        }
5987
5988        insertNameLookup(rawContactId, dataId,
5989                NameLookupType.EMAIL_BASED_NICKNAME, NameNormalizer.normalize(address));
5990        return address;
5991    }
5992
5993    /**
5994     * Normalizes the nickname and inserts it in the name lookup table.
5995     */
5996    public void insertNameLookupForNickname(long rawContactId, long dataId, String nickname) {
5997        if (!TextUtils.isEmpty(nickname)) {
5998            insertNameLookup(rawContactId, dataId,
5999                    NameLookupType.NICKNAME, NameNormalizer.normalize(nickname));
6000        }
6001    }
6002
6003    /**
6004     * Performs a query and returns true if any Data item of the raw contact with the given
6005     * id and mimetype is marked as super-primary
6006     */
6007    public boolean rawContactHasSuperPrimary(long rawContactId, long mimeTypeId) {
6008        final Cursor existsCursor = getReadableDatabase().rawQuery(
6009                "SELECT EXISTS(SELECT 1 FROM " + Tables.DATA +
6010                " WHERE " + Data.RAW_CONTACT_ID + "=?" +
6011                " AND " + DataColumns.MIMETYPE_ID + "=?" +
6012                " AND " + Data.IS_SUPER_PRIMARY + "<>0)",
6013                new String[] {String.valueOf(rawContactId), String.valueOf(mimeTypeId)});
6014        try {
6015            if (!existsCursor.moveToFirst()) throw new IllegalStateException();
6016            return existsCursor.getInt(0) != 0;
6017        } finally {
6018            existsCursor.close();
6019        }
6020    }
6021
6022    public String getCurrentCountryIso() {
6023        return mCountryMonitor.getCountryIso();
6024    }
6025
6026    @NeededForTesting
6027    /* package */ void setUseStrictPhoneNumberComparisonForTest(boolean useStrict) {
6028        mUseStrictPhoneNumberComparison = useStrict;
6029    }
6030
6031    @NeededForTesting
6032    /* package */ boolean getUseStrictPhoneNumberComparisonForTest() {
6033        return mUseStrictPhoneNumberComparison;
6034    }
6035
6036    @NeededForTesting
6037    /* package */ String querySearchIndexContentForTest(long contactId) {
6038        return DatabaseUtils.stringForQuery(getReadableDatabase(),
6039                "SELECT " + SearchIndexColumns.CONTENT +
6040                " FROM " + Tables.SEARCH_INDEX +
6041                " WHERE " + SearchIndexColumns.CONTACT_ID + "=CAST(? AS int)",
6042                new String[] {String.valueOf(contactId)});
6043    }
6044
6045    @NeededForTesting
6046    /* package */ String querySearchIndexTokensForTest(long contactId) {
6047        return DatabaseUtils.stringForQuery(getReadableDatabase(),
6048                "SELECT " + SearchIndexColumns.TOKENS +
6049                " FROM " + Tables.SEARCH_INDEX +
6050                " WHERE " + SearchIndexColumns.CONTACT_ID + "=CAST(? AS int)",
6051                new String[] {String.valueOf(contactId)});
6052    }
6053}
6054