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