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