ContactsDatabaseHelper.java revision 6423c02762d2975d02a06ea5c7b4e2955a3a4bed
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 = 1105;
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.DATE + " INTEGER," +
1560                Calls.DURATION + " INTEGER," +
1561                Calls.DATA_USAGE + " INTEGER," +
1562                Calls.TYPE + " INTEGER," +
1563                Calls.FEATURES + " INTEGER NOT NULL DEFAULT 0," +
1564                Calls.PHONE_ACCOUNT_COMPONENT_NAME + " TEXT," +
1565                Calls.PHONE_ACCOUNT_ID + " TEXT," +
1566                Calls.PHONE_ACCOUNT_ADDRESS + " TEXT," +
1567                Calls.PHONE_ACCOUNT_HIDDEN + " INTEGER NOT NULL DEFAULT 0," +
1568                Calls.SUB_ID + " INTEGER DEFAULT -1," +
1569                Calls.NEW + " INTEGER," +
1570                Calls.CACHED_NAME + " TEXT," +
1571                Calls.CACHED_NUMBER_TYPE + " INTEGER," +
1572                Calls.CACHED_NUMBER_LABEL + " TEXT," +
1573                Calls.COUNTRY_ISO + " TEXT," +
1574                Calls.VOICEMAIL_URI + " TEXT," +
1575                Calls.IS_READ + " INTEGER," +
1576                Calls.GEOCODED_LOCATION + " TEXT," +
1577                Calls.CACHED_LOOKUP_URI + " TEXT," +
1578                Calls.CACHED_MATCHED_NUMBER + " TEXT," +
1579                Calls.CACHED_NORMALIZED_NUMBER + " TEXT," +
1580                Calls.CACHED_PHOTO_ID + " INTEGER NOT NULL DEFAULT 0," +
1581                Calls.CACHED_PHOTO_URI + " TEXT," +
1582                Calls.CACHED_FORMATTED_NUMBER + " TEXT," +
1583                Voicemails._DATA + " TEXT," +
1584                Voicemails.HAS_CONTENT + " INTEGER," +
1585                Voicemails.MIME_TYPE + " TEXT," +
1586                Voicemails.SOURCE_DATA + " TEXT," +
1587                Voicemails.SOURCE_PACKAGE + " TEXT," +
1588                Voicemails.TRANSCRIPTION + " TEXT," +
1589                Voicemails.STATE + " INTEGER," +
1590                Voicemails.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
1591                Voicemails.DELETED + " INTEGER NOT NULL DEFAULT 0" +
1592        ");");
1593
1594        // Voicemail source status table.
1595        db.execSQL("CREATE TABLE " + Tables.VOICEMAIL_STATUS + " (" +
1596                VoicemailContract.Status._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1597                VoicemailContract.Status.SOURCE_PACKAGE + " TEXT UNIQUE NOT NULL," +
1598                VoicemailContract.Status.PHONE_ACCOUNT_COMPONENT_NAME + " TEXT," +
1599                VoicemailContract.Status.PHONE_ACCOUNT_ID + " TEXT," +
1600                VoicemailContract.Status.SETTINGS_URI + " TEXT," +
1601                VoicemailContract.Status.VOICEMAIL_ACCESS_URI + " TEXT," +
1602                VoicemailContract.Status.CONFIGURATION_STATE + " INTEGER," +
1603                VoicemailContract.Status.DATA_CHANNEL_STATE + " INTEGER," +
1604                VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE + " INTEGER" +
1605        ");");
1606
1607        db.execSQL("CREATE TABLE " + Tables.STATUS_UPDATES + " (" +
1608                StatusUpdatesColumns.DATA_ID + " INTEGER PRIMARY KEY REFERENCES data(_id)," +
1609                StatusUpdates.STATUS + " TEXT," +
1610                StatusUpdates.STATUS_TIMESTAMP + " INTEGER," +
1611                StatusUpdates.STATUS_RES_PACKAGE + " TEXT, " +
1612                StatusUpdates.STATUS_LABEL + " INTEGER, " +
1613                StatusUpdates.STATUS_ICON + " INTEGER" +
1614        ");");
1615
1616        createDirectoriesTable(db);
1617        createSearchIndexTable(db, false /* we build stats table later */);
1618
1619        db.execSQL("CREATE TABLE " + Tables.DATA_USAGE_STAT + "(" +
1620                DataUsageStatColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
1621                DataUsageStatColumns.DATA_ID + " INTEGER NOT NULL, " +
1622                DataUsageStatColumns.USAGE_TYPE_INT + " INTEGER NOT NULL DEFAULT 0, " +
1623                DataUsageStatColumns.TIMES_USED + " INTEGER NOT NULL DEFAULT 0, " +
1624                DataUsageStatColumns.LAST_TIME_USED + " INTERGER NOT NULL DEFAULT 0, " +
1625                "FOREIGN KEY(" + DataUsageStatColumns.DATA_ID + ") REFERENCES "
1626                        + Tables.DATA + "(" + Data._ID + ")" +
1627        ");");
1628        db.execSQL("CREATE UNIQUE INDEX data_usage_stat_index ON " +
1629                Tables.DATA_USAGE_STAT + " (" +
1630                DataUsageStatColumns.DATA_ID + ", " +
1631                DataUsageStatColumns.USAGE_TYPE_INT +
1632        ");");
1633
1634        db.execSQL("CREATE TABLE IF NOT EXISTS "
1635                + Tables.METADATA_SYNC + " (" +
1636                MetadataSync._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1637                MetadataSync.RAW_CONTACT_BACKUP_ID + " TEXT NOT NULL," +
1638                MetadataSyncColumns.ACCOUNT_ID + " INTEGER NOT NULL," +
1639                MetadataSync.DATA + " TEXT," +
1640                MetadataSync.DELETED + " INTEGER NOT NULL DEFAULT 0);");
1641
1642        db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS metadata_sync_index ON " +
1643                Tables.METADATA_SYNC + " (" +
1644                MetadataSync.RAW_CONTACT_BACKUP_ID + ", " +
1645                MetadataSyncColumns.ACCOUNT_ID +");");
1646
1647        db.execSQL("CREATE TABLE " + Tables.PRE_AUTHORIZED_URIS + " ("+
1648                PreAuthorizedUris._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
1649                PreAuthorizedUris.URI + " STRING NOT NULL, " +
1650                PreAuthorizedUris.EXPIRATION + " INTEGER NOT NULL DEFAULT 0);");
1651
1652        db.execSQL("CREATE TABLE IF NOT EXISTS "
1653                + Tables.METADATA_SYNC_STATE + " (" +
1654                MetadataSyncState._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1655                MetadataSyncStateColumns.ACCOUNT_ID + " INTEGER NOT NULL," +
1656                MetadataSyncState.STATE + " BLOB);");
1657
1658        db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS metadata_sync_state_index ON " +
1659                Tables.METADATA_SYNC_STATE + " (" +
1660                MetadataSyncColumns.ACCOUNT_ID +");");
1661
1662        // When adding new tables, be sure to also add size-estimates in updateSqliteStats
1663        createContactsViews(db);
1664        createGroupsView(db);
1665        createContactsTriggers(db);
1666        createContactsIndexes(db, false /* we build stats table later */);
1667
1668        loadNicknameLookupTable(db);
1669
1670        // Set sequence starts.
1671        initializeAutoIncrementSequences(db);
1672
1673        // Add the legacy API support views, etc.
1674        LegacyApiSupport.createDatabase(db);
1675
1676        if (mDatabaseOptimizationEnabled) {
1677            // This will create a sqlite_stat1 table that is used for query optimization
1678            db.execSQL("ANALYZE;");
1679
1680            updateSqliteStats(db);
1681        }
1682
1683        ContentResolver.requestSync(null /* all accounts */,
1684                ContactsContract.AUTHORITY, new Bundle());
1685
1686        // Only send broadcasts for regular contacts db.
1687        if (dbForProfile() == 0) {
1688            final Intent dbCreatedIntent = new Intent(
1689                    ContactsContract.Intents.CONTACTS_DATABASE_CREATED);
1690            dbCreatedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
1691            mContext.sendBroadcast(dbCreatedIntent, android.Manifest.permission.READ_CONTACTS);
1692        }
1693    }
1694
1695    protected void initializeAutoIncrementSequences(SQLiteDatabase db) {
1696        // Default implementation does nothing.
1697    }
1698
1699    private void createDirectoriesTable(SQLiteDatabase db) {
1700        db.execSQL("CREATE TABLE " + Tables.DIRECTORIES + "(" +
1701                Directory._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
1702                Directory.PACKAGE_NAME + " TEXT NOT NULL," +
1703                Directory.DIRECTORY_AUTHORITY + " TEXT NOT NULL," +
1704                Directory.TYPE_RESOURCE_ID + " INTEGER," +
1705                DirectoryColumns.TYPE_RESOURCE_NAME + " TEXT," +
1706                Directory.ACCOUNT_TYPE + " TEXT," +
1707                Directory.ACCOUNT_NAME + " TEXT," +
1708                Directory.DISPLAY_NAME + " TEXT, " +
1709                Directory.EXPORT_SUPPORT + " INTEGER NOT NULL" +
1710                        " DEFAULT " + Directory.EXPORT_SUPPORT_NONE + "," +
1711                Directory.SHORTCUT_SUPPORT + " INTEGER NOT NULL" +
1712                        " DEFAULT " + Directory.SHORTCUT_SUPPORT_NONE + "," +
1713                Directory.PHOTO_SUPPORT + " INTEGER NOT NULL" +
1714                        " DEFAULT " + Directory.PHOTO_SUPPORT_NONE +
1715        ");");
1716
1717        // Trigger a full scan of directories in the system
1718        setProperty(db, DbProperties.DIRECTORY_SCAN_COMPLETE, "0");
1719    }
1720
1721    public void createSearchIndexTable(SQLiteDatabase db, boolean rebuildSqliteStats) {
1722        db.beginTransaction();
1723        try {
1724            db.execSQL("DROP TABLE IF EXISTS " + Tables.SEARCH_INDEX);
1725            db.execSQL("CREATE VIRTUAL TABLE " + Tables.SEARCH_INDEX
1726                    + " USING FTS4 ("
1727                    + SearchIndexColumns.CONTACT_ID + " INTEGER REFERENCES contacts(_id) NOT NULL,"
1728                    + SearchIndexColumns.CONTENT + " TEXT, "
1729                    + SearchIndexColumns.NAME + " TEXT, "
1730                    + SearchIndexColumns.TOKENS + " TEXT"
1731                    + ")");
1732            if (rebuildSqliteStats) {
1733                updateSqliteStats(db);
1734            }
1735            db.setTransactionSuccessful();
1736        } finally {
1737            db.endTransaction();
1738        }
1739    }
1740
1741    private void createContactsTriggers(SQLiteDatabase db) {
1742
1743        // Automatically delete Data rows when a raw contact is deleted.
1744        db.execSQL("DROP TRIGGER IF EXISTS " + Tables.RAW_CONTACTS + "_deleted;");
1745        db.execSQL("CREATE TRIGGER " + Tables.RAW_CONTACTS + "_deleted "
1746                + "   BEFORE DELETE ON " + Tables.RAW_CONTACTS
1747                + " BEGIN "
1748                + "   DELETE FROM " + Tables.DATA
1749                + "     WHERE " + Data.RAW_CONTACT_ID
1750                                + "=OLD." + RawContacts._ID + ";"
1751                + "   DELETE FROM " + Tables.AGGREGATION_EXCEPTIONS
1752                + "     WHERE " + AggregationExceptions.RAW_CONTACT_ID1
1753                                + "=OLD." + RawContacts._ID
1754                + "        OR " + AggregationExceptions.RAW_CONTACT_ID2
1755                                + "=OLD." + RawContacts._ID + ";"
1756                + "   DELETE FROM " + Tables.VISIBLE_CONTACTS
1757                + "     WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID
1758                + "       AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS
1759                + "            WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID
1760                + "           )=1;"
1761                + "   DELETE FROM " + Tables.DEFAULT_DIRECTORY
1762                + "     WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID
1763                + "       AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS
1764                + "            WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID
1765                + "           )=1;"
1766                + "   DELETE FROM " + Tables.CONTACTS
1767                + "     WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID
1768                + "       AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS
1769                + "            WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID
1770                + "           )=1;"
1771                + " END");
1772
1773
1774        db.execSQL("DROP TRIGGER IF EXISTS contacts_times_contacted;");
1775        db.execSQL("DROP TRIGGER IF EXISTS raw_contacts_times_contacted;");
1776
1777        // Triggers that update {@link RawContacts#VERSION} when the contact is marked for deletion
1778        // or any time a data row is inserted, updated or deleted.
1779        db.execSQL("DROP TRIGGER IF EXISTS " + Tables.RAW_CONTACTS + "_marked_deleted;");
1780        db.execSQL("CREATE TRIGGER " + Tables.RAW_CONTACTS + "_marked_deleted "
1781                + "   AFTER UPDATE ON " + Tables.RAW_CONTACTS
1782                + " BEGIN "
1783                + "   UPDATE " + Tables.RAW_CONTACTS
1784                + "     SET "
1785                +         RawContacts.VERSION + "=OLD." + RawContacts.VERSION + "+1 "
1786                + "     WHERE " + RawContacts._ID + "=OLD." + RawContacts._ID
1787                + "       AND NEW." + RawContacts.DELETED + "!= OLD." + RawContacts.DELETED + ";"
1788                + " END");
1789
1790        db.execSQL("DROP TRIGGER IF EXISTS " + Tables.DATA + "_updated;");
1791        db.execSQL("CREATE TRIGGER " + Tables.DATA + "_updated AFTER UPDATE ON " + Tables.DATA
1792                + " BEGIN "
1793                + "   UPDATE " + Tables.DATA
1794                + "     SET " + Data.DATA_VERSION + "=OLD." + Data.DATA_VERSION + "+1 "
1795                + "     WHERE " + Data._ID + "=OLD." + Data._ID + ";"
1796                + "   UPDATE " + Tables.RAW_CONTACTS
1797                + "     SET " + RawContacts.VERSION + "=" + RawContacts.VERSION + "+1 "
1798                + "     WHERE " + RawContacts._ID + "=OLD." + Data.RAW_CONTACT_ID + ";"
1799                + " END");
1800
1801        db.execSQL("DROP TRIGGER IF EXISTS " + Tables.DATA + "_deleted;");
1802        db.execSQL("CREATE TRIGGER " + Tables.DATA + "_deleted BEFORE DELETE ON " + Tables.DATA
1803                + " BEGIN "
1804                + "   UPDATE " + Tables.RAW_CONTACTS
1805                + "     SET " + RawContacts.VERSION + "=" + RawContacts.VERSION + "+1 "
1806                + "     WHERE " + RawContacts._ID + "=OLD." + Data.RAW_CONTACT_ID + ";"
1807                + "   DELETE FROM " + Tables.PHONE_LOOKUP
1808                + "     WHERE " + PhoneLookupColumns.DATA_ID + "=OLD." + Data._ID + ";"
1809                + "   DELETE FROM " + Tables.STATUS_UPDATES
1810                + "     WHERE " + StatusUpdatesColumns.DATA_ID + "=OLD." + Data._ID + ";"
1811                + "   DELETE FROM " + Tables.NAME_LOOKUP
1812                + "     WHERE " + NameLookupColumns.DATA_ID + "=OLD." + Data._ID + ";"
1813                + " END");
1814
1815
1816        db.execSQL("DROP TRIGGER IF EXISTS " + Tables.GROUPS + "_updated1;");
1817        db.execSQL("CREATE TRIGGER " + Tables.GROUPS + "_updated1 "
1818                + "   AFTER UPDATE ON " + Tables.GROUPS
1819                + " BEGIN "
1820                + "   UPDATE " + Tables.GROUPS
1821                + "     SET "
1822                +         Groups.VERSION + "=OLD." + Groups.VERSION + "+1"
1823                + "     WHERE " + Groups._ID + "=OLD." + Groups._ID + ";"
1824                + " END");
1825
1826        // Update DEFAULT_FILTER table per AUTO_ADD column update, see upgradeToVersion411.
1827        final String insertContactsWithoutAccount = (
1828                " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
1829                "     SELECT " + RawContacts.CONTACT_ID +
1830                "     FROM " + Tables.RAW_CONTACTS +
1831                "     WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID +
1832                            "=" + Clauses.LOCAL_ACCOUNT_ID + ";");
1833
1834        final String insertContactsWithAccountNoDefaultGroup = (
1835                " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
1836                "     SELECT " + RawContacts.CONTACT_ID +
1837                "         FROM " + Tables.RAW_CONTACTS +
1838                "     WHERE NOT EXISTS" +
1839                "         (SELECT " + Groups._ID +
1840                "             FROM " + Tables.GROUPS +
1841                "             WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID + " = " +
1842                                    GroupsColumns.CONCRETE_ACCOUNT_ID +
1843                "             AND " + Groups.AUTO_ADD + " != 0" + ");");
1844
1845        final String insertContactsWithAccountDefaultGroup = (
1846                " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
1847                "     SELECT " + RawContacts.CONTACT_ID +
1848                "         FROM " + Tables.RAW_CONTACTS +
1849                "     JOIN " + Tables.DATA +
1850                "           ON (" + RawContactsColumns.CONCRETE_ID + "=" +
1851                        Data.RAW_CONTACT_ID + ")" +
1852                "     WHERE " + DataColumns.MIMETYPE_ID + "=" +
1853                    "(SELECT " + MimetypesColumns._ID + " FROM " + Tables.MIMETYPES +
1854                        " WHERE " + MimetypesColumns.MIMETYPE +
1855                            "='" + GroupMembership.CONTENT_ITEM_TYPE + "')" +
1856                "     AND EXISTS" +
1857                "         (SELECT " + Groups._ID +
1858                "             FROM " + Tables.GROUPS +
1859                "                 WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID + " = " +
1860                                        GroupsColumns.CONCRETE_ACCOUNT_ID +
1861                "                 AND " + Groups.AUTO_ADD + " != 0" + ");");
1862
1863        db.execSQL("DROP TRIGGER IF EXISTS " + Tables.GROUPS + "_auto_add_updated1;");
1864        db.execSQL("CREATE TRIGGER " + Tables.GROUPS + "_auto_add_updated1 "
1865                + "   AFTER UPDATE OF " + Groups.AUTO_ADD + " ON " + Tables.GROUPS
1866                + " BEGIN "
1867                + "   DELETE FROM " + Tables.DEFAULT_DIRECTORY + ";"
1868                    + insertContactsWithoutAccount
1869                    + insertContactsWithAccountNoDefaultGroup
1870                    + insertContactsWithAccountDefaultGroup
1871                + " END");
1872    }
1873
1874    private void createContactsIndexes(SQLiteDatabase db, boolean rebuildSqliteStats) {
1875        db.execSQL("DROP INDEX IF EXISTS name_lookup_index");
1876        db.execSQL("CREATE INDEX name_lookup_index ON " + Tables.NAME_LOOKUP + " (" +
1877                NameLookupColumns.NORMALIZED_NAME + "," +
1878                NameLookupColumns.NAME_TYPE + ", " +
1879                NameLookupColumns.RAW_CONTACT_ID + ", " +
1880                NameLookupColumns.DATA_ID +
1881        ");");
1882
1883        db.execSQL("DROP INDEX IF EXISTS raw_contact_sort_key1_index");
1884        db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" +
1885                RawContacts.SORT_KEY_PRIMARY +
1886        ");");
1887
1888        db.execSQL("DROP INDEX IF EXISTS raw_contact_sort_key2_index");
1889        db.execSQL("CREATE INDEX raw_contact_sort_key2_index ON " + Tables.RAW_CONTACTS + " (" +
1890                RawContacts.SORT_KEY_ALTERNATIVE +
1891        ");");
1892
1893        if (rebuildSqliteStats) {
1894            updateSqliteStats(db);
1895        }
1896    }
1897
1898    private void createContactsViews(SQLiteDatabase db) {
1899        db.execSQL("DROP VIEW IF EXISTS " + Views.CONTACTS + ";");
1900        db.execSQL("DROP VIEW IF EXISTS " + Views.DATA + ";");
1901        db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_CONTACTS + ";");
1902        db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_ENTITIES + ";");
1903        db.execSQL("DROP VIEW IF EXISTS " + Views.ENTITIES + ";");
1904        db.execSQL("DROP VIEW IF EXISTS " + Views.DATA_USAGE_STAT + ";");
1905        db.execSQL("DROP VIEW IF EXISTS " + Views.STREAM_ITEMS + ";");
1906        db.execSQL("DROP VIEW IF EXISTS " + Views.METADATA_SYNC + ";");
1907
1908        String dataColumns =
1909                Data.IS_PRIMARY + ", "
1910                + Data.IS_SUPER_PRIMARY + ", "
1911                + Data.DATA_VERSION + ", "
1912                + DataColumns.CONCRETE_PACKAGE_ID + ","
1913                + PackagesColumns.PACKAGE + " AS " + Data.RES_PACKAGE + ","
1914                + DataColumns.CONCRETE_MIMETYPE_ID + ","
1915                + MimetypesColumns.MIMETYPE + " AS " + Data.MIMETYPE + ", "
1916                + Data.IS_READ_ONLY + ", "
1917                + Data.DATA1 + ", "
1918                + Data.DATA2 + ", "
1919                + Data.DATA3 + ", "
1920                + Data.DATA4 + ", "
1921                + Data.DATA5 + ", "
1922                + Data.DATA6 + ", "
1923                + Data.DATA7 + ", "
1924                + Data.DATA8 + ", "
1925                + Data.DATA9 + ", "
1926                + Data.DATA10 + ", "
1927                + Data.DATA11 + ", "
1928                + Data.DATA12 + ", "
1929                + Data.DATA13 + ", "
1930                + Data.DATA14 + ", "
1931                + Data.DATA15 + ", "
1932                + Data.CARRIER_PRESENCE + ", "
1933                + Data.SYNC1 + ", "
1934                + Data.SYNC2 + ", "
1935                + Data.SYNC3 + ", "
1936                + Data.SYNC4;
1937
1938        String syncColumns =
1939                RawContactsColumns.CONCRETE_ACCOUNT_ID + ","
1940                + AccountsColumns.CONCRETE_ACCOUNT_NAME + " AS " + RawContacts.ACCOUNT_NAME + ","
1941                + AccountsColumns.CONCRETE_ACCOUNT_TYPE + " AS " + RawContacts.ACCOUNT_TYPE + ","
1942                + AccountsColumns.CONCRETE_DATA_SET + " AS " + RawContacts.DATA_SET + ","
1943                + "(CASE WHEN " + AccountsColumns.CONCRETE_DATA_SET + " IS NULL THEN "
1944                            + AccountsColumns.CONCRETE_ACCOUNT_TYPE
1945                        + " ELSE " + AccountsColumns.CONCRETE_ACCOUNT_TYPE + "||'/'||"
1946                            + AccountsColumns.CONCRETE_DATA_SET + " END) AS "
1947                                + RawContacts.ACCOUNT_TYPE_AND_DATA_SET + ","
1948                + RawContactsColumns.CONCRETE_SOURCE_ID + " AS " + RawContacts.SOURCE_ID + ","
1949                + RawContactsColumns.CONCRETE_BACKUP_ID + " AS " + RawContacts.BACKUP_ID + ","
1950                + RawContactsColumns.CONCRETE_VERSION + " AS " + RawContacts.VERSION + ","
1951                + RawContactsColumns.CONCRETE_DIRTY + " AS " + RawContacts.DIRTY + ","
1952                + RawContactsColumns.CONCRETE_SYNC1 + " AS " + RawContacts.SYNC1 + ","
1953                + RawContactsColumns.CONCRETE_SYNC2 + " AS " + RawContacts.SYNC2 + ","
1954                + RawContactsColumns.CONCRETE_SYNC3 + " AS " + RawContacts.SYNC3 + ","
1955                + RawContactsColumns.CONCRETE_SYNC4 + " AS " + RawContacts.SYNC4;
1956
1957        String baseContactColumns =
1958                Contacts.HAS_PHONE_NUMBER + ", "
1959                + Contacts.NAME_RAW_CONTACT_ID + ", "
1960                + Contacts.LOOKUP_KEY + ", "
1961                + Contacts.PHOTO_ID + ", "
1962                + Contacts.PHOTO_FILE_ID + ", "
1963                + "CAST(" + Clauses.CONTACT_VISIBLE + " AS INTEGER) AS "
1964                        + Contacts.IN_VISIBLE_GROUP + ", "
1965                + "CAST(" + Clauses.CONTACT_IN_DEFAULT_DIRECTORY + " AS INTEGER) AS "
1966                        + Contacts.IN_DEFAULT_DIRECTORY + ", "
1967                + ContactsColumns.LAST_STATUS_UPDATE_ID + ", "
1968                + ContactsColumns.CONCRETE_CONTACT_LAST_UPDATED_TIMESTAMP;
1969
1970        String contactOptionColumns =
1971                ContactsColumns.CONCRETE_CUSTOM_RINGTONE
1972                        + " AS " + RawContacts.CUSTOM_RINGTONE + ","
1973                + ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL
1974                        + " AS " + RawContacts.SEND_TO_VOICEMAIL + ","
1975                + ContactsColumns.CONCRETE_LAST_TIME_CONTACTED
1976                        + " AS " + RawContacts.LAST_TIME_CONTACTED + ","
1977                + ContactsColumns.CONCRETE_TIMES_CONTACTED
1978                        + " AS " + RawContacts.TIMES_CONTACTED + ","
1979                + ContactsColumns.CONCRETE_STARRED
1980                        + " AS " + RawContacts.STARRED + ","
1981                + ContactsColumns.CONCRETE_PINNED
1982                        + " AS " + RawContacts.PINNED;
1983
1984        String contactNameColumns =
1985                "name_raw_contact." + RawContacts.DISPLAY_NAME_SOURCE
1986                        + " AS " + Contacts.DISPLAY_NAME_SOURCE + ", "
1987                + "name_raw_contact." + RawContacts.DISPLAY_NAME_PRIMARY
1988                        + " AS " + Contacts.DISPLAY_NAME_PRIMARY + ", "
1989                + "name_raw_contact." + RawContacts.DISPLAY_NAME_ALTERNATIVE
1990                        + " AS " + Contacts.DISPLAY_NAME_ALTERNATIVE + ", "
1991                + "name_raw_contact." + RawContacts.PHONETIC_NAME
1992                        + " AS " + Contacts.PHONETIC_NAME + ", "
1993                + "name_raw_contact." + RawContacts.PHONETIC_NAME_STYLE
1994                        + " AS " + Contacts.PHONETIC_NAME_STYLE + ", "
1995                + "name_raw_contact." + RawContacts.SORT_KEY_PRIMARY
1996                        + " AS " + Contacts.SORT_KEY_PRIMARY + ", "
1997                + "name_raw_contact." + RawContactsColumns.PHONEBOOK_LABEL_PRIMARY
1998                        + " AS " + ContactsColumns.PHONEBOOK_LABEL_PRIMARY + ", "
1999                + "name_raw_contact." + RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY
2000                        + " AS " + ContactsColumns.PHONEBOOK_BUCKET_PRIMARY + ", "
2001                + "name_raw_contact." + RawContacts.SORT_KEY_ALTERNATIVE
2002                        + " AS " + Contacts.SORT_KEY_ALTERNATIVE + ", "
2003                + "name_raw_contact." + RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE
2004                        + " AS " + ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + ", "
2005                + "name_raw_contact." + RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE
2006                        + " AS " + ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE;
2007
2008        String dataSelect = "SELECT "
2009                + DataColumns.CONCRETE_ID + " AS " + Data._ID + ","
2010                + Data.HASH_ID + ", "
2011                + Data.RAW_CONTACT_ID + ", "
2012                + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", "
2013                + syncColumns + ", "
2014                + dataColumns + ", "
2015                + contactOptionColumns + ", "
2016                + contactNameColumns + ", "
2017                + baseContactColumns + ", "
2018                + buildDisplayPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
2019                        Contacts.PHOTO_URI) + ", "
2020                + buildThumbnailPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
2021                        Contacts.PHOTO_THUMBNAIL_URI) + ", "
2022                + dbForProfile() + " AS " + RawContacts.RAW_CONTACT_IS_USER_PROFILE + ", "
2023                + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID
2024                + " FROM " + Tables.DATA
2025                + " JOIN " + Tables.MIMETYPES + " ON ("
2026                +   DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")"
2027                + " JOIN " + Tables.RAW_CONTACTS + " ON ("
2028                +   DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")"
2029                + " JOIN " + Tables.ACCOUNTS + " ON ("
2030                +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
2031                    + ")"
2032                + " JOIN " + Tables.CONTACTS + " ON ("
2033                +   RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")"
2034                + " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON("
2035                +   Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")"
2036                + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON ("
2037                +   DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")"
2038                + " LEFT OUTER JOIN " + Tables.GROUPS + " ON ("
2039                +   MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
2040                +   "' AND " + GroupsColumns.CONCRETE_ID + "="
2041                        + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")";
2042
2043        db.execSQL("CREATE VIEW " + Views.DATA + " AS " + dataSelect);
2044
2045        String rawContactOptionColumns =
2046                RawContacts.CUSTOM_RINGTONE + ","
2047                + RawContacts.SEND_TO_VOICEMAIL + ","
2048                + RawContacts.LAST_TIME_CONTACTED + ","
2049                + RawContacts.TIMES_CONTACTED + ","
2050                + RawContacts.STARRED + ","
2051                + RawContacts.PINNED;
2052
2053        String rawContactsSelect = "SELECT "
2054                + RawContactsColumns.CONCRETE_ID + " AS " + RawContacts._ID + ","
2055                + RawContacts.CONTACT_ID + ", "
2056                + RawContacts.AGGREGATION_MODE + ", "
2057                + RawContacts.RAW_CONTACT_IS_READ_ONLY + ", "
2058                + RawContacts.DELETED + ", "
2059                + RawContactsColumns.CONCRETE_METADATA_DIRTY + ", "
2060                + RawContacts.DISPLAY_NAME_SOURCE  + ", "
2061                + RawContacts.DISPLAY_NAME_PRIMARY  + ", "
2062                + RawContacts.DISPLAY_NAME_ALTERNATIVE  + ", "
2063                + RawContacts.PHONETIC_NAME  + ", "
2064                + RawContacts.PHONETIC_NAME_STYLE  + ", "
2065                + RawContacts.SORT_KEY_PRIMARY  + ", "
2066                + RawContactsColumns.PHONEBOOK_LABEL_PRIMARY  + ", "
2067                + RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY  + ", "
2068                + RawContacts.SORT_KEY_ALTERNATIVE + ", "
2069                + RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE  + ", "
2070                + RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE  + ", "
2071                + dbForProfile() + " AS " + RawContacts.RAW_CONTACT_IS_USER_PROFILE + ", "
2072                + rawContactOptionColumns + ", "
2073                + syncColumns
2074                + " FROM " + Tables.RAW_CONTACTS
2075                + " JOIN " + Tables.ACCOUNTS + " ON ("
2076                +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
2077                    + ")";
2078
2079        db.execSQL("CREATE VIEW " + Views.RAW_CONTACTS + " AS " + rawContactsSelect);
2080
2081        String contactsColumns =
2082                ContactsColumns.CONCRETE_CUSTOM_RINGTONE
2083                        + " AS " + Contacts.CUSTOM_RINGTONE + ", "
2084                + contactNameColumns + ", "
2085                + baseContactColumns + ", "
2086                + ContactsColumns.CONCRETE_LAST_TIME_CONTACTED
2087                        + " AS " + Contacts.LAST_TIME_CONTACTED + ", "
2088                + ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL
2089                        + " AS " + Contacts.SEND_TO_VOICEMAIL + ", "
2090                + ContactsColumns.CONCRETE_STARRED
2091                        + " AS " + Contacts.STARRED + ", "
2092                + ContactsColumns.CONCRETE_PINNED
2093                + " AS " + Contacts.PINNED + ", "
2094                + ContactsColumns.CONCRETE_TIMES_CONTACTED
2095                        + " AS " + Contacts.TIMES_CONTACTED;
2096
2097        String contactsSelect = "SELECT "
2098                + ContactsColumns.CONCRETE_ID + " AS " + Contacts._ID + ","
2099                + contactsColumns + ", "
2100                + buildDisplayPhotoUriAlias(ContactsColumns.CONCRETE_ID, Contacts.PHOTO_URI) + ", "
2101                + buildThumbnailPhotoUriAlias(ContactsColumns.CONCRETE_ID,
2102                        Contacts.PHOTO_THUMBNAIL_URI) + ", "
2103                + dbForProfile() + " AS " + Contacts.IS_USER_PROFILE
2104                + " FROM " + Tables.CONTACTS
2105                + " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON("
2106                +   Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")";
2107
2108        db.execSQL("CREATE VIEW " + Views.CONTACTS + " AS " + contactsSelect);
2109
2110        String rawEntitiesSelect = "SELECT "
2111                + RawContacts.CONTACT_ID + ", "
2112                + RawContactsColumns.CONCRETE_DELETED + " AS " + RawContacts.DELETED + ","
2113                + RawContactsColumns.CONCRETE_METADATA_DIRTY + ", "
2114                + dataColumns + ", "
2115                + syncColumns + ", "
2116                + Data.SYNC1 + ", "
2117                + Data.SYNC2 + ", "
2118                + Data.SYNC3 + ", "
2119                + Data.SYNC4 + ", "
2120                + RawContactsColumns.CONCRETE_ID + " AS " + RawContacts._ID + ", "
2121                + DataColumns.CONCRETE_ID + " AS " + RawContacts.Entity.DATA_ID + ","
2122                + RawContactsColumns.CONCRETE_STARRED + " AS " + RawContacts.STARRED + ","
2123                + dbForProfile() + " AS " + RawContacts.RAW_CONTACT_IS_USER_PROFILE + ","
2124                + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID
2125                + " FROM " + Tables.RAW_CONTACTS
2126                + " JOIN " + Tables.ACCOUNTS + " ON ("
2127                +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
2128                    + ")"
2129                + " LEFT OUTER JOIN " + Tables.DATA + " ON ("
2130                +   DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")"
2131                + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON ("
2132                +   DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")"
2133                + " LEFT OUTER JOIN " + Tables.MIMETYPES + " ON ("
2134                +   DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")"
2135                + " LEFT OUTER JOIN " + Tables.GROUPS + " ON ("
2136                +   MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
2137                +   "' AND " + GroupsColumns.CONCRETE_ID + "="
2138                + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")";
2139
2140        db.execSQL("CREATE VIEW " + Views.RAW_ENTITIES + " AS "
2141                + rawEntitiesSelect);
2142
2143        String entitiesSelect = "SELECT "
2144                + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + Contacts._ID + ", "
2145                + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", "
2146                + RawContactsColumns.CONCRETE_DELETED + " AS " + RawContacts.DELETED + ","
2147                + RawContactsColumns.CONCRETE_METADATA_DIRTY + ", "
2148                + dataColumns + ", "
2149                + syncColumns + ", "
2150                + contactsColumns + ", "
2151                + buildDisplayPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
2152                        Contacts.PHOTO_URI) + ", "
2153                + buildThumbnailPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
2154                        Contacts.PHOTO_THUMBNAIL_URI) + ", "
2155                + dbForProfile() + " AS " + Contacts.IS_USER_PROFILE + ", "
2156                + Data.SYNC1 + ", "
2157                + Data.SYNC2 + ", "
2158                + Data.SYNC3 + ", "
2159                + Data.SYNC4 + ", "
2160                + RawContactsColumns.CONCRETE_ID + " AS " + Contacts.Entity.RAW_CONTACT_ID + ", "
2161                + DataColumns.CONCRETE_ID + " AS " + Contacts.Entity.DATA_ID + ","
2162                + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID
2163                + " FROM " + Tables.RAW_CONTACTS
2164                + " JOIN " + Tables.ACCOUNTS + " ON ("
2165                +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
2166                    + ")"
2167                + " JOIN " + Tables.CONTACTS + " ON ("
2168                +   RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")"
2169                + " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON("
2170                +   Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")"
2171                + " LEFT OUTER JOIN " + Tables.DATA + " ON ("
2172                +   DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")"
2173                + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON ("
2174                +   DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")"
2175                + " LEFT OUTER JOIN " + Tables.MIMETYPES + " ON ("
2176                +   DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")"
2177                + " LEFT OUTER JOIN " + Tables.GROUPS + " ON ("
2178                +   MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
2179                +   "' AND " + GroupsColumns.CONCRETE_ID + "="
2180                + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")";
2181
2182        db.execSQL("CREATE VIEW " + Views.ENTITIES + " AS "
2183                + entitiesSelect);
2184
2185        String dataUsageStatSelect = "SELECT "
2186                + DataUsageStatColumns.CONCRETE_ID + " AS " + DataUsageStatColumns._ID + ", "
2187                + DataUsageStatColumns.DATA_ID + ", "
2188                + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", "
2189                + MimetypesColumns.CONCRETE_MIMETYPE + " AS " + Data.MIMETYPE + ", "
2190                + DataUsageStatColumns.USAGE_TYPE_INT + ", "
2191                + DataUsageStatColumns.TIMES_USED + ", "
2192                + DataUsageStatColumns.LAST_TIME_USED
2193                + " FROM " + Tables.DATA_USAGE_STAT
2194                + " JOIN " + Tables.DATA + " ON ("
2195                +   DataColumns.CONCRETE_ID + "=" + DataUsageStatColumns.CONCRETE_DATA_ID + ")"
2196                + " JOIN " + Tables.RAW_CONTACTS + " ON ("
2197                +   RawContactsColumns.CONCRETE_ID + "=" + DataColumns.CONCRETE_RAW_CONTACT_ID
2198                    + " )"
2199                + " JOIN " + Tables.MIMETYPES + " ON ("
2200                +   MimetypesColumns.CONCRETE_ID + "=" + DataColumns.CONCRETE_MIMETYPE_ID + ")";
2201
2202        db.execSQL("CREATE VIEW " + Views.DATA_USAGE_STAT + " AS " + dataUsageStatSelect);
2203
2204        String streamItemSelect = "SELECT " +
2205                StreamItemsColumns.CONCRETE_ID + ", " +
2206                ContactsColumns.CONCRETE_ID + " AS " + StreamItems.CONTACT_ID + ", " +
2207                ContactsColumns.CONCRETE_LOOKUP_KEY +
2208                        " AS " + StreamItems.CONTACT_LOOKUP_KEY + ", " +
2209                AccountsColumns.CONCRETE_ACCOUNT_NAME + ", " +
2210                AccountsColumns.CONCRETE_ACCOUNT_TYPE + ", " +
2211                AccountsColumns.CONCRETE_DATA_SET + ", " +
2212                StreamItemsColumns.CONCRETE_RAW_CONTACT_ID +
2213                        " as " + StreamItems.RAW_CONTACT_ID + ", " +
2214                RawContactsColumns.CONCRETE_SOURCE_ID +
2215                        " as " + StreamItems.RAW_CONTACT_SOURCE_ID + ", " +
2216                StreamItemsColumns.CONCRETE_PACKAGE + ", " +
2217                StreamItemsColumns.CONCRETE_ICON + ", " +
2218                StreamItemsColumns.CONCRETE_LABEL + ", " +
2219                StreamItemsColumns.CONCRETE_TEXT + ", " +
2220                StreamItemsColumns.CONCRETE_TIMESTAMP + ", " +
2221                StreamItemsColumns.CONCRETE_COMMENTS + ", " +
2222                StreamItemsColumns.CONCRETE_SYNC1 + ", " +
2223                StreamItemsColumns.CONCRETE_SYNC2 + ", " +
2224                StreamItemsColumns.CONCRETE_SYNC3 + ", " +
2225                StreamItemsColumns.CONCRETE_SYNC4 +
2226                " FROM " + Tables.STREAM_ITEMS
2227                + " JOIN " + Tables.RAW_CONTACTS + " ON ("
2228                + StreamItemsColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID
2229                    + ")"
2230                + " JOIN " + Tables.ACCOUNTS + " ON ("
2231                +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
2232                    + ")"
2233                + " JOIN " + Tables.CONTACTS + " ON ("
2234                + RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")";
2235
2236        db.execSQL("CREATE VIEW " + Views.STREAM_ITEMS + " AS " + streamItemSelect);
2237
2238        String metadataSyncSelect = "SELECT " +
2239                MetadataSyncColumns.CONCRETE_ID + ", " +
2240                MetadataSync.RAW_CONTACT_BACKUP_ID + ", " +
2241                AccountsColumns.ACCOUNT_NAME + ", " +
2242                AccountsColumns.ACCOUNT_TYPE + ", " +
2243                AccountsColumns.DATA_SET + ", " +
2244                MetadataSync.DATA + ", " +
2245                MetadataSync.DELETED +
2246                " FROM " + Tables.METADATA_SYNC
2247                + " JOIN " + Tables.ACCOUNTS + " ON ("
2248                +   MetadataSyncColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
2249                + ")";
2250
2251        db.execSQL("CREATE VIEW " + Views.METADATA_SYNC + " AS " + metadataSyncSelect);
2252
2253        String metadataSyncStateSelect = "SELECT " +
2254                MetadataSyncStateColumns.CONCRETE_ID + ", " +
2255                AccountsColumns.ACCOUNT_NAME + ", " +
2256                AccountsColumns.ACCOUNT_TYPE + ", " +
2257                AccountsColumns.DATA_SET + ", " +
2258                MetadataSyncState.STATE +
2259                " FROM " + Tables.METADATA_SYNC_STATE
2260                + " JOIN " + Tables.ACCOUNTS + " ON ("
2261                +   MetadataSyncStateColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
2262                + ")";
2263
2264        db.execSQL("CREATE VIEW " + Views.METADATA_SYNC_STATE + " AS " + metadataSyncStateSelect);
2265    }
2266
2267    private static String buildDisplayPhotoUriAlias(String contactIdColumn, String alias) {
2268        return "(CASE WHEN " + Contacts.PHOTO_FILE_ID + " IS NULL THEN (CASE WHEN "
2269                + Contacts.PHOTO_ID + " IS NULL"
2270                + " OR " + Contacts.PHOTO_ID + "=0"
2271                + " THEN NULL"
2272                + " ELSE '" + Contacts.CONTENT_URI + "/'||"
2273                        + contactIdColumn + "|| '/" + Photo.CONTENT_DIRECTORY + "'"
2274                + " END) ELSE '" + DisplayPhoto.CONTENT_URI + "/'||"
2275                        + Contacts.PHOTO_FILE_ID + " END)"
2276                + " AS " + alias;
2277    }
2278
2279    private static String buildThumbnailPhotoUriAlias(String contactIdColumn, String alias) {
2280        return "(CASE WHEN "
2281                + Contacts.PHOTO_ID + " IS NULL"
2282                + " OR " + Contacts.PHOTO_ID + "=0"
2283                + " THEN NULL"
2284                + " ELSE '" + Contacts.CONTENT_URI + "/'||"
2285                        + contactIdColumn + "|| '/" + Photo.CONTENT_DIRECTORY + "'"
2286                + " END)"
2287                + " AS " + alias;
2288    }
2289
2290    /**
2291     * Returns the value to be returned when querying the column indicating that the contact
2292     * or raw contact belongs to the user's personal profile.  Overridden in the profile
2293     * DB helper subclass.
2294     */
2295    protected int dbForProfile() {
2296        return 0;
2297    }
2298
2299    private void createGroupsView(SQLiteDatabase db) {
2300        db.execSQL("DROP VIEW IF EXISTS " + Views.GROUPS + ";");
2301
2302        String groupsColumns =
2303                GroupsColumns.CONCRETE_ACCOUNT_ID + " AS " + GroupsColumns.ACCOUNT_ID + ","
2304                + AccountsColumns.CONCRETE_ACCOUNT_NAME + " AS " + Groups.ACCOUNT_NAME + ","
2305                + AccountsColumns.CONCRETE_ACCOUNT_TYPE + " AS " + Groups.ACCOUNT_TYPE + ","
2306                + AccountsColumns.CONCRETE_DATA_SET + " AS " + Groups.DATA_SET + ","
2307                + "(CASE WHEN " + AccountsColumns.CONCRETE_DATA_SET
2308                    + " IS NULL THEN " + AccountsColumns.CONCRETE_ACCOUNT_TYPE
2309                    + " ELSE " + AccountsColumns.CONCRETE_ACCOUNT_TYPE
2310                        + "||'/'||" + AccountsColumns.CONCRETE_DATA_SET + " END) AS "
2311                            + Groups.ACCOUNT_TYPE_AND_DATA_SET + ","
2312                + Groups.SOURCE_ID + ","
2313                + Groups.VERSION + ","
2314                + Groups.DIRTY + ","
2315                + Groups.TITLE + ","
2316                + Groups.TITLE_RES + ","
2317                + Groups.NOTES + ","
2318                + Groups.SYSTEM_ID + ","
2319                + Groups.DELETED + ","
2320                + Groups.GROUP_VISIBLE + ","
2321                + Groups.SHOULD_SYNC + ","
2322                + Groups.AUTO_ADD + ","
2323                + Groups.FAVORITES + ","
2324                + Groups.GROUP_IS_READ_ONLY + ","
2325                + Groups.SYNC1 + ","
2326                + Groups.SYNC2 + ","
2327                + Groups.SYNC3 + ","
2328                + Groups.SYNC4 + ","
2329                + PackagesColumns.PACKAGE + " AS " + Groups.RES_PACKAGE;
2330
2331        String groupsSelect = "SELECT "
2332                + GroupsColumns.CONCRETE_ID + " AS " + Groups._ID + ","
2333                + groupsColumns
2334                + " FROM " + Tables.GROUPS
2335                + " JOIN " + Tables.ACCOUNTS + " ON ("
2336                    + GroupsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID + ")"
2337                + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON ("
2338                    + GroupsColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")";
2339
2340        db.execSQL("CREATE VIEW " + Views.GROUPS + " AS " + groupsSelect);
2341    }
2342
2343    @Override
2344    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
2345        Log.i(TAG, "ContactsProvider cannot proceed because downgrading your database is not " +
2346                "supported. To continue, please either re-upgrade to your previous Android " +
2347                "version, or clear all application data in Contacts Storage (this will result " +
2348                "in the loss of all local contacts that are not synced). To avoid data loss, " +
2349                "your contacts database will not be wiped automatically.");
2350        super.onDowngrade(db, oldVersion, newVersion);
2351    }
2352
2353    @Override
2354    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
2355        if (oldVersion < 99) {
2356            Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion
2357                    + ", data will be lost!");
2358
2359            db.execSQL("DROP TABLE IF EXISTS " + Tables.CONTACTS + ";");
2360            db.execSQL("DROP TABLE IF EXISTS " + Tables.RAW_CONTACTS + ";");
2361            db.execSQL("DROP TABLE IF EXISTS " + Tables.PACKAGES + ";");
2362            db.execSQL("DROP TABLE IF EXISTS " + Tables.MIMETYPES + ";");
2363            db.execSQL("DROP TABLE IF EXISTS " + Tables.DATA + ";");
2364            db.execSQL("DROP TABLE IF EXISTS " + Tables.PHONE_LOOKUP + ";");
2365            db.execSQL("DROP TABLE IF EXISTS " + Tables.NAME_LOOKUP + ";");
2366            db.execSQL("DROP TABLE IF EXISTS " + Tables.NICKNAME_LOOKUP + ";");
2367            db.execSQL("DROP TABLE IF EXISTS " + Tables.GROUPS + ";");
2368            db.execSQL("DROP TABLE IF EXISTS activities;");
2369            db.execSQL("DROP TABLE IF EXISTS " + Tables.CALLS + ";");
2370            db.execSQL("DROP TABLE IF EXISTS " + Tables.SETTINGS + ";");
2371            db.execSQL("DROP TABLE IF EXISTS " + Tables.STATUS_UPDATES + ";");
2372
2373            // TODO: we should not be dropping agg_exceptions and contact_options. In case that
2374            // table's schema changes, we should try to preserve the data, because it was entered
2375            // by the user and has never been synched to the server.
2376            db.execSQL("DROP TABLE IF EXISTS " + Tables.AGGREGATION_EXCEPTIONS + ";");
2377
2378            onCreate(db);
2379            return;
2380        }
2381
2382        Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion);
2383
2384        boolean upgradeViewsAndTriggers = false;
2385        boolean upgradeNameLookup = false;
2386        boolean upgradeLegacyApiSupport = false;
2387        boolean upgradeSearchIndex = false;
2388        boolean rescanDirectories = false;
2389        boolean rebuildSqliteStats = false;
2390        boolean upgradeLocaleSpecificData = false;
2391
2392        if (oldVersion == 99) {
2393            upgradeViewsAndTriggers = true;
2394            oldVersion++;
2395        }
2396
2397        if (oldVersion == 100) {
2398            db.execSQL("CREATE INDEX IF NOT EXISTS mimetypes_mimetype_index ON "
2399                    + Tables.MIMETYPES + " ("
2400                            + MimetypesColumns.MIMETYPE + ","
2401                            + MimetypesColumns._ID + ");");
2402            updateIndexStats(db, Tables.MIMETYPES,
2403                    "mimetypes_mimetype_index", "50 1 1");
2404
2405            upgradeViewsAndTriggers = true;
2406            oldVersion++;
2407        }
2408
2409        if (oldVersion == 101) {
2410            upgradeViewsAndTriggers = true;
2411            oldVersion++;
2412        }
2413
2414        if (oldVersion == 102) {
2415            upgradeViewsAndTriggers = true;
2416            oldVersion++;
2417        }
2418
2419        if (oldVersion == 103) {
2420            upgradeViewsAndTriggers = true;
2421            oldVersion++;
2422        }
2423
2424        if (oldVersion == 104 || oldVersion == 201) {
2425            LegacyApiSupport.createSettingsTable(db);
2426            upgradeViewsAndTriggers = true;
2427            oldVersion++;
2428        }
2429
2430        if (oldVersion == 105) {
2431            upgradeToVersion202(db);
2432            upgradeNameLookup = true;
2433            oldVersion = 202;
2434        }
2435
2436        if (oldVersion == 202) {
2437            upgradeToVersion203(db);
2438            upgradeViewsAndTriggers = true;
2439            oldVersion++;
2440        }
2441
2442        if (oldVersion == 203) {
2443            upgradeViewsAndTriggers = true;
2444            oldVersion++;
2445        }
2446
2447        if (oldVersion == 204) {
2448            upgradeToVersion205(db);
2449            upgradeViewsAndTriggers = true;
2450            oldVersion++;
2451        }
2452
2453        if (oldVersion == 205) {
2454            upgrateToVersion206(db);
2455            upgradeViewsAndTriggers = true;
2456            oldVersion++;
2457        }
2458
2459        if (oldVersion == 206) {
2460            // Fix for the bug where name lookup records for organizations would get removed by
2461            // unrelated updates of the data rows. No longer needed.
2462            oldVersion = 300;
2463        }
2464
2465        if (oldVersion == 300) {
2466            upgradeViewsAndTriggers = true;
2467            oldVersion = 301;
2468        }
2469
2470        if (oldVersion == 301) {
2471            upgradeViewsAndTriggers = true;
2472            oldVersion = 302;
2473        }
2474
2475        if (oldVersion == 302) {
2476            upgradeEmailToVersion303(db);
2477            upgradeNicknameToVersion303(db);
2478            oldVersion = 303;
2479        }
2480
2481        if (oldVersion == 303) {
2482            upgradeToVersion304(db);
2483            oldVersion = 304;
2484        }
2485
2486        if (oldVersion == 304) {
2487            upgradeNameLookup = true;
2488            oldVersion = 305;
2489        }
2490
2491        if (oldVersion == 305) {
2492            upgradeToVersion306(db);
2493            oldVersion = 306;
2494        }
2495
2496        if (oldVersion == 306) {
2497            upgradeToVersion307(db);
2498            oldVersion = 307;
2499        }
2500
2501        if (oldVersion == 307) {
2502            upgradeToVersion308(db);
2503            oldVersion = 308;
2504        }
2505
2506        // Gingerbread upgrades.
2507        if (oldVersion < 350) {
2508            upgradeViewsAndTriggers = true;
2509            oldVersion = 351;
2510        }
2511
2512        if (oldVersion == 351) {
2513            upgradeNameLookup = true;
2514            oldVersion = 352;
2515        }
2516
2517        if (oldVersion == 352) {
2518            upgradeToVersion353(db);
2519            oldVersion = 353;
2520        }
2521
2522        // Honeycomb upgrades.
2523        if (oldVersion < 400) {
2524            upgradeViewsAndTriggers = true;
2525            upgradeToVersion400(db);
2526            oldVersion = 400;
2527        }
2528
2529        if (oldVersion == 400) {
2530            upgradeViewsAndTriggers = true;
2531            upgradeToVersion401(db);
2532            oldVersion = 401;
2533        }
2534
2535        if (oldVersion == 401) {
2536            upgradeToVersion402(db);
2537            oldVersion = 402;
2538        }
2539
2540        if (oldVersion == 402) {
2541            upgradeViewsAndTriggers = true;
2542            upgradeToVersion403(db);
2543            oldVersion = 403;
2544        }
2545
2546        if (oldVersion == 403) {
2547            upgradeViewsAndTriggers = true;
2548            oldVersion = 404;
2549        }
2550
2551        if (oldVersion == 404) {
2552            upgradeViewsAndTriggers = true;
2553            upgradeToVersion405(db);
2554            oldVersion = 405;
2555        }
2556
2557        if (oldVersion == 405) {
2558            upgradeViewsAndTriggers = true;
2559            upgradeToVersion406(db);
2560            oldVersion = 406;
2561        }
2562
2563        if (oldVersion == 406) {
2564            upgradeViewsAndTriggers = true;
2565            oldVersion = 407;
2566        }
2567
2568        if (oldVersion == 407) {
2569            oldVersion = 408;  // Obsolete.
2570        }
2571
2572        if (oldVersion == 408) {
2573            upgradeViewsAndTriggers = true;
2574            upgradeToVersion409(db);
2575            oldVersion = 409;
2576        }
2577
2578        if (oldVersion == 409) {
2579            upgradeViewsAndTriggers = true;
2580            oldVersion = 410;
2581        }
2582
2583        if (oldVersion == 410) {
2584            upgradeToVersion411(db);
2585            oldVersion = 411;
2586        }
2587
2588        if (oldVersion == 411) {
2589            // Same upgrade as 353, only on Honeycomb devices.
2590            upgradeToVersion353(db);
2591            oldVersion = 412;
2592        }
2593
2594        if (oldVersion == 412) {
2595            upgradeToVersion413(db);
2596            oldVersion = 413;
2597        }
2598
2599        if (oldVersion == 413) {
2600            upgradeNameLookup = true;
2601            oldVersion = 414;
2602        }
2603
2604        if (oldVersion == 414) {
2605            upgradeToVersion415(db);
2606            upgradeViewsAndTriggers = true;
2607            oldVersion = 415;
2608        }
2609
2610        if (oldVersion == 415) {
2611            upgradeToVersion416(db);
2612            oldVersion = 416;
2613        }
2614
2615        if (oldVersion == 416) {
2616            upgradeLegacyApiSupport = true;
2617            oldVersion = 417;
2618        }
2619
2620        // Honeycomb-MR1 upgrades.
2621        if (oldVersion < 500) {
2622            upgradeSearchIndex = true;
2623        }
2624
2625        if (oldVersion < 501) {
2626            upgradeSearchIndex = true;
2627            upgradeToVersion501(db);
2628            oldVersion = 501;
2629        }
2630
2631        if (oldVersion < 502) {
2632            upgradeSearchIndex = true;
2633            upgradeToVersion502(db);
2634            oldVersion = 502;
2635        }
2636
2637        if (oldVersion < 503) {
2638            upgradeSearchIndex = true;
2639            oldVersion = 503;
2640        }
2641
2642        if (oldVersion < 504) {
2643            upgradeToVersion504(db);
2644            oldVersion = 504;
2645        }
2646
2647        if (oldVersion < 600) {
2648            // This change used to add the profile raw contact ID to the Accounts table.  That
2649            // column is no longer needed (as of version 614) since the profile records are stored in
2650            // a separate copy of the database for security reasons.  So this change is now a no-op.
2651            upgradeViewsAndTriggers = true;
2652            oldVersion = 600;
2653        }
2654
2655        if (oldVersion < 601) {
2656            upgradeToVersion601(db);
2657            oldVersion = 601;
2658        }
2659
2660        if (oldVersion < 602) {
2661            upgradeToVersion602(db);
2662            oldVersion = 602;
2663        }
2664
2665        if (oldVersion < 603) {
2666            upgradeViewsAndTriggers = true;
2667            oldVersion = 603;
2668        }
2669
2670        if (oldVersion < 604) {
2671            upgradeToVersion604(db);
2672            oldVersion = 604;
2673        }
2674
2675        if (oldVersion < 605) {
2676            upgradeViewsAndTriggers = true;
2677            // This version used to create the stream item and stream item photos tables, but
2678            // a newer version of those tables is created in version 609 below. So omitting the
2679            // creation in this upgrade step to avoid a create->drop->create.
2680            oldVersion = 605;
2681        }
2682
2683        if (oldVersion < 606) {
2684            upgradeViewsAndTriggers = true;
2685            upgradeLegacyApiSupport = true;
2686            upgradeToVersion606(db);
2687            oldVersion = 606;
2688        }
2689
2690        if (oldVersion < 607) {
2691            upgradeViewsAndTriggers = true;
2692            // We added "action" and "action_uri" to groups here, but realized this was not a smart
2693            // move. This upgrade step has been removed (all dogfood phones that executed this step
2694            // will have those columns, but that shouldn't hurt. Unfortunately, SQLite makes it
2695            // hard to remove columns).
2696            oldVersion = 607;
2697        }
2698
2699        if (oldVersion < 608) {
2700            upgradeViewsAndTriggers = true;
2701            upgradeToVersion608(db);
2702            oldVersion = 608;
2703        }
2704
2705        if (oldVersion < 609) {
2706            // This version used to create the stream item and stream item photos tables, but a
2707            // newer version of those tables is created in version 613 below.  So omitting the
2708            // creation in this upgrade step to avoid a create->drop->create.
2709            oldVersion = 609;
2710        }
2711
2712        if (oldVersion < 610) {
2713            upgradeToVersion610(db);
2714            oldVersion = 610;
2715        }
2716
2717        if (oldVersion < 611) {
2718            upgradeViewsAndTriggers = true;
2719            upgradeToVersion611(db);
2720            oldVersion = 611;
2721        }
2722
2723        if (oldVersion < 612) {
2724            upgradeViewsAndTriggers = true;
2725            upgradeToVersion612(db);
2726            oldVersion = 612;
2727        }
2728
2729        if (oldVersion < 613) {
2730            upgradeToVersion613(db);
2731            oldVersion = 613;
2732        }
2733
2734        if (oldVersion < 614) {
2735            // This creates the "view_stream_items" view.
2736            upgradeViewsAndTriggers = true;
2737            oldVersion = 614;
2738        }
2739
2740        if (oldVersion < 615) {
2741            upgradeToVersion615(db);
2742            oldVersion = 615;
2743        }
2744
2745        if (oldVersion < 616) {
2746            // This updates the "view_stream_items" view.
2747            upgradeViewsAndTriggers = true;
2748            oldVersion = 616;
2749        }
2750
2751        if (oldVersion < 617) {
2752            // This version upgrade obsoleted the profile_raw_contact_id field of the Accounts
2753            // table, but we aren't removing the column because it is very little data (and not
2754            // referenced anymore).  We do need to upgrade the views to handle the simplified
2755            // per-database "is profile" columns.
2756            upgradeViewsAndTriggers = true;
2757            oldVersion = 617;
2758        }
2759
2760        if (oldVersion < 618) {
2761            upgradeToVersion618(db);
2762            oldVersion = 618;
2763        }
2764
2765        if (oldVersion < 619) {
2766            upgradeViewsAndTriggers = true;
2767            oldVersion = 619;
2768        }
2769
2770        if (oldVersion < 620) {
2771            upgradeViewsAndTriggers = true;
2772            oldVersion = 620;
2773        }
2774
2775        if (oldVersion < 621) {
2776            upgradeSearchIndex = true;
2777            oldVersion = 621;
2778        }
2779
2780        if (oldVersion < 622) {
2781            upgradeToVersion622(db);
2782            oldVersion = 622;
2783        }
2784
2785        if (oldVersion < 623) {
2786            // Change FTS to normalize names using collation key.
2787            upgradeSearchIndex = true;
2788            oldVersion = 623;
2789        }
2790
2791        if (oldVersion < 624) {
2792            // Upgraded the SQLite index stats.
2793            upgradeViewsAndTriggers = true;
2794            oldVersion = 624;
2795        }
2796
2797        if (oldVersion < 625) {
2798            // Fix for search for hyphenated names
2799            upgradeSearchIndex = true;
2800            oldVersion = 625;
2801        }
2802
2803        if (oldVersion < 626) {
2804            upgradeToVersion626(db);
2805            upgradeViewsAndTriggers = true;
2806            oldVersion = 626;
2807        }
2808
2809        if (oldVersion < 700) {
2810            rescanDirectories = true;
2811            oldVersion = 700;
2812        }
2813
2814        if (oldVersion < 701) {
2815            upgradeToVersion701(db);
2816            oldVersion = 701;
2817        }
2818
2819        if (oldVersion < 702) {
2820            upgradeToVersion702(db);
2821            oldVersion = 702;
2822        }
2823
2824        if (oldVersion < 703) {
2825            // Now names like "L'Image" will be searchable.
2826            upgradeSearchIndex = true;
2827            oldVersion = 703;
2828        }
2829
2830        if (oldVersion < 704) {
2831            db.execSQL("DROP TABLE IF EXISTS activities;");
2832            oldVersion = 704;
2833        }
2834
2835        if (oldVersion < 705) {
2836            // Before this version, we didn't rebuild the search index on locale changes, so
2837            // if the locale has changed after sync, the index contains gets stale.
2838            // To correct the issue we have to rebuild the index here.
2839            upgradeSearchIndex = true;
2840            oldVersion = 705;
2841        }
2842
2843        if (oldVersion < 706) {
2844            // Prior to this version, we didn't rebuild the stats table after drop operations,
2845            // which resulted in losing some of the rows from the stats table.
2846            rebuildSqliteStats = true;
2847            oldVersion = 706;
2848        }
2849
2850        if (oldVersion < 707) {
2851            upgradeToVersion707(db);
2852            upgradeViewsAndTriggers = true;
2853            oldVersion = 707;
2854        }
2855
2856        if (oldVersion < 708) {
2857            // Sort keys, phonebook labels and buckets, and search keys have
2858            // changed so force a rebuild.
2859            upgradeLocaleSpecificData = true;
2860            oldVersion = 708;
2861        }
2862        if (oldVersion < 709) {
2863            // Added secondary locale phonebook labels; changed Japanese
2864            // and Chinese sort keys.
2865            upgradeLocaleSpecificData = true;
2866            oldVersion = 709;
2867        }
2868
2869        if (oldVersion < 710) {
2870            upgradeToVersion710(db);
2871            upgradeViewsAndTriggers = true;
2872            oldVersion = 710;
2873        }
2874
2875        if (oldVersion < 800) {
2876            upgradeToVersion800(db);
2877            oldVersion = 800;
2878        }
2879
2880        if (oldVersion < 801) {
2881            setProperty(db, DbProperties.DATABASE_TIME_CREATED, String.valueOf(
2882                    System.currentTimeMillis()));
2883            oldVersion = 801;
2884        }
2885
2886        if (oldVersion < 802) {
2887            upgradeToVersion802(db);
2888            upgradeViewsAndTriggers = true;
2889            oldVersion = 802;
2890        }
2891
2892        if (oldVersion < 803) {
2893            // Rebuild the search index so that names, organizations and nicknames are
2894            // now indexed as names.
2895            upgradeSearchIndex = true;
2896            oldVersion = 803;
2897        }
2898
2899        if (oldVersion < 804) {
2900            // Reserved.
2901            oldVersion = 804;
2902        }
2903
2904        if (oldVersion < 900) {
2905            upgradeViewsAndTriggers = true;
2906            oldVersion = 900;
2907        }
2908
2909        if (oldVersion < 901) {
2910            // Rebuild the search index to fix any search index that was previously in a
2911            // broken state due to b/11059351
2912            upgradeSearchIndex = true;
2913            oldVersion = 901;
2914        }
2915
2916        if (oldVersion < 902) {
2917            upgradeToVersion902(db);
2918            oldVersion = 902;
2919        }
2920
2921        if (oldVersion < 903) {
2922            upgradeToVersion903(db);
2923            oldVersion = 903;
2924        }
2925
2926        if (oldVersion < 904) {
2927            upgradeToVersion904(db);
2928            oldVersion = 904;
2929        }
2930
2931        if (oldVersion < 905) {
2932            upgradeToVersion905(db);
2933            oldVersion = 905;
2934        }
2935
2936        if (oldVersion < 906) {
2937            upgradeToVersion906(db);
2938            oldVersion = 906;
2939        }
2940
2941        if (oldVersion < 907) {
2942            // Rebuild NAME_LOOKUP.
2943            upgradeNameLookup = true;
2944            oldVersion = 907;
2945        }
2946
2947        if (oldVersion < 908) {
2948            upgradeToVersion908(db);
2949            oldVersion = 908;
2950        }
2951
2952        if (oldVersion < 909) {
2953            upgradeToVersion909(db);
2954            oldVersion = 909;
2955        }
2956
2957        if (oldVersion < 910) {
2958            upgradeToVersion910(db);
2959            oldVersion = 910;
2960        }
2961        if (oldVersion < 1000) {
2962            upgradeToVersion1000(db);
2963            upgradeViewsAndTriggers = true;
2964            oldVersion = 1000;
2965        }
2966
2967        if (oldVersion < 1002) {
2968            rebuildSqliteStats = true;
2969            upgradeToVersion1002(db);
2970            oldVersion = 1002;
2971        }
2972
2973        if (oldVersion < 1003) {
2974            upgradeToVersion1003(db);
2975            oldVersion = 1003;
2976        }
2977
2978        if (oldVersion < 1004) {
2979            upgradeToVersion1004(db);
2980            oldVersion = 1004;
2981        }
2982
2983        if (oldVersion < 1005) {
2984            upgradeToVersion1005(db);
2985            oldVersion = 1005;
2986        }
2987
2988        if (oldVersion < 1006) {
2989            upgradeViewsAndTriggers = true;
2990            oldVersion = 1006;
2991        }
2992
2993        if (oldVersion < 1007) {
2994            upgradeToVersion1007(db);
2995            oldVersion = 1007;
2996        }
2997
2998        if (oldVersion < 1009) {
2999            upgradeToVersion1009(db);
3000            oldVersion = 1009;
3001        }
3002
3003        if (oldVersion < 1100) {
3004            upgradeToVersion1100(db);
3005            upgradeViewsAndTriggers = true;
3006            oldVersion = 1100;
3007        }
3008
3009        if (oldVersion < 1101) {
3010            upgradeToVersion1101(db);
3011            oldVersion = 1101;
3012        }
3013
3014        if (oldVersion < 1102) {
3015            // Version 1009 was added *after* 1100/1101.  For master devices
3016            // that have already been updated to 1101, we do it again.
3017            upgradeToVersion1009(db);
3018            oldVersion = 1102;
3019        }
3020
3021        if (oldVersion < 1103) {
3022            upgradeViewsAndTriggers = true;
3023            oldVersion = 1103;
3024        }
3025
3026        if (oldVersion < 1104) {
3027            upgradeToVersion1104(db);
3028            upgradeViewsAndTriggers = true;
3029            oldVersion = 1104;
3030        }
3031
3032        if (oldVersion < 1105) {
3033            upgradeToVersion1105(db);
3034            upgradeViewsAndTriggers = true;
3035            oldVersion = 1105;
3036        }
3037
3038        if (upgradeViewsAndTriggers) {
3039            createContactsViews(db);
3040            createGroupsView(db);
3041            createContactsTriggers(db);
3042            createContactsIndexes(db, false /* we build stats table later */);
3043            upgradeLegacyApiSupport = true;
3044            rebuildSqliteStats = true;
3045        }
3046
3047        if (upgradeLegacyApiSupport) {
3048            LegacyApiSupport.createViews(db);
3049        }
3050
3051        if (upgradeLocaleSpecificData) {
3052            upgradeLocaleData(db, false /* we build stats table later */);
3053            // Name lookups are rebuilt as part of the full locale rebuild
3054            upgradeNameLookup = false;
3055            upgradeSearchIndex = true;
3056            rebuildSqliteStats = true;
3057        }
3058
3059        if (upgradeNameLookup) {
3060            rebuildNameLookup(db, false /* we build stats table later */);
3061            rebuildSqliteStats = true;
3062        }
3063
3064        if (upgradeSearchIndex) {
3065            rebuildSearchIndex(db, false /* we build stats table later */);
3066            rebuildSqliteStats = true;
3067        }
3068
3069        if (rescanDirectories) {
3070            // Force the next ContactDirectoryManager.scanAllPackages() to rescan all packages.
3071            // (It's called from the BACKGROUND_TASK_UPDATE_ACCOUNTS background task.)
3072            setProperty(db, DbProperties.DIRECTORY_SCAN_COMPLETE, "0");
3073        }
3074
3075        if (rebuildSqliteStats) {
3076            updateSqliteStats(db);
3077        }
3078
3079        if (oldVersion != newVersion) {
3080            throw new IllegalStateException(
3081                    "error upgrading the database to version " + newVersion);
3082        }
3083    }
3084
3085    private void upgradeToVersion202(SQLiteDatabase db) {
3086        db.execSQL(
3087                "ALTER TABLE " + Tables.PHONE_LOOKUP +
3088                " ADD " + PhoneLookupColumns.MIN_MATCH + " TEXT;");
3089
3090        db.execSQL("CREATE INDEX phone_lookup_min_match_index ON " + Tables.PHONE_LOOKUP + " (" +
3091                PhoneLookupColumns.MIN_MATCH + "," +
3092                PhoneLookupColumns.RAW_CONTACT_ID + "," +
3093                PhoneLookupColumns.DATA_ID +
3094        ");");
3095
3096        updateIndexStats(db, Tables.PHONE_LOOKUP,
3097                "phone_lookup_min_match_index", "10000 2 2 1");
3098
3099        SQLiteStatement update = db.compileStatement(
3100                "UPDATE " + Tables.PHONE_LOOKUP +
3101                " SET " + PhoneLookupColumns.MIN_MATCH + "=?" +
3102                " WHERE " + PhoneLookupColumns.DATA_ID + "=?");
3103
3104        // Populate the new column
3105        Cursor c = db.query(Tables.PHONE_LOOKUP + " JOIN " + Tables.DATA +
3106                " ON (" + PhoneLookupColumns.DATA_ID + "=" + DataColumns.CONCRETE_ID + ")",
3107                new String[] {Data._ID, Phone.NUMBER}, null, null, null, null, null);
3108        try {
3109            while (c.moveToNext()) {
3110                long dataId = c.getLong(0);
3111                String number = c.getString(1);
3112                if (!TextUtils.isEmpty(number)) {
3113                    update.bindString(1, PhoneNumberUtils.toCallerIDMinMatch(number));
3114                    update.bindLong(2, dataId);
3115                    update.execute();
3116                }
3117            }
3118        } finally {
3119            c.close();
3120        }
3121    }
3122
3123    private void upgradeToVersion203(SQLiteDatabase db) {
3124        // Garbage-collect first. A bug in Eclair was sometimes leaving
3125        // raw_contacts in the database that no longer had contacts associated
3126        // with them.  To avoid failures during this database upgrade, drop
3127        // the orphaned raw_contacts.
3128        db.execSQL(
3129                "DELETE FROM raw_contacts" +
3130                " WHERE contact_id NOT NULL" +
3131                " AND contact_id NOT IN (SELECT _id FROM contacts)");
3132
3133        db.execSQL(
3134                "ALTER TABLE " + Tables.CONTACTS +
3135                " ADD " + Contacts.NAME_RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)");
3136        db.execSQL(
3137                "ALTER TABLE " + Tables.RAW_CONTACTS +
3138                " ADD contact_in_visible_group INTEGER NOT NULL DEFAULT 0");
3139
3140        // For each Contact, find the RawContact that contributed the display name
3141        db.execSQL(
3142                "UPDATE " + Tables.CONTACTS +
3143                        " SET " + Contacts.NAME_RAW_CONTACT_ID + "=(" +
3144                        " SELECT " + RawContacts._ID +
3145                        " FROM " + Tables.RAW_CONTACTS +
3146                        " WHERE " + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID +
3147                        " AND " + RawContactsColumns.CONCRETE_DISPLAY_NAME + "=" +
3148                        Tables.CONTACTS + "." + Contacts.DISPLAY_NAME +
3149                        " ORDER BY " + RawContacts._ID +
3150                        " LIMIT 1)"
3151        );
3152
3153        db.execSQL("CREATE INDEX contacts_name_raw_contact_id_index ON " + Tables.CONTACTS + " (" +
3154                Contacts.NAME_RAW_CONTACT_ID +
3155        ");");
3156
3157        // If for some unknown reason we missed some names, let's make sure there are
3158        // no contacts without a name, picking a raw contact "at random".
3159        db.execSQL(
3160                "UPDATE " + Tables.CONTACTS +
3161                " SET " + Contacts.NAME_RAW_CONTACT_ID + "=(" +
3162                        " SELECT " + RawContacts._ID +
3163                        " FROM " + Tables.RAW_CONTACTS +
3164                        " WHERE " + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID +
3165                        " ORDER BY " + RawContacts._ID +
3166                        " LIMIT 1)" +
3167                " WHERE " + Contacts.NAME_RAW_CONTACT_ID + " IS NULL"
3168        );
3169
3170        // Wipe out DISPLAY_NAME on the Contacts table as it is no longer in use.
3171        db.execSQL(
3172                "UPDATE " + Tables.CONTACTS +
3173                " SET " + Contacts.DISPLAY_NAME + "=NULL"
3174        );
3175
3176        // Copy the IN_VISIBLE_GROUP flag down to all raw contacts to allow
3177        // indexing on (display_name, in_visible_group)
3178        db.execSQL(
3179                "UPDATE " + Tables.RAW_CONTACTS +
3180                " SET contact_in_visible_group=(" +
3181                        "SELECT " + Contacts.IN_VISIBLE_GROUP +
3182                        " FROM " + Tables.CONTACTS +
3183                        " WHERE " + Contacts._ID + "=" + RawContacts.CONTACT_ID + ")" +
3184                " WHERE " + RawContacts.CONTACT_ID + " NOT NULL"
3185        );
3186
3187        db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" +
3188                "contact_in_visible_group" + "," +
3189                RawContactsColumns.DISPLAY_NAME + " COLLATE LOCALIZED ASC" +
3190        ");");
3191
3192        db.execSQL("DROP INDEX contacts_visible_index");
3193        db.execSQL("CREATE INDEX contacts_visible_index ON " + Tables.CONTACTS + " (" +
3194                Contacts.IN_VISIBLE_GROUP +
3195        ");");
3196    }
3197
3198    private void upgradeToVersion205(SQLiteDatabase db) {
3199        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
3200                + " ADD " + RawContacts.DISPLAY_NAME_ALTERNATIVE + " TEXT;");
3201        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
3202                + " ADD " + RawContacts.PHONETIC_NAME + " TEXT;");
3203        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
3204                + " ADD " + RawContacts.PHONETIC_NAME_STYLE + " INTEGER;");
3205        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
3206                + " ADD " + RawContacts.SORT_KEY_PRIMARY
3207                + " TEXT COLLATE " + ContactsProvider2.PHONEBOOK_COLLATOR_NAME + ";");
3208        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
3209                + " ADD " + RawContacts.SORT_KEY_ALTERNATIVE
3210                + " TEXT COLLATE " + ContactsProvider2.PHONEBOOK_COLLATOR_NAME + ";");
3211
3212        NameSplitter splitter = createNameSplitter();
3213
3214        SQLiteStatement rawContactUpdate = db.compileStatement(
3215                "UPDATE " + Tables.RAW_CONTACTS +
3216                " SET " +
3217                        RawContacts.DISPLAY_NAME_PRIMARY + "=?," +
3218                        RawContacts.DISPLAY_NAME_ALTERNATIVE + "=?," +
3219                        RawContacts.PHONETIC_NAME + "=?," +
3220                        RawContacts.PHONETIC_NAME_STYLE + "=?," +
3221                        RawContacts.SORT_KEY_PRIMARY + "=?," +
3222                        RawContacts.SORT_KEY_ALTERNATIVE + "=?" +
3223                " WHERE " + RawContacts._ID + "=?");
3224
3225        upgradeStructuredNamesToVersion205(db, rawContactUpdate, splitter);
3226        upgradeOrganizationsToVersion205(db, rawContactUpdate, splitter);
3227
3228        db.execSQL("DROP INDEX raw_contact_sort_key1_index");
3229        db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" +
3230                "contact_in_visible_group" + "," +
3231                RawContacts.SORT_KEY_PRIMARY +
3232        ");");
3233
3234        db.execSQL("CREATE INDEX raw_contact_sort_key2_index ON " + Tables.RAW_CONTACTS + " (" +
3235                "contact_in_visible_group" + "," +
3236                RawContacts.SORT_KEY_ALTERNATIVE +
3237        ");");
3238    }
3239
3240    private void upgradeStructuredNamesToVersion205(
3241            SQLiteDatabase db, SQLiteStatement rawContactUpdate, NameSplitter splitter) {
3242
3243        // Process structured names to detect the style of the full name and phonetic name.
3244        long mMimeType;
3245        try {
3246            mMimeType = DatabaseUtils.longForQuery(db,
3247                    "SELECT " + MimetypesColumns._ID +
3248                    " FROM " + Tables.MIMETYPES +
3249                    " WHERE " + MimetypesColumns.MIMETYPE
3250                            + "='" + StructuredName.CONTENT_ITEM_TYPE + "'", null);
3251
3252        } catch (SQLiteDoneException e) {
3253            // No structured names in the database.
3254            return;
3255        }
3256
3257        SQLiteStatement structuredNameUpdate = db.compileStatement(
3258                "UPDATE " + Tables.DATA +
3259                        " SET " +
3260                        StructuredName.FULL_NAME_STYLE + "=?," +
3261                        StructuredName.DISPLAY_NAME + "=?," +
3262                        StructuredName.PHONETIC_NAME_STYLE + "=?" +
3263                        " WHERE " + Data._ID + "=?");
3264
3265        NameSplitter.Name name = new NameSplitter.Name();
3266        Cursor cursor = db.query(StructName205Query.TABLE,
3267                StructName205Query.COLUMNS,
3268                DataColumns.MIMETYPE_ID + "=" + mMimeType, null, null, null, null);
3269        try {
3270            while (cursor.moveToNext()) {
3271                long dataId = cursor.getLong(StructName205Query.ID);
3272                long rawContactId = cursor.getLong(StructName205Query.RAW_CONTACT_ID);
3273                int displayNameSource = cursor.getInt(StructName205Query.DISPLAY_NAME_SOURCE);
3274
3275                name.clear();
3276                name.prefix = cursor.getString(StructName205Query.PREFIX);
3277                name.givenNames = cursor.getString(StructName205Query.GIVEN_NAME);
3278                name.middleName = cursor.getString(StructName205Query.MIDDLE_NAME);
3279                name.familyName = cursor.getString(StructName205Query.FAMILY_NAME);
3280                name.suffix = cursor.getString(StructName205Query.SUFFIX);
3281                name.phoneticFamilyName = cursor.getString(StructName205Query.PHONETIC_FAMILY_NAME);
3282                name.phoneticMiddleName = cursor.getString(StructName205Query.PHONETIC_MIDDLE_NAME);
3283                name.phoneticGivenName = cursor.getString(StructName205Query.PHONETIC_GIVEN_NAME);
3284
3285                upgradeNameToVersion205(dataId, rawContactId, displayNameSource, name,
3286                        structuredNameUpdate, rawContactUpdate, splitter);
3287            }
3288        } finally {
3289            cursor.close();
3290        }
3291    }
3292
3293    private void upgradeNameToVersion205(
3294            long dataId,
3295            long rawContactId,
3296            int displayNameSource,
3297            NameSplitter.Name name,
3298            SQLiteStatement structuredNameUpdate,
3299            SQLiteStatement rawContactUpdate,
3300            NameSplitter splitter) {
3301
3302        splitter.guessNameStyle(name);
3303        int unadjustedFullNameStyle = name.fullNameStyle;
3304        name.fullNameStyle = splitter.getAdjustedFullNameStyle(name.fullNameStyle);
3305        String displayName = splitter.join(name, true, true);
3306
3307        // Don't update database with the adjusted fullNameStyle as it is locale
3308        // related
3309        structuredNameUpdate.bindLong(1, unadjustedFullNameStyle);
3310        DatabaseUtils.bindObjectToProgram(structuredNameUpdate, 2, displayName);
3311        structuredNameUpdate.bindLong(3, name.phoneticNameStyle);
3312        structuredNameUpdate.bindLong(4, dataId);
3313        structuredNameUpdate.execute();
3314
3315        if (displayNameSource == DisplayNameSources.STRUCTURED_NAME) {
3316            String displayNameAlternative = splitter.join(name, false, false);
3317            String phoneticName = splitter.joinPhoneticName(name);
3318            String sortKey = null;
3319            String sortKeyAlternative = null;
3320
3321            if (phoneticName != null) {
3322                sortKey = sortKeyAlternative = phoneticName;
3323            } else if (name.fullNameStyle == FullNameStyle.CHINESE ||
3324                    name.fullNameStyle == FullNameStyle.CJK) {
3325                sortKey = sortKeyAlternative = displayName;
3326            }
3327
3328            if (sortKey == null) {
3329                sortKey = displayName;
3330                sortKeyAlternative = displayNameAlternative;
3331            }
3332
3333            updateRawContact205(rawContactUpdate, rawContactId, displayName,
3334                    displayNameAlternative, name.phoneticNameStyle, phoneticName, sortKey,
3335                    sortKeyAlternative);
3336        }
3337    }
3338
3339    private void upgradeOrganizationsToVersion205(
3340            SQLiteDatabase db, SQLiteStatement rawContactUpdate, NameSplitter splitter) {
3341
3342        final long mimeType = lookupMimeTypeId(db, Organization.CONTENT_ITEM_TYPE);
3343        SQLiteStatement organizationUpdate = db.compileStatement(
3344                "UPDATE " + Tables.DATA +
3345                " SET " +
3346                        Organization.PHONETIC_NAME_STYLE + "=?" +
3347                " WHERE " + Data._ID + "=?");
3348
3349        Cursor cursor = db.query(Organization205Query.TABLE, Organization205Query.COLUMNS,
3350                DataColumns.MIMETYPE_ID + "=" + mimeType + " AND "
3351                        + RawContacts.DISPLAY_NAME_SOURCE + "=" + DisplayNameSources.ORGANIZATION,
3352                null, null, null, null);
3353        try {
3354            while (cursor.moveToNext()) {
3355                long dataId = cursor.getLong(Organization205Query.ID);
3356                long rawContactId = cursor.getLong(Organization205Query.RAW_CONTACT_ID);
3357                String company = cursor.getString(Organization205Query.COMPANY);
3358                String phoneticName = cursor.getString(Organization205Query.PHONETIC_NAME);
3359
3360                int phoneticNameStyle = splitter.guessPhoneticNameStyle(phoneticName);
3361
3362                organizationUpdate.bindLong(1, phoneticNameStyle);
3363                organizationUpdate.bindLong(2, dataId);
3364                organizationUpdate.execute();
3365
3366                String sortKey = company;
3367
3368                updateRawContact205(rawContactUpdate, rawContactId, company,
3369                        company, phoneticNameStyle, phoneticName, sortKey, sortKey);
3370            }
3371        } finally {
3372            cursor.close();
3373        }
3374    }
3375
3376    private void updateRawContact205(SQLiteStatement rawContactUpdate, long rawContactId,
3377            String displayName, String displayNameAlternative, int phoneticNameStyle,
3378            String phoneticName, String sortKeyPrimary, String sortKeyAlternative) {
3379        bindString(rawContactUpdate, 1, displayName);
3380        bindString(rawContactUpdate, 2, displayNameAlternative);
3381        bindString(rawContactUpdate, 3, phoneticName);
3382        rawContactUpdate.bindLong(4, phoneticNameStyle);
3383        bindString(rawContactUpdate, 5, sortKeyPrimary);
3384        bindString(rawContactUpdate, 6, sortKeyAlternative);
3385        rawContactUpdate.bindLong(7, rawContactId);
3386        rawContactUpdate.execute();
3387    }
3388
3389    private void upgrateToVersion206(SQLiteDatabase db) {
3390        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
3391                + " ADD name_verified INTEGER NOT NULL DEFAULT 0;");
3392    }
3393
3394    /**
3395     * The {@link ContactsProvider2#update} method was deleting name lookup for new
3396     * emails during the sync.  We need to restore the lost name lookup rows.
3397     */
3398    private void upgradeEmailToVersion303(SQLiteDatabase db) {
3399        final long mimeTypeId = lookupMimeTypeId(db, Email.CONTENT_ITEM_TYPE);
3400        if (mimeTypeId == -1) {
3401            return;
3402        }
3403
3404        ContentValues values = new ContentValues();
3405
3406        // Find all data rows with the mime type "email" that are missing name lookup
3407        Cursor cursor = db.query(Upgrade303Query.TABLE, Upgrade303Query.COLUMNS,
3408                Upgrade303Query.SELECTION, new String[] {String.valueOf(mimeTypeId)},
3409                null, null, null);
3410        try {
3411            while (cursor.moveToNext()) {
3412                long dataId = cursor.getLong(Upgrade303Query.ID);
3413                long rawContactId = cursor.getLong(Upgrade303Query.RAW_CONTACT_ID);
3414                String value = cursor.getString(Upgrade303Query.DATA1);
3415                value = extractHandleFromEmailAddress(value);
3416
3417                if (value != null) {
3418                    values.put(NameLookupColumns.DATA_ID, dataId);
3419                    values.put(NameLookupColumns.RAW_CONTACT_ID, rawContactId);
3420                    values.put(NameLookupColumns.NAME_TYPE, NameLookupType.EMAIL_BASED_NICKNAME);
3421                    values.put(NameLookupColumns.NORMALIZED_NAME, NameNormalizer.normalize(value));
3422                    db.insert(Tables.NAME_LOOKUP, null, values);
3423                }
3424            }
3425        } finally {
3426            cursor.close();
3427        }
3428    }
3429
3430    /**
3431     * The {@link ContactsProvider2#update} method was deleting name lookup for new
3432     * nicknames during the sync.  We need to restore the lost name lookup rows.
3433     */
3434    private void upgradeNicknameToVersion303(SQLiteDatabase db) {
3435        final long mimeTypeId = lookupMimeTypeId(db, Nickname.CONTENT_ITEM_TYPE);
3436        if (mimeTypeId == -1) {
3437            return;
3438        }
3439
3440        ContentValues values = new ContentValues();
3441
3442        // Find all data rows with the mime type "nickname" that are missing name lookup
3443        Cursor cursor = db.query(Upgrade303Query.TABLE, Upgrade303Query.COLUMNS,
3444                Upgrade303Query.SELECTION, new String[] {String.valueOf(mimeTypeId)},
3445                null, null, null);
3446        try {
3447            while (cursor.moveToNext()) {
3448                long dataId = cursor.getLong(Upgrade303Query.ID);
3449                long rawContactId = cursor.getLong(Upgrade303Query.RAW_CONTACT_ID);
3450                String value = cursor.getString(Upgrade303Query.DATA1);
3451
3452                values.put(NameLookupColumns.DATA_ID, dataId);
3453                values.put(NameLookupColumns.RAW_CONTACT_ID, rawContactId);
3454                values.put(NameLookupColumns.NAME_TYPE, NameLookupType.NICKNAME);
3455                values.put(NameLookupColumns.NORMALIZED_NAME, NameNormalizer.normalize(value));
3456                db.insert(Tables.NAME_LOOKUP, null, values);
3457            }
3458        } finally {
3459            cursor.close();
3460        }
3461    }
3462
3463    private void upgradeToVersion304(SQLiteDatabase db) {
3464        // Mimetype table requires an index on mime type.
3465        db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS mime_type ON " + Tables.MIMETYPES + " (" +
3466                MimetypesColumns.MIMETYPE +
3467        ");");
3468    }
3469
3470    private void upgradeToVersion306(SQLiteDatabase db) {
3471        // Fix invalid lookup that was used for Exchange contacts (it was not escaped)
3472        // It happened when a new contact was created AND synchronized
3473        final StringBuilder lookupKeyBuilder = new StringBuilder();
3474        final SQLiteStatement updateStatement = db.compileStatement(
3475                "UPDATE contacts " +
3476                "SET lookup=? " +
3477                "WHERE _id=?");
3478        final Cursor contactIdCursor = db.rawQuery(
3479                "SELECT DISTINCT contact_id " +
3480                "FROM raw_contacts " +
3481                "WHERE deleted=0 AND account_type='com.android.exchange'",
3482                null);
3483        try {
3484            while (contactIdCursor.moveToNext()) {
3485                final long contactId = contactIdCursor.getLong(0);
3486                lookupKeyBuilder.setLength(0);
3487                final Cursor c = db.rawQuery(
3488                        "SELECT account_type, account_name, _id, sourceid, display_name " +
3489                        "FROM raw_contacts " +
3490                        "WHERE contact_id=? " +
3491                        "ORDER BY _id",
3492                        new String[] {String.valueOf(contactId)});
3493                try {
3494                    while (c.moveToNext()) {
3495                        ContactLookupKey.appendToLookupKey(lookupKeyBuilder,
3496                                c.getString(0),
3497                                c.getString(1),
3498                                c.getLong(2),
3499                                c.getString(3),
3500                                c.getString(4));
3501                    }
3502                } finally {
3503                    c.close();
3504                }
3505
3506                if (lookupKeyBuilder.length() == 0) {
3507                    updateStatement.bindNull(1);
3508                } else {
3509                    updateStatement.bindString(1, Uri.encode(lookupKeyBuilder.toString()));
3510                }
3511                updateStatement.bindLong(2, contactId);
3512
3513                updateStatement.execute();
3514            }
3515        } finally {
3516            updateStatement.close();
3517            contactIdCursor.close();
3518        }
3519    }
3520
3521    private void upgradeToVersion307(SQLiteDatabase db) {
3522        db.execSQL("CREATE TABLE properties (" +
3523                "property_key TEXT PRIMARY_KEY, " +
3524                "property_value TEXT" +
3525        ");");
3526    }
3527
3528    private void upgradeToVersion308(SQLiteDatabase db) {
3529        db.execSQL("CREATE TABLE accounts (" +
3530                "account_name TEXT, " +
3531                "account_type TEXT " +
3532        ");");
3533
3534        db.execSQL("INSERT INTO accounts " +
3535                "SELECT DISTINCT account_name, account_type FROM raw_contacts");
3536    }
3537
3538    private void upgradeToVersion400(SQLiteDatabase db) {
3539        db.execSQL("ALTER TABLE " + Tables.GROUPS
3540                + " ADD " + Groups.FAVORITES + " INTEGER NOT NULL DEFAULT 0;");
3541        db.execSQL("ALTER TABLE " + Tables.GROUPS
3542                + " ADD " + Groups.AUTO_ADD + " INTEGER NOT NULL DEFAULT 0;");
3543    }
3544
3545    private void upgradeToVersion353(SQLiteDatabase db) {
3546        db.execSQL("DELETE FROM contacts " +
3547                "WHERE NOT EXISTS (SELECT 1 FROM raw_contacts WHERE contact_id=contacts._id)");
3548    }
3549
3550    private void rebuildNameLookup(SQLiteDatabase db, boolean rebuildSqliteStats) {
3551        db.execSQL("DROP INDEX IF EXISTS name_lookup_index");
3552        insertNameLookup(db);
3553        createContactsIndexes(db, rebuildSqliteStats);
3554    }
3555
3556    protected void rebuildSearchIndex() {
3557        rebuildSearchIndex(getWritableDatabase(), true);
3558    }
3559
3560    private void rebuildSearchIndex(SQLiteDatabase db, boolean rebuildSqliteStats) {
3561        createSearchIndexTable(db, rebuildSqliteStats);
3562        setProperty(db, SearchIndexManager.PROPERTY_SEARCH_INDEX_VERSION, "0");
3563    }
3564
3565    /**
3566     * Checks whether the current ICU code version matches that used to build
3567     * the locale specific data in the ContactsDB.
3568     */
3569    public boolean needsToUpdateLocaleData(LocaleSet locales) {
3570        final String dbLocale = getProperty(DbProperties.LOCALE, "");
3571        if (!dbLocale.equals(locales.toString())) {
3572            return true;
3573        }
3574        final String curICUVersion = ICU.getIcuVersion();
3575        final String dbICUVersion = getProperty(DbProperties.ICU_VERSION,
3576                "(unknown)");
3577        if (!curICUVersion.equals(dbICUVersion)) {
3578            Log.i(TAG, "ICU version has changed. Current version is "
3579                    + curICUVersion + "; DB was built with " + dbICUVersion);
3580            return true;
3581        }
3582        return false;
3583    }
3584
3585    private void upgradeLocaleData(SQLiteDatabase db, boolean rebuildSqliteStats) {
3586        final LocaleSet locales = LocaleSet.getDefault();
3587        Log.i(TAG, "Upgrading locale data for " + locales
3588                + " (ICU v" + ICU.getIcuVersion() + ")");
3589        final long start = SystemClock.elapsedRealtime();
3590        initializeCache(db);
3591        rebuildLocaleData(db, locales, rebuildSqliteStats);
3592        Log.i(TAG, "Locale update completed in " + (SystemClock.elapsedRealtime() - start) + "ms");
3593    }
3594
3595    private void rebuildLocaleData(SQLiteDatabase db, LocaleSet locales, boolean rebuildSqliteStats) {
3596        db.execSQL("DROP INDEX raw_contact_sort_key1_index");
3597        db.execSQL("DROP INDEX raw_contact_sort_key2_index");
3598        db.execSQL("DROP INDEX IF EXISTS name_lookup_index");
3599
3600        loadNicknameLookupTable(db);
3601        insertNameLookup(db);
3602        rebuildSortKeys(db);
3603        createContactsIndexes(db, rebuildSqliteStats);
3604
3605        FastScrollingIndexCache.getInstance(mContext).invalidate();
3606        // Update the ICU version used to generate the locale derived data
3607        // so we can tell when we need to rebuild with new ICU versions.
3608        setProperty(db, DbProperties.ICU_VERSION, ICU.getIcuVersion());
3609        setProperty(db, DbProperties.LOCALE, locales.toString());
3610    }
3611
3612    /**
3613     * Regenerates all locale-sensitive data if needed:
3614     * nickname_lookup, name_lookup and sort keys. Invalidates the fast
3615     * scrolling index cache.
3616     */
3617    public void setLocale(LocaleSet locales) {
3618        if (!needsToUpdateLocaleData(locales)) {
3619            return;
3620        }
3621        Log.i(TAG, "Switching to locale " + locales
3622                + " (ICU v" + ICU.getIcuVersion() + ")");
3623
3624        final long start = SystemClock.elapsedRealtime();
3625        SQLiteDatabase db = getWritableDatabase();
3626        db.setLocale(locales.getPrimaryLocale());
3627        db.beginTransaction();
3628        try {
3629            rebuildLocaleData(db, locales, true);
3630            db.setTransactionSuccessful();
3631        } finally {
3632            db.endTransaction();
3633        }
3634
3635        Log.i(TAG, "Locale change completed in " + (SystemClock.elapsedRealtime() - start) + "ms");
3636    }
3637
3638    /**
3639     * Regenerates sort keys for all contacts.
3640     */
3641    private void rebuildSortKeys(SQLiteDatabase db) {
3642        Cursor cursor = db.query(Tables.RAW_CONTACTS, new String[] {RawContacts._ID},
3643                null, null, null, null, null);
3644        try {
3645            while (cursor.moveToNext()) {
3646                long rawContactId = cursor.getLong(0);
3647                updateRawContactDisplayName(db, rawContactId);
3648            }
3649        } finally {
3650            cursor.close();
3651        }
3652    }
3653
3654    private void insertNameLookup(SQLiteDatabase db) {
3655        db.execSQL("DELETE FROM " + Tables.NAME_LOOKUP);
3656
3657        SQLiteStatement nameLookupInsert = db.compileStatement(
3658                "INSERT OR IGNORE INTO " + Tables.NAME_LOOKUP + "("
3659                        + NameLookupColumns.RAW_CONTACT_ID + ","
3660                        + NameLookupColumns.DATA_ID + ","
3661                        + NameLookupColumns.NAME_TYPE + ","
3662                        + NameLookupColumns.NORMALIZED_NAME +
3663                ") VALUES (?,?,?,?)");
3664
3665        try {
3666            insertStructuredNameLookup(db, nameLookupInsert);
3667            insertEmailLookup(db, nameLookupInsert);
3668            insertNicknameLookup(db, nameLookupInsert);
3669        } finally {
3670            nameLookupInsert.close();
3671        }
3672    }
3673
3674    /**
3675     * Inserts name lookup rows for all structured names in the database.
3676     */
3677    private void insertStructuredNameLookup(SQLiteDatabase db, SQLiteStatement nameLookupInsert) {
3678        NameSplitter nameSplitter = createNameSplitter();
3679        NameLookupBuilder nameLookupBuilder = new StructuredNameLookupBuilder(nameSplitter,
3680                new CommonNicknameCache(db), nameLookupInsert);
3681        final long mimeTypeId = lookupMimeTypeId(db, StructuredName.CONTENT_ITEM_TYPE);
3682        Cursor cursor = db.query(StructuredNameQuery.TABLE, StructuredNameQuery.COLUMNS,
3683                StructuredNameQuery.SELECTION, new String[] {String.valueOf(mimeTypeId)},
3684                null, null, null);
3685        try {
3686            while (cursor.moveToNext()) {
3687                long dataId = cursor.getLong(StructuredNameQuery.ID);
3688                long rawContactId = cursor.getLong(StructuredNameQuery.RAW_CONTACT_ID);
3689                String name = cursor.getString(StructuredNameQuery.DISPLAY_NAME);
3690                int fullNameStyle = nameSplitter.guessFullNameStyle(name);
3691                fullNameStyle = nameSplitter.getAdjustedFullNameStyle(fullNameStyle);
3692                nameLookupBuilder.insertNameLookup(rawContactId, dataId, name, fullNameStyle);
3693            }
3694        } finally {
3695            cursor.close();
3696        }
3697    }
3698
3699    /**
3700     * Inserts name lookup rows for all email addresses in the database.
3701     */
3702    private void insertEmailLookup(SQLiteDatabase db, SQLiteStatement nameLookupInsert) {
3703        final long mimeTypeId = lookupMimeTypeId(db, Email.CONTENT_ITEM_TYPE);
3704        Cursor cursor = db.query(EmailQuery.TABLE, EmailQuery.COLUMNS,
3705                EmailQuery.SELECTION, new String[] {String.valueOf(mimeTypeId)},
3706                null, null, null);
3707        try {
3708            while (cursor.moveToNext()) {
3709                long dataId = cursor.getLong(EmailQuery.ID);
3710                long rawContactId = cursor.getLong(EmailQuery.RAW_CONTACT_ID);
3711                String address = cursor.getString(EmailQuery.ADDRESS);
3712                address = extractHandleFromEmailAddress(address);
3713                insertNameLookup(nameLookupInsert, rawContactId, dataId,
3714                        NameLookupType.EMAIL_BASED_NICKNAME, address);
3715            }
3716        } finally {
3717            cursor.close();
3718        }
3719    }
3720
3721    /**
3722     * Inserts name lookup rows for all nicknames in the database.
3723     */
3724    private void insertNicknameLookup(SQLiteDatabase db, SQLiteStatement nameLookupInsert) {
3725        final long mimeTypeId = lookupMimeTypeId(db, Nickname.CONTENT_ITEM_TYPE);
3726        Cursor cursor = db.query(NicknameQuery.TABLE, NicknameQuery.COLUMNS,
3727                NicknameQuery.SELECTION, new String[]{String.valueOf(mimeTypeId)},
3728                null, null, null);
3729        try {
3730            while (cursor.moveToNext()) {
3731                long dataId = cursor.getLong(NicknameQuery.ID);
3732                long rawContactId = cursor.getLong(NicknameQuery.RAW_CONTACT_ID);
3733                String nickname = cursor.getString(NicknameQuery.NAME);
3734                insertNameLookup(nameLookupInsert, rawContactId, dataId,
3735                        NameLookupType.NICKNAME, nickname);
3736            }
3737        } finally {
3738            cursor.close();
3739        }
3740    }
3741
3742    /**
3743     * Inserts a record in the {@link Tables#NAME_LOOKUP} table.
3744     */
3745    public void insertNameLookup(SQLiteStatement stmt, long rawContactId, long dataId,
3746            int lookupType, String name) {
3747        if (TextUtils.isEmpty(name)) {
3748            return;
3749        }
3750
3751        String normalized = NameNormalizer.normalize(name);
3752        if (TextUtils.isEmpty(normalized)) {
3753            return;
3754        }
3755
3756        insertNormalizedNameLookup(stmt, rawContactId, dataId, lookupType, normalized);
3757    }
3758
3759    private void insertNormalizedNameLookup(SQLiteStatement stmt, long rawContactId, long dataId,
3760            int lookupType, String normalizedName) {
3761        stmt.bindLong(1, rawContactId);
3762        stmt.bindLong(2, dataId);
3763        stmt.bindLong(3, lookupType);
3764        stmt.bindString(4, normalizedName);
3765        stmt.executeInsert();
3766    }
3767
3768    /**
3769     * Changing the VISIBLE bit from a field on both RawContacts and Contacts to a separate table.
3770     */
3771    private void upgradeToVersion401(SQLiteDatabase db) {
3772        db.execSQL("CREATE TABLE " + Tables.VISIBLE_CONTACTS + " (" +
3773                Contacts._ID + " INTEGER PRIMARY KEY" +
3774                ");");
3775        db.execSQL("INSERT INTO " + Tables.VISIBLE_CONTACTS +
3776                " SELECT " + Contacts._ID +
3777                " FROM " + Tables.CONTACTS +
3778                " WHERE " + Contacts.IN_VISIBLE_GROUP + "!=0");
3779        db.execSQL("DROP INDEX contacts_visible_index");
3780    }
3781
3782    /**
3783     * Introducing a new table: directories.
3784     */
3785    private void upgradeToVersion402(SQLiteDatabase db) {
3786        createDirectoriesTable(db);
3787    }
3788
3789    private void upgradeToVersion403(SQLiteDatabase db) {
3790        db.execSQL("DROP TABLE IF EXISTS directories;");
3791        createDirectoriesTable(db);
3792
3793        db.execSQL("ALTER TABLE raw_contacts"
3794                + " ADD raw_contact_is_read_only INTEGER NOT NULL DEFAULT 0;");
3795
3796        db.execSQL("ALTER TABLE data"
3797                + " ADD is_read_only INTEGER NOT NULL DEFAULT 0;");
3798    }
3799
3800    private void upgradeToVersion405(SQLiteDatabase db) {
3801        db.execSQL("DROP TABLE IF EXISTS phone_lookup;");
3802        // Private phone numbers table used for lookup
3803        db.execSQL("CREATE TABLE " + Tables.PHONE_LOOKUP + " (" +
3804                PhoneLookupColumns.DATA_ID
3805                + " INTEGER REFERENCES data(_id) NOT NULL," +
3806                PhoneLookupColumns.RAW_CONTACT_ID
3807                + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," +
3808                PhoneLookupColumns.NORMALIZED_NUMBER + " TEXT NOT NULL," +
3809                PhoneLookupColumns.MIN_MATCH + " TEXT NOT NULL" +
3810        ");");
3811
3812        db.execSQL("CREATE INDEX phone_lookup_index ON " + Tables.PHONE_LOOKUP + " (" +
3813                PhoneLookupColumns.NORMALIZED_NUMBER + "," +
3814                PhoneLookupColumns.RAW_CONTACT_ID + "," +
3815                PhoneLookupColumns.DATA_ID +
3816        ");");
3817
3818        db.execSQL("CREATE INDEX phone_lookup_min_match_index ON " + Tables.PHONE_LOOKUP + " (" +
3819                PhoneLookupColumns.MIN_MATCH + "," +
3820                PhoneLookupColumns.RAW_CONTACT_ID + "," +
3821                PhoneLookupColumns.DATA_ID +
3822        ");");
3823
3824        final long mimeTypeId = lookupMimeTypeId(db, Phone.CONTENT_ITEM_TYPE);
3825        if (mimeTypeId == -1) {
3826            return;
3827        }
3828
3829        Cursor cursor = db.rawQuery(
3830                    "SELECT _id, " + Phone.RAW_CONTACT_ID + ", " + Phone.NUMBER +
3831                    " FROM " + Tables.DATA +
3832                    " WHERE " + DataColumns.MIMETYPE_ID + "=" + mimeTypeId
3833                            + " AND " + Phone.NUMBER + " NOT NULL", null);
3834
3835        ContentValues phoneValues = new ContentValues();
3836        try {
3837            while (cursor.moveToNext()) {
3838                long dataID = cursor.getLong(0);
3839                long rawContactID = cursor.getLong(1);
3840                String number = cursor.getString(2);
3841                String normalizedNumber = PhoneNumberUtils.normalizeNumber(number);
3842                if (!TextUtils.isEmpty(normalizedNumber)) {
3843                    phoneValues.clear();
3844                    phoneValues.put(PhoneLookupColumns.RAW_CONTACT_ID, rawContactID);
3845                    phoneValues.put(PhoneLookupColumns.DATA_ID, dataID);
3846                    phoneValues.put(PhoneLookupColumns.NORMALIZED_NUMBER, normalizedNumber);
3847                    phoneValues.put(PhoneLookupColumns.MIN_MATCH,
3848                            PhoneNumberUtils.toCallerIDMinMatch(normalizedNumber));
3849                    db.insert(Tables.PHONE_LOOKUP, null, phoneValues);
3850                }
3851            }
3852        } finally {
3853            cursor.close();
3854        }
3855    }
3856
3857    private void upgradeToVersion406(SQLiteDatabase db) {
3858        db.execSQL("ALTER TABLE calls ADD countryiso TEXT;");
3859    }
3860
3861    private void upgradeToVersion409(SQLiteDatabase db) {
3862        db.execSQL("DROP TABLE IF EXISTS directories;");
3863        createDirectoriesTable(db);
3864    }
3865
3866    /**
3867     * Adding DEFAULT_DIRECTORY table.
3868     * DEFAULT_DIRECTORY should contain every contact which should be shown to users in default.
3869     * - if a contact doesn't belong to any account (local contact), it should be in
3870     *   default_directory
3871     * - if a contact belongs to an account that doesn't have a "default" group, it should be in
3872     *   default_directory
3873     * - if a contact belongs to an account that has a "default" group (like Google directory,
3874     *   which has "My contacts" group as default), it should be in default_directory.
3875     *
3876     * This logic assumes that accounts with the "default" group should have at least one
3877     * group with AUTO_ADD (implying it is the default group) flag in the groups table.
3878     */
3879    private void upgradeToVersion411(SQLiteDatabase db) {
3880        db.execSQL("DROP TABLE IF EXISTS " + Tables.DEFAULT_DIRECTORY);
3881        db.execSQL("CREATE TABLE default_directory (_id INTEGER PRIMARY KEY);");
3882
3883        // Process contacts without an account
3884        db.execSQL("INSERT OR IGNORE INTO default_directory " +
3885                " SELECT contact_id " +
3886                " FROM raw_contacts " +
3887                " WHERE raw_contacts.account_name IS NULL " +
3888                "   AND raw_contacts.account_type IS NULL ");
3889
3890        // Process accounts that don't have a default group (e.g. Exchange).
3891        db.execSQL("INSERT OR IGNORE INTO default_directory " +
3892                " SELECT contact_id " +
3893                " FROM raw_contacts " +
3894                " WHERE NOT EXISTS" +
3895                " (SELECT _id " +
3896                "  FROM groups " +
3897                "  WHERE raw_contacts.account_name = groups.account_name" +
3898                "    AND raw_contacts.account_type = groups.account_type" +
3899                "    AND groups.auto_add != 0)");
3900
3901        final long mimetype = lookupMimeTypeId(db, GroupMembership.CONTENT_ITEM_TYPE);
3902
3903        // Process accounts that do have a default group (e.g. Google)
3904        db.execSQL("INSERT OR IGNORE INTO default_directory " +
3905                " SELECT contact_id " +
3906                " FROM raw_contacts " +
3907                " JOIN data " +
3908                "   ON (raw_contacts._id=raw_contact_id)" +
3909                " WHERE mimetype_id=" + mimetype +
3910                " AND EXISTS" +
3911                " (SELECT _id" +
3912                "  FROM groups" +
3913                "  WHERE raw_contacts.account_name = groups.account_name" +
3914                "    AND raw_contacts.account_type = groups.account_type" +
3915                "    AND groups.auto_add != 0)");
3916    }
3917
3918    private void upgradeToVersion413(SQLiteDatabase db) {
3919        db.execSQL("DROP TABLE IF EXISTS directories;");
3920        createDirectoriesTable(db);
3921    }
3922
3923    private void upgradeToVersion415(SQLiteDatabase db) {
3924        db.execSQL(
3925                "ALTER TABLE " + Tables.GROUPS +
3926                " ADD " + Groups.GROUP_IS_READ_ONLY + " INTEGER NOT NULL DEFAULT 0");
3927        db.execSQL(
3928                "UPDATE " + Tables.GROUPS +
3929                        "   SET " + Groups.GROUP_IS_READ_ONLY + "=1" +
3930                        " WHERE " + Groups.SYSTEM_ID + " NOT NULL");
3931    }
3932
3933    private void upgradeToVersion416(SQLiteDatabase db) {
3934        db.execSQL("CREATE INDEX phone_lookup_data_id_min_match_index ON " + Tables.PHONE_LOOKUP +
3935                " (" + PhoneLookupColumns.DATA_ID + ", " + PhoneLookupColumns.MIN_MATCH + ");");
3936    }
3937
3938    private void upgradeToVersion501(SQLiteDatabase db) {
3939        // Remove organization rows from the name lookup, we now use search index for that
3940        db.execSQL("DELETE FROM name_lookup WHERE name_type=5");
3941    }
3942
3943    private void upgradeToVersion502(SQLiteDatabase db) {
3944        // Remove Chinese and Korean name lookup - this data is now in the search index
3945        db.execSQL("DELETE FROM name_lookup WHERE name_type IN (6, 7)");
3946    }
3947
3948    private void upgradeToVersion504(SQLiteDatabase db) {
3949        initializeCache(db);
3950
3951        // Find all names with prefixes and recreate display name
3952        Cursor cursor = db.rawQuery(
3953                "SELECT " + StructuredName.RAW_CONTACT_ID +
3954                " FROM " + Tables.DATA +
3955                " WHERE " + DataColumns.MIMETYPE_ID + "=?"
3956                        + " AND " + StructuredName.PREFIX + " NOT NULL",
3957                new String[] {String.valueOf(mMimeTypeIdStructuredName)});
3958
3959        try {
3960            while(cursor.moveToNext()) {
3961                long rawContactId = cursor.getLong(0);
3962                updateRawContactDisplayName(db, rawContactId);
3963            }
3964
3965        } finally {
3966            cursor.close();
3967        }
3968    }
3969
3970    private void upgradeToVersion601(SQLiteDatabase db) {
3971        db.execSQL("CREATE TABLE data_usage_stat(" +
3972                "stat_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
3973                "data_id INTEGER NOT NULL, " +
3974                "usage_type INTEGER NOT NULL DEFAULT 0, " +
3975                "times_used INTEGER NOT NULL DEFAULT 0, " +
3976                "last_time_used INTERGER NOT NULL DEFAULT 0, " +
3977                "FOREIGN KEY(data_id) REFERENCES data(_id));");
3978        db.execSQL("CREATE UNIQUE INDEX data_usage_stat_index ON " +
3979                "data_usage_stat (data_id, usage_type)");
3980    }
3981
3982    private void upgradeToVersion602(SQLiteDatabase db) {
3983        db.execSQL("ALTER TABLE calls ADD voicemail_uri TEXT;");
3984        db.execSQL("ALTER TABLE calls ADD _data TEXT;");
3985        db.execSQL("ALTER TABLE calls ADD has_content INTEGER;");
3986        db.execSQL("ALTER TABLE calls ADD mime_type TEXT;");
3987        db.execSQL("ALTER TABLE calls ADD source_data TEXT;");
3988        db.execSQL("ALTER TABLE calls ADD source_package TEXT;");
3989        db.execSQL("ALTER TABLE calls ADD state INTEGER;");
3990    }
3991
3992    private void upgradeToVersion604(SQLiteDatabase db) {
3993        db.execSQL("CREATE TABLE voicemail_status (" +
3994                "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
3995                "source_package TEXT UNIQUE NOT NULL," +
3996                "settings_uri TEXT," +
3997                "voicemail_access_uri TEXT," +
3998                "configuration_state INTEGER," +
3999                "data_channel_state INTEGER," +
4000                "notification_channel_state INTEGER" +
4001        ");");
4002    }
4003
4004    private void upgradeToVersion606(SQLiteDatabase db) {
4005        db.execSQL("DROP VIEW IF EXISTS view_contacts_restricted;");
4006        db.execSQL("DROP VIEW IF EXISTS view_data_restricted;");
4007        db.execSQL("DROP VIEW IF EXISTS view_raw_contacts_restricted;");
4008        db.execSQL("DROP VIEW IF EXISTS view_raw_entities_restricted;");
4009        db.execSQL("DROP VIEW IF EXISTS view_entities_restricted;");
4010        db.execSQL("DROP VIEW IF EXISTS view_data_usage_stat_restricted;");
4011        db.execSQL("DROP INDEX IF EXISTS contacts_restricted_index");
4012
4013        // We should remove the restricted columns here as well, but unfortunately SQLite doesn't
4014        // provide ALTER TABLE DROP COLUMN. As they have DEFAULT 0, we can keep but ignore them
4015    }
4016
4017    private void upgradeToVersion608(SQLiteDatabase db) {
4018        db.execSQL("ALTER TABLE contacts ADD photo_file_id INTEGER REFERENCES photo_files(_id);");
4019
4020        db.execSQL("CREATE TABLE photo_files(" +
4021                "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
4022                "height INTEGER NOT NULL, " +
4023                "width INTEGER NOT NULL, " +
4024                "filesize INTEGER NOT NULL);");
4025    }
4026
4027    private void upgradeToVersion610(SQLiteDatabase db) {
4028        db.execSQL("ALTER TABLE calls ADD is_read INTEGER;");
4029    }
4030
4031    private void upgradeToVersion611(SQLiteDatabase db) {
4032        db.execSQL("ALTER TABLE raw_contacts ADD data_set TEXT DEFAULT NULL;");
4033        db.execSQL("ALTER TABLE groups ADD data_set TEXT DEFAULT NULL;");
4034        db.execSQL("ALTER TABLE accounts ADD data_set TEXT DEFAULT NULL;");
4035
4036        db.execSQL("CREATE INDEX raw_contacts_source_id_data_set_index ON raw_contacts " +
4037                "(sourceid, account_type, account_name, data_set);");
4038
4039        db.execSQL("CREATE INDEX groups_source_id_data_set_index ON groups " +
4040                "(sourceid, account_type, account_name, data_set);");
4041    }
4042
4043    private void upgradeToVersion612(SQLiteDatabase db) {
4044        db.execSQL("ALTER TABLE calls ADD geocoded_location TEXT DEFAULT NULL;");
4045        // Old calls will not have a geocoded location; new calls will get it when inserted.
4046    }
4047
4048    private void upgradeToVersion613(SQLiteDatabase db) {
4049        // The stream item and stream item photos APIs were not in-use by anyone in the time
4050        // between their initial creation (in v609) and this update.  So we're just dropping
4051        // and re-creating them to get appropriate columns.  The delta is as follows:
4052        // - In stream_items, package_id was replaced by res_package.
4053        // - In stream_item_photos, picture was replaced by photo_file_id.
4054        // - Instead of resource IDs for icon and label, we use resource name strings now
4055        // - Added sync columns
4056        // - Removed action and action_uri
4057        // - Text and comments are now nullable
4058
4059        db.execSQL("DROP TABLE IF EXISTS stream_items");
4060        db.execSQL("DROP TABLE IF EXISTS stream_item_photos");
4061
4062        db.execSQL("CREATE TABLE stream_items(" +
4063                "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
4064                "raw_contact_id INTEGER NOT NULL, " +
4065                "res_package TEXT, " +
4066                "icon TEXT, " +
4067                "label TEXT, " +
4068                "text TEXT, " +
4069                "timestamp INTEGER NOT NULL, " +
4070                "comments TEXT, " +
4071                "stream_item_sync1 TEXT, " +
4072                "stream_item_sync2 TEXT, " +
4073                "stream_item_sync3 TEXT, " +
4074                "stream_item_sync4 TEXT, " +
4075                "FOREIGN KEY(raw_contact_id) REFERENCES raw_contacts(_id));");
4076
4077        db.execSQL("CREATE TABLE stream_item_photos(" +
4078                "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
4079                "stream_item_id INTEGER NOT NULL, " +
4080                "sort_index INTEGER, " +
4081                "photo_file_id INTEGER NOT NULL, " +
4082                "stream_item_photo_sync1 TEXT, " +
4083                "stream_item_photo_sync2 TEXT, " +
4084                "stream_item_photo_sync3 TEXT, " +
4085                "stream_item_photo_sync4 TEXT, " +
4086                "FOREIGN KEY(stream_item_id) REFERENCES stream_items(_id));");
4087    }
4088
4089    private void upgradeToVersion615(SQLiteDatabase db) {
4090        // Old calls will not have up to date values for these columns, they will be filled in
4091        // as needed.
4092        db.execSQL("ALTER TABLE calls ADD lookup_uri TEXT DEFAULT NULL;");
4093        db.execSQL("ALTER TABLE calls ADD matched_number TEXT DEFAULT NULL;");
4094        db.execSQL("ALTER TABLE calls ADD normalized_number TEXT DEFAULT NULL;");
4095        db.execSQL("ALTER TABLE calls ADD photo_id INTEGER NOT NULL DEFAULT 0;");
4096    }
4097
4098    private void upgradeToVersion618(SQLiteDatabase db) {
4099        // The Settings table needs a data_set column which technically should be part of the
4100        // primary key but can't be because it may be null.  Since SQLite doesn't support nuking
4101        // the primary key, we'll drop the old table, re-create it, and copy the settings back in.
4102        db.execSQL("CREATE TEMPORARY TABLE settings_backup(" +
4103                "account_name STRING NOT NULL," +
4104                "account_type STRING NOT NULL," +
4105                "ungrouped_visible INTEGER NOT NULL DEFAULT 0," +
4106                "should_sync INTEGER NOT NULL DEFAULT 1" +
4107        ");");
4108        db.execSQL("INSERT INTO settings_backup " +
4109                "SELECT account_name, account_type, ungrouped_visible, should_sync" +
4110                " FROM settings");
4111        db.execSQL("DROP TABLE settings");
4112        db.execSQL("CREATE TABLE settings (" +
4113                "account_name STRING NOT NULL," +
4114                "account_type STRING NOT NULL," +
4115                "data_set STRING," +
4116                "ungrouped_visible INTEGER NOT NULL DEFAULT 0," +
4117                "should_sync INTEGER NOT NULL DEFAULT 1" +
4118        ");");
4119        db.execSQL("INSERT INTO settings " +
4120                "SELECT account_name, account_type, NULL, ungrouped_visible, should_sync " +
4121                "FROM settings_backup");
4122        db.execSQL("DROP TABLE settings_backup");
4123    }
4124
4125    private void upgradeToVersion622(SQLiteDatabase db) {
4126        db.execSQL("ALTER TABLE calls ADD formatted_number TEXT DEFAULT NULL;");
4127    }
4128
4129    private void upgradeToVersion626(SQLiteDatabase db) {
4130        db.execSQL("DROP TABLE IF EXISTS accounts");
4131
4132        db.execSQL("CREATE TABLE accounts (" +
4133                "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
4134                "account_name TEXT, " +
4135                "account_type TEXT, " +
4136                "data_set TEXT" +
4137        ");");
4138
4139        // Add "account_id" column to groups and raw_contacts
4140        db.execSQL("ALTER TABLE raw_contacts ADD " +
4141                "account_id INTEGER REFERENCES accounts(_id)");
4142        db.execSQL("ALTER TABLE groups ADD " +
4143                "account_id INTEGER REFERENCES accounts(_id)");
4144
4145        // Update indexes.
4146        db.execSQL("DROP INDEX IF EXISTS raw_contacts_source_id_index");
4147        db.execSQL("DROP INDEX IF EXISTS raw_contacts_source_id_data_set_index");
4148        db.execSQL("DROP INDEX IF EXISTS groups_source_id_index");
4149        db.execSQL("DROP INDEX IF EXISTS groups_source_id_data_set_index");
4150
4151        db.execSQL("CREATE INDEX raw_contacts_source_id_account_id_index ON raw_contacts ("
4152                + "sourceid, account_id);");
4153        db.execSQL("CREATE INDEX groups_source_id_account_id_index ON groups ("
4154                + "sourceid, account_id);");
4155
4156        // Migrate account_name/account_type/data_set to accounts table
4157
4158        final Set<AccountWithDataSet> accountsWithDataSets = Sets.newHashSet();
4159        upgradeToVersion626_findAccountsWithDataSets(accountsWithDataSets, db, "raw_contacts");
4160        upgradeToVersion626_findAccountsWithDataSets(accountsWithDataSets, db, "groups");
4161
4162        for (AccountWithDataSet accountWithDataSet : accountsWithDataSets) {
4163            db.execSQL("INSERT INTO accounts (account_name,account_type,data_set)VALUES(?, ?, ?)",
4164                    new String[] {
4165                            accountWithDataSet.getAccountName(),
4166                            accountWithDataSet.getAccountType(),
4167                            accountWithDataSet.getDataSet()
4168                    });
4169        }
4170        upgradeToVersion626_fillAccountId(db, "raw_contacts");
4171        upgradeToVersion626_fillAccountId(db, "groups");
4172    }
4173
4174    private static void upgradeToVersion626_findAccountsWithDataSets(
4175            Set<AccountWithDataSet> result, SQLiteDatabase db, String table) {
4176        Cursor c = db.rawQuery(
4177                "SELECT DISTINCT account_name, account_type, data_set FROM " + table, null);
4178        try {
4179            while (c.moveToNext()) {
4180                result.add(AccountWithDataSet.get(c.getString(0), c.getString(1), c.getString(2)));
4181            }
4182        } finally {
4183            c.close();
4184        }
4185    }
4186
4187    private static void upgradeToVersion626_fillAccountId(SQLiteDatabase db, String table) {
4188        StringBuilder sb = new StringBuilder();
4189
4190        // Set account_id and null out account_name, account_type and data_set
4191
4192        sb.append("UPDATE " + table + " SET account_id = (SELECT _id FROM accounts WHERE ");
4193
4194        addJoinExpressionAllowingNull(sb, table + ".account_name", "accounts.account_name");
4195        sb.append("AND");
4196        addJoinExpressionAllowingNull(sb, table + ".account_type", "accounts.account_type");
4197        sb.append("AND");
4198        addJoinExpressionAllowingNull(sb, table + ".data_set", "accounts.data_set");
4199
4200        sb.append("), account_name = null, account_type = null, data_set = null");
4201        db.execSQL(sb.toString());
4202    }
4203
4204    private void upgradeToVersion701(SQLiteDatabase db) {
4205        db.execSQL("UPDATE raw_contacts SET last_time_contacted =" +
4206                " max(ifnull(last_time_contacted, 0), " +
4207                " ifnull((SELECT max(last_time_used) " +
4208                    " FROM data JOIN data_usage_stat ON (data._id = data_usage_stat.data_id)" +
4209                    " WHERE data.raw_contact_id = raw_contacts._id), 0))");
4210        // Replace 0 with null.  This isn't really necessary, but we do this anyway for consistency.
4211        db.execSQL("UPDATE raw_contacts SET last_time_contacted = null" +
4212                " where last_time_contacted = 0");
4213    }
4214
4215    /**
4216     * Pre-HC devices don't have correct "NORMALIZED_NUMBERS".  Clear them up.
4217     */
4218    private void upgradeToVersion702(SQLiteDatabase db) {
4219        // All the "correct" Phone.NORMALIZED_NUMBERS should begin with "+".  The upgraded data
4220        // don't.  Find all Phone.NORMALIZED_NUMBERS that don't begin with "+".
4221        final int count;
4222        final long[] dataIds;
4223        final long[] rawContactIds;
4224        final String[] phoneNumbers;
4225        final StringBuilder sbDataIds;
4226        final Cursor c = db.rawQuery(
4227                "SELECT _id, raw_contact_id, data1 FROM data " +
4228                " WHERE mimetype_id=" +
4229                    "(SELECT _id FROM mimetypes" +
4230                    " WHERE mimetype='vnd.android.cursor.item/phone_v2')" +
4231                " AND data4 not like '+%'", // "Not like" will exclude nulls too.
4232                null);
4233        try {
4234            count = c.getCount();
4235            if (count == 0) {
4236                return;
4237            }
4238            dataIds = new long[count];
4239            rawContactIds = new long[count];
4240            phoneNumbers = new String[count];
4241            sbDataIds = new StringBuilder();
4242
4243            c.moveToPosition(-1);
4244            while (c.moveToNext()) {
4245                final int i = c.getPosition();
4246                dataIds[i] = c.getLong(0);
4247                rawContactIds[i] = c.getLong(1);
4248                phoneNumbers[i] = c.getString(2);
4249
4250                if (sbDataIds.length() > 0) {
4251                    sbDataIds.append(",");
4252                }
4253                sbDataIds.append(dataIds[i]);
4254            }
4255        } finally {
4256            c.close();
4257        }
4258
4259        final String dataIdList = sbDataIds.toString();
4260
4261        // Then, update the Data and PhoneLookup tables.
4262
4263        // First, just null out all Phone.NORMALIZED_NUMBERS for those.
4264        db.execSQL("UPDATE data SET data4 = null" +
4265                " WHERE _id IN (" + dataIdList + ")");
4266
4267        // Then, re-create phone_lookup for them.
4268        db.execSQL("DELETE FROM phone_lookup" +
4269                " WHERE data_id IN (" + dataIdList + ")");
4270
4271        for (int i = 0; i < count; i++) {
4272            // Mimic how DataRowHandlerForPhoneNumber.insert() works when it can't normalize
4273            // numbers.
4274            final String phoneNumber = phoneNumbers[i];
4275            if (TextUtils.isEmpty(phoneNumber)) continue;
4276
4277            final String normalized = PhoneNumberUtils.normalizeNumber(phoneNumber);
4278            if (!TextUtils.isEmpty(normalized)) {
4279                db.execSQL("INSERT INTO phone_lookup" +
4280                        "(data_id, raw_contact_id, normalized_number, min_match)" +
4281                        " VALUES(?,?,?,?)",
4282                        new String[] {
4283                            String.valueOf(dataIds[i]),
4284                            String.valueOf(rawContactIds[i]),
4285                            normalized,
4286                            PhoneNumberUtils.toCallerIDMinMatch(normalized)});
4287            }
4288        }
4289    }
4290
4291    private void upgradeToVersion707(SQLiteDatabase db) {
4292        db.execSQL("ALTER TABLE raw_contacts ADD phonebook_label TEXT;");
4293        db.execSQL("ALTER TABLE raw_contacts ADD phonebook_bucket INTEGER;");
4294        db.execSQL("ALTER TABLE raw_contacts ADD phonebook_label_alt TEXT;");
4295        db.execSQL("ALTER TABLE raw_contacts ADD phonebook_bucket_alt INTEGER;");
4296    }
4297
4298    private void upgradeToVersion710(SQLiteDatabase db) {
4299
4300        // Adding timestamp to contacts table.
4301        db.execSQL("ALTER TABLE contacts"
4302                + " ADD contact_last_updated_timestamp INTEGER;");
4303
4304        db.execSQL("UPDATE contacts"
4305                + " SET contact_last_updated_timestamp"
4306                + " = " + System.currentTimeMillis());
4307
4308        db.execSQL("CREATE INDEX contacts_contact_last_updated_timestamp_index "
4309                + "ON contacts(contact_last_updated_timestamp)");
4310
4311        // New deleted contacts table.
4312        db.execSQL("CREATE TABLE deleted_contacts (" +
4313                "contact_id INTEGER PRIMARY KEY," +
4314                "contact_deleted_timestamp INTEGER NOT NULL default 0"
4315                + ");");
4316
4317        db.execSQL("CREATE INDEX deleted_contacts_contact_deleted_timestamp_index "
4318                + "ON deleted_contacts(contact_deleted_timestamp)");
4319    }
4320
4321    private void upgradeToVersion800(SQLiteDatabase db) {
4322        // Default Calls.PRESENTATION_ALLOWED=1
4323        db.execSQL("ALTER TABLE calls ADD presentation INTEGER NOT NULL DEFAULT 1;");
4324
4325        // Re-map CallerInfo.{..}_NUMBER strings to Calls.PRESENTATION_{..} ints
4326        //  PRIVATE_NUMBER="-2" -> PRESENTATION_RESTRICTED=2
4327        //  UNKNOWN_NUMBER="-1" -> PRESENTATION_UNKNOWN   =3
4328        // PAYPHONE_NUMBER="-3" -> PRESENTATION_PAYPHONE  =4
4329        db.execSQL("UPDATE calls SET presentation=2, number='' WHERE number='-2';");
4330        db.execSQL("UPDATE calls SET presentation=3, number='' WHERE number='-1';");
4331        db.execSQL("UPDATE calls SET presentation=4, number='' WHERE number='-3';");
4332    }
4333
4334    private void upgradeToVersion802(SQLiteDatabase db) {
4335        db.execSQL("ALTER TABLE contacts ADD pinned INTEGER NOT NULL DEFAULT " +
4336                ContactsContract.PinnedPositions.UNPINNED + ";");
4337        db.execSQL("ALTER TABLE raw_contacts ADD pinned INTEGER NOT NULL DEFAULT  " +
4338                ContactsContract.PinnedPositions.UNPINNED + ";");
4339    }
4340
4341    private void upgradeToVersion902(SQLiteDatabase db) {
4342        // adding account identifier to call log table
4343        db.execSQL("ALTER TABLE calls ADD subscription_component_name TEXT;");
4344        db.execSQL("ALTER TABLE calls ADD subscription_id TEXT;");
4345    }
4346
4347    /**
4348     * Searches for any calls in the call log with no normalized phone number and attempts to add
4349     * one if the number can be normalized.
4350     *
4351     * @param db The database.
4352     */
4353    private void upgradeToVersion903(SQLiteDatabase db) {
4354        // Find the calls in the call log with no normalized phone number.
4355        final Cursor c = db.rawQuery(
4356                "SELECT _id, number, countryiso FROM calls " +
4357                        " WHERE (normalized_number is null OR normalized_number = '') " +
4358                        " AND countryiso != '' AND countryiso is not null " +
4359                        " AND number != '' AND number is not null;",
4360                null
4361        );
4362
4363        try {
4364            if (c.getCount() == 0) {
4365                return;
4366            }
4367
4368            db.beginTransaction();
4369            try {
4370                c.moveToPosition(-1);
4371                while (c.moveToNext()) {
4372                    final long callId = c.getLong(0);
4373                    final String unNormalizedNumber = c.getString(1);
4374                    final String countryIso = c.getString(2);
4375
4376                    // Attempt to get normalized number.
4377                    String normalizedNumber = PhoneNumberUtils
4378                            .formatNumberToE164(unNormalizedNumber, countryIso);
4379
4380                    if (!TextUtils.isEmpty(normalizedNumber)) {
4381                        db.execSQL("UPDATE calls set normalized_number = ? " +
4382                                        "where _id = ?;",
4383                                new String[]{
4384                                        normalizedNumber,
4385                                        String.valueOf(callId),
4386                                }
4387                        );
4388                    }
4389                }
4390
4391                db.setTransactionSuccessful();
4392            } finally {
4393                db.endTransaction();
4394            }
4395        } finally {
4396            c.close();
4397        }
4398    }
4399
4400    /**
4401     * Updates the calls table in the database to include the call_duration and features columns.
4402     * @param db The database to update.
4403     */
4404    private void upgradeToVersion904(SQLiteDatabase db) {
4405        db.execSQL("ALTER TABLE calls ADD features INTEGER NOT NULL DEFAULT 0;");
4406        db.execSQL("ALTER TABLE calls ADD data_usage INTEGER;");
4407    }
4408
4409    /**
4410     * Adds the voicemail transcription to the Table.Calls
4411     */
4412    private void upgradeToVersion905(SQLiteDatabase db) {
4413        db.execSQL("ALTER TABLE calls ADD transcription TEXT;");
4414    }
4415
4416    /**
4417     * Upgrades the database with the new value for {@link PinnedPositions#UNPINNED}. In this
4418     * database version upgrade, the value is changed from 2147483647 (Integer.MAX_VALUE) to 0.
4419     *
4420     * The first pinned contact now starts from position 1.
4421     */
4422    @VisibleForTesting
4423    public void upgradeToVersion906(SQLiteDatabase db) {
4424        db.execSQL("UPDATE contacts SET pinned = pinned + 1"
4425                + " WHERE pinned >= 0 AND pinned < 2147483647;");
4426        db.execSQL("UPDATE raw_contacts SET pinned = pinned + 1"
4427                + " WHERE pinned >= 0 AND pinned < 2147483647;");
4428
4429        db.execSQL("UPDATE contacts SET pinned = 0"
4430                + " WHERE pinned = 2147483647;");
4431        db.execSQL("UPDATE raw_contacts SET pinned = 0"
4432                + " WHERE pinned = 2147483647;");
4433    }
4434
4435    private void upgradeToVersion908(SQLiteDatabase db) {
4436        db.execSQL("UPDATE contacts SET pinned = 0 WHERE pinned = 2147483647;");
4437        db.execSQL("UPDATE raw_contacts SET pinned = 0 WHERE pinned = 2147483647;");
4438    }
4439
4440    private void upgradeToVersion909(SQLiteDatabase db) {
4441        try {
4442            db.execSQL("ALTER TABLE calls ADD sub_id INTEGER DEFAULT -1;");
4443        } catch (SQLiteException e) {
4444            // The column already exists--copy over data
4445            db.execSQL("UPDATE calls SET subscription_component_name='com.android.phone/"
4446                    + "com.android.services.telephony.TelephonyConnectionService';");
4447            db.execSQL("UPDATE calls SET subscription_id=sub_id;");
4448        }
4449    }
4450
4451    /**
4452     * Delete any remaining rows in the calls table if the user is a profile of another user.
4453     * b/17096027
4454     */
4455    @VisibleForTesting
4456    public void upgradeToVersion910(SQLiteDatabase db) {
4457        final UserManager userManager = (UserManager) mContext.getSystemService(
4458                Context.USER_SERVICE);
4459        final UserInfo user = userManager.getUserInfo(userManager.getUserHandle());
4460        if (user.isManagedProfile()) {
4461            db.execSQL("DELETE FROM calls;");
4462        }
4463    }
4464
4465    /**
4466     * Add backup_id column to raw_contacts table and hash_id column to data table.
4467     */
4468    private void upgradeToVersion1000(SQLiteDatabase db) {
4469        db.execSQL("ALTER TABLE raw_contacts ADD backup_id TEXT;");
4470        db.execSQL("ALTER TABLE data ADD hash_id TEXT;");
4471        db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS raw_contacts_backup_id_account_id_index ON " +
4472                "raw_contacts (backup_id, account_id);");
4473        db.execSQL("CREATE INDEX IF NOT EXISTS data_hash_id_index ON data (hash_id);");
4474    }
4475
4476    @VisibleForTesting
4477    public void upgradeToVersion1002(SQLiteDatabase db) {
4478        db.execSQL("DROP TABLE IF EXISTS pre_authorized_uris;");
4479        db.execSQL("CREATE TABLE pre_authorized_uris ("+
4480                "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
4481                "uri STRING NOT NULL, " +
4482                "expiration INTEGER NOT NULL DEFAULT 0);");
4483    }
4484
4485    public void upgradeToVersion1003(SQLiteDatabase db) {
4486        db.execSQL("ALTER TABLE calls ADD phone_account_address TEXT;");
4487
4488        // After version 1003, we are using the ICC ID as the phone-account ID. This code updates
4489        // any existing telephony connection-service calllog entries to the ICC ID from the
4490        // previously used subscription ID.
4491        // TODO: This is inconsistent, depending on the initialization state of SubscriptionManager.
4492        //       Sometimes it returns zero subscriptions. May want to move this upgrade to run after
4493        //       ON_BOOT_COMPLETE instead of PRE_BOOT_COMPLETE.
4494        SubscriptionManager sm = SubscriptionManager.from(mContext);
4495        if (sm != null) {
4496            Log.i(TAG, "count: " + sm.getAllSubscriptionInfoCount());
4497            for (SubscriptionInfo info : sm.getAllSubscriptionInfoList()) {
4498                String iccId = info.getIccId();
4499                int subId = info.getSubscriptionId();
4500                if (!TextUtils.isEmpty(iccId) &&
4501                        subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
4502                    StringBuilder sb = new StringBuilder();
4503                    sb.append("UPDATE calls SET subscription_id=");
4504                    DatabaseUtils.appendEscapedSQLString(sb, iccId);
4505                    sb.append(" WHERE subscription_id=");
4506                    sb.append(subId);
4507                    sb.append(" AND subscription_component_name='com.android.phone/"
4508                            + "com.android.services.telephony.TelephonyConnectionService';");
4509
4510                    db.execSQL(sb.toString());
4511                }
4512            }
4513        }
4514    }
4515
4516    /**
4517     * Add a "hidden" column for call log entries we want to hide after an upgrade until the user
4518     * adds the right phone account to the device.
4519     */
4520    public void upgradeToVersion1004(SQLiteDatabase db) {
4521        db.execSQL("ALTER TABLE calls ADD phone_account_hidden INTEGER NOT NULL DEFAULT 0;");
4522    }
4523
4524    public void upgradeToVersion1005(SQLiteDatabase db) {
4525        db.execSQL("ALTER TABLE calls ADD photo_uri TEXT;");
4526    }
4527
4528    /**
4529     * The try/catch pattern exists because some devices have the upgrade and some do not. This is
4530     * because the below updates were merged into version 1005 after some devices had already
4531     * upgraded to version 1005 and hence did not receive the below upgrades.
4532     */
4533    public void upgradeToVersion1007(SQLiteDatabase db) {
4534        try {
4535            // Add multi-sim fields
4536            db.execSQL("ALTER TABLE voicemail_status ADD phone_account_component_name TEXT;");
4537            db.execSQL("ALTER TABLE voicemail_status ADD phone_account_id TEXT;");
4538
4539            // For use by the sync adapter
4540            db.execSQL("ALTER TABLE calls ADD dirty INTEGER NOT NULL DEFAULT 0;");
4541            db.execSQL("ALTER TABLE calls ADD deleted INTEGER NOT NULL DEFAULT 0;");
4542        } catch (SQLiteException e) {
4543            // These columns already exist. Do nothing.
4544            // Log verbose because this should be the majority case.
4545            Log.v(TAG, "Version 1007: Columns already exist, skipping upgrade steps.");
4546        }
4547  }
4548
4549
4550    public void upgradeToVersion1009(SQLiteDatabase db) {
4551        try {
4552            db.execSQL("ALTER TABLE data ADD carrier_presence INTEGER NOT NULL DEFAULT 0");
4553        } catch (SQLiteException ignore) {
4554        }
4555    }
4556
4557    private void upgradeToVersion1100(SQLiteDatabase db) {
4558        db.execSQL("ALTER TABLE raw_contacts ADD metadata_dirty INTEGER NOT NULL DEFAULT 0;");
4559    }
4560
4561    // Data.hash_id column is used for metadata backup, and this upgrade is to generate
4562    // hash_id column. Usually data1 and data2 are two main columns to store data info.
4563    // But for photo, we don't use data1 and data2, instead, use data15 to store photo blob.
4564    // So this upgrade generates hash_id from (data1 + data2) or (data15) using sha-1.
4565    private void upgradeToVersion1101(SQLiteDatabase db) {
4566        final SQLiteStatement update = db.compileStatement(
4567                "UPDATE " + Tables.DATA +
4568                " SET " + Data.HASH_ID + "=?" +
4569                " WHERE " + Data._ID + "=?"
4570        );
4571        final String selection = Data.HASH_ID + " IS NULL";
4572        final Cursor c = db.query(Tables.DATA, new String[] {Data._ID, Data.DATA1, Data.DATA2,
4573                        Data.DATA15},
4574                selection, null, null, null, null);
4575        try {
4576            while (c.moveToNext()) {
4577                final long dataId = c.getLong(0);
4578                final String data1 = c.getString(1);
4579                final String data2 = c.getString(2);
4580                final byte[] data15 = c.getBlob(3);
4581                final String hashId = generateHashId(data1, data2, data15);
4582                if (!TextUtils.isEmpty(hashId)) {
4583                    update.bindString(1, hashId);
4584                    update.bindLong(2, dataId);
4585                    update.execute();
4586                }
4587            }
4588        } finally {
4589            c.close();
4590        }
4591    }
4592
4593    /**
4594     * Add new metadata_sync table to cache the meta data on raw contacts level from server before
4595     * they are merged into other CP2 tables. The data column is the blob column containing all
4596     * the backed up metadata for this raw_contact. This table should only be used by metadata
4597     * sync adapter.
4598     */
4599    public void upgradeToVersion1104(SQLiteDatabase db) {
4600        db.execSQL("DROP TABLE IF EXISTS metadata_sync;");
4601        db.execSQL("CREATE TABLE metadata_sync (" +
4602                "_id INTEGER PRIMARY KEY AUTOINCREMENT, raw_contact_backup_id TEXT NOT NULL, " +
4603                "account_id INTEGER NOT NULL, data TEXT, deleted INTEGER NOT NULL DEFAULT 0);");
4604        db.execSQL("CREATE UNIQUE INDEX metadata_sync_index ON metadata_sync (" +
4605                "raw_contact_backup_id, account_id);");
4606    }
4607
4608    /**
4609     * Add new metadata_sync_state table to store the metadata sync state for a set of accounts.
4610     */
4611    public void upgradeToVersion1105(SQLiteDatabase db) {
4612        db.execSQL("DROP TABLE IF EXISTS metadata_sync_state;");
4613        db.execSQL("CREATE TABLE metadata_sync_state (" +
4614                "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
4615                "account_id INTEGER NOT NULL, state BLOB);");
4616        db.execSQL("CREATE UNIQUE INDEX metadata_sync_state_index ON metadata_sync_state (" +
4617                "account_id);");
4618    }
4619
4620    /**
4621     * Generate hash_id from data1, data2 and data15 columns.
4622     * If one of data1 and data2 is not null, using data1 and data2 to get hash_id,
4623     * otherwise, using data15 to generate.
4624     */
4625    public String generateHashId(String data1, String data2, byte[] data15) {
4626        final StringBuilder sb = new StringBuilder();
4627        byte[] hashInput = null;
4628        if (!TextUtils.isEmpty(data1) || !TextUtils.isEmpty(data2)) {
4629            sb.append(data1);
4630            sb.append(data2);
4631            hashInput = sb.toString().getBytes();
4632        } else if (data15 != null) {
4633            hashInput = data15;
4634        }
4635        if (hashInput != null) {
4636            final String hashId = generateHashIdForData(hashInput);
4637            return hashId;
4638        } else {
4639            return null;
4640        }
4641    }
4642
4643    // Use SHA-1 hash method to generate hash string for the input.
4644    @VisibleForTesting
4645    String generateHashIdForData(byte[] input) {
4646        synchronized (mMessageDigest) {
4647            final byte[] hashResult = mMessageDigest.digest(input);
4648            return Base64.encodeToString(hashResult, Base64.DEFAULT);
4649        }
4650    }
4651
4652    public String extractHandleFromEmailAddress(String email) {
4653        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email);
4654        if (tokens.length == 0) {
4655            return null;
4656        }
4657
4658        String address = tokens[0].getAddress();
4659        int index = address.indexOf('@');
4660        if (index != -1) {
4661            return address.substring(0, index);
4662        }
4663        return null;
4664    }
4665
4666    public String extractAddressFromEmailAddress(String email) {
4667        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email);
4668        if (tokens.length == 0) {
4669            return null;
4670        }
4671        return tokens[0].getAddress().trim();
4672    }
4673
4674    private static long lookupMimeTypeId(SQLiteDatabase db, String mimeType) {
4675        try {
4676            return DatabaseUtils.longForQuery(db,
4677                    "SELECT " + MimetypesColumns._ID +
4678                    " FROM " + Tables.MIMETYPES +
4679                    " WHERE " + MimetypesColumns.MIMETYPE
4680                            + "='" + mimeType + "'", null);
4681        } catch (SQLiteDoneException e) {
4682            // No rows of this type in the database.
4683            return -1;
4684        }
4685    }
4686
4687    private void bindString(SQLiteStatement stmt, int index, String value) {
4688        if (value == null) {
4689            stmt.bindNull(index);
4690        } else {
4691            stmt.bindString(index, value);
4692        }
4693    }
4694
4695    private void bindLong(SQLiteStatement stmt, int index, Number value) {
4696        if (value == null) {
4697            stmt.bindNull(index);
4698        } else {
4699            stmt.bindLong(index, value.longValue());
4700        }
4701    }
4702
4703    /**
4704     * Add a string like "(((column1) = (column2)) OR ((column1) IS NULL AND (column2) IS NULL))"
4705     */
4706    private static StringBuilder addJoinExpressionAllowingNull(
4707            StringBuilder sb, String column1, String column2) {
4708
4709        sb.append("(((").append(column1).append(")=(").append(column2);
4710        sb.append("))OR((");
4711        sb.append(column1).append(") IS NULL AND (").append(column2).append(") IS NULL))");
4712        return sb;
4713    }
4714
4715    /**
4716     * Adds index stats into the SQLite database to force it to always use the lookup indexes.
4717     *
4718     * Note if you drop a table or an index, the corresponding row will be removed from this table.
4719     * Make sure to call this method after such operations.
4720     */
4721    private void updateSqliteStats(SQLiteDatabase db) {
4722        if (!mDatabaseOptimizationEnabled) {
4723            return;  // We don't use sqlite_stat1 during tests.
4724        }
4725
4726        // Specific stats strings are based on an actual large database after running ANALYZE
4727        // Important here are relative sizes. Raw-Contacts is slightly bigger than Contacts
4728        // Warning: Missing tables in here will make SQLite assume to contain 1000000 rows,
4729        // which can lead to catastrophic query plans for small tables
4730
4731        // What these numbers mean is described in this file.
4732        // http://www.sqlite.org/cgi/src/finfo?name=src/analyze.c
4733
4734        // Excerpt:
4735        /*
4736        ** Format of sqlite_stat1:
4737        **
4738        ** There is normally one row per index, with the index identified by the
4739        ** name in the idx column.  The tbl column is the name of the table to
4740        ** which the index belongs.  In each such row, the stat column will be
4741        ** a string consisting of a list of integers.  The first integer in this
4742        ** list is the number of rows in the index and in the table.  The second
4743        ** integer is the average number of rows in the index that have the same
4744        ** value in the first column of the index.  The third integer is the average
4745        ** number of rows in the index that have the same value for the first two
4746        ** columns.  The N-th integer (for N>1) is the average number of rows in
4747        ** the index which have the same value for the first N-1 columns.  For
4748        ** a K-column index, there will be K+1 integers in the stat column.  If
4749        ** the index is unique, then the last integer will be 1.
4750        **
4751        ** The list of integers in the stat column can optionally be followed
4752        ** by the keyword "unordered".  The "unordered" keyword, if it is present,
4753        ** must be separated from the last integer by a single space.  If the
4754        ** "unordered" keyword is present, then the query planner assumes that
4755        ** the index is unordered and will not use the index for a range query.
4756        **
4757        ** If the sqlite_stat1.idx column is NULL, then the sqlite_stat1.stat
4758        ** column contains a single integer which is the (estimated) number of
4759        ** rows in the table identified by sqlite_stat1.tbl.
4760        */
4761
4762        try {
4763            db.execSQL("DELETE FROM sqlite_stat1");
4764            updateIndexStats(db, Tables.CONTACTS,
4765                    "contacts_has_phone_index", "9000 500");
4766            updateIndexStats(db, Tables.CONTACTS,
4767                    "contacts_name_raw_contact_id_index", "9000 1");
4768            updateIndexStats(db, Tables.CONTACTS, MoreDatabaseUtils.buildIndexName(Tables.CONTACTS,
4769                    Contacts.CONTACT_LAST_UPDATED_TIMESTAMP), "9000 10");
4770
4771            updateIndexStats(db, Tables.RAW_CONTACTS,
4772                    "raw_contacts_contact_id_index", "10000 2");
4773            updateIndexStats(db, Tables.RAW_CONTACTS,
4774                    "raw_contact_sort_key2_index", "10000 2");
4775            updateIndexStats(db, Tables.RAW_CONTACTS,
4776                    "raw_contact_sort_key1_index", "10000 2");
4777            updateIndexStats(db, Tables.RAW_CONTACTS,
4778                    "raw_contacts_source_id_account_id_index", "10000 1 1");
4779
4780            updateIndexStats(db, Tables.NAME_LOOKUP,
4781                    "name_lookup_raw_contact_id_index", "35000 4");
4782            updateIndexStats(db, Tables.NAME_LOOKUP,
4783                    "name_lookup_index", "35000 2 2 2 1");
4784            updateIndexStats(db, Tables.NAME_LOOKUP,
4785                    "sqlite_autoindex_name_lookup_1", "35000 3 2 1");
4786
4787            updateIndexStats(db, Tables.PHONE_LOOKUP,
4788                    "phone_lookup_index", "3500 3 2 1");
4789            updateIndexStats(db, Tables.PHONE_LOOKUP,
4790                    "phone_lookup_min_match_index", "3500 3 2 2");
4791            updateIndexStats(db, Tables.PHONE_LOOKUP,
4792                    "phone_lookup_data_id_min_match_index", "3500 2 2");
4793
4794            updateIndexStats(db, Tables.DATA,
4795                    "data_mimetype_data1_index", "60000 5000 2");
4796            updateIndexStats(db, Tables.DATA,
4797                    "data_raw_contact_id", "60000 10");
4798
4799            updateIndexStats(db, Tables.GROUPS,
4800                    "groups_source_id_account_id_index", "50 2 2 1 1");
4801
4802            updateIndexStats(db, Tables.NICKNAME_LOOKUP,
4803                    "nickname_lookup_index", "500 2 1");
4804
4805            updateIndexStats(db, Tables.CALLS,
4806                    null, "250");
4807
4808            updateIndexStats(db, Tables.STATUS_UPDATES,
4809                    null, "100");
4810
4811            updateIndexStats(db, Tables.STREAM_ITEMS,
4812                    null, "500");
4813            updateIndexStats(db, Tables.STREAM_ITEM_PHOTOS,
4814                    null, "50");
4815
4816            updateIndexStats(db, Tables.VOICEMAIL_STATUS,
4817                    null, "5");
4818
4819            updateIndexStats(db, Tables.ACCOUNTS,
4820                    null, "3");
4821
4822            updateIndexStats(db, Tables.PRE_AUTHORIZED_URIS,
4823                    null, "1");
4824
4825            updateIndexStats(db, Tables.VISIBLE_CONTACTS,
4826                    null, "2000");
4827
4828            updateIndexStats(db, Tables.PHOTO_FILES,
4829                    null, "50");
4830
4831            updateIndexStats(db, Tables.DEFAULT_DIRECTORY,
4832                    null, "1500");
4833
4834            updateIndexStats(db, Tables.MIMETYPES,
4835                    "mime_type", "18 1");
4836
4837            updateIndexStats(db, Tables.DATA_USAGE_STAT,
4838                    "data_usage_stat_index", "20 2 1");
4839
4840            updateIndexStats(db, Tables.METADATA_SYNC,
4841                    "metadata_sync_index", "10000 1 1");
4842
4843            // Tiny tables
4844            updateIndexStats(db, Tables.AGGREGATION_EXCEPTIONS,
4845                    null, "10");
4846            updateIndexStats(db, Tables.SETTINGS,
4847                    null, "10");
4848            updateIndexStats(db, Tables.PACKAGES,
4849                    null, "0");
4850            updateIndexStats(db, Tables.DIRECTORIES,
4851                    null, "3");
4852            updateIndexStats(db, LegacyApiSupport.LegacyTables.SETTINGS,
4853                    null, "0");
4854            updateIndexStats(db, "android_metadata",
4855                    null, "1");
4856            updateIndexStats(db, "_sync_state",
4857                    "sqlite_autoindex__sync_state_1", "2 1 1");
4858            updateIndexStats(db, "_sync_state_metadata",
4859                    null, "1");
4860            updateIndexStats(db, "properties",
4861                    "sqlite_autoindex_properties_1", "4 1");
4862
4863            updateIndexStats(db, Tables.METADATA_SYNC_STATE,
4864                    "metadata_sync_state_index", "2 1 1");
4865
4866            // Search index
4867            updateIndexStats(db, "search_index_docsize",
4868                    null, "9000");
4869            updateIndexStats(db, "search_index_content",
4870                    null, "9000");
4871            updateIndexStats(db, "search_index_stat",
4872                    null, "1");
4873            updateIndexStats(db, "search_index_segments",
4874                    null, "450");
4875            updateIndexStats(db, "search_index_segdir",
4876                    "sqlite_autoindex_search_index_segdir_1", "9 5 1");
4877
4878            // Force SQLite to reload sqlite_stat1.
4879            db.execSQL("ANALYZE sqlite_master;");
4880        } catch (SQLException e) {
4881            Log.e(TAG, "Could not update index stats", e);
4882        }
4883    }
4884
4885    /**
4886     * Stores statistics for a given index.
4887     *
4888     * @param stats has the following structure: the first index is the expected size of
4889     * the table.  The following integer(s) are the expected number of records selected with the
4890     * index.  There should be one integer per indexed column.
4891     */
4892    private void updateIndexStats(SQLiteDatabase db, String table, String index, String stats) {
4893        if (index == null) {
4894            db.execSQL("DELETE FROM sqlite_stat1 WHERE tbl=? AND idx IS NULL",
4895                    new String[] {table});
4896        } else {
4897            db.execSQL("DELETE FROM sqlite_stat1 WHERE tbl=? AND idx=?",
4898                    new String[] {table, index});
4899        }
4900        db.execSQL("INSERT INTO sqlite_stat1 (tbl,idx,stat) VALUES (?,?,?)",
4901                new String[] {table, index, stats});
4902    }
4903
4904    /**
4905     * Wipes all data except mime type and package lookup tables.
4906     */
4907    public void wipeData() {
4908        SQLiteDatabase db = getWritableDatabase();
4909
4910        db.execSQL("DELETE FROM " + Tables.ACCOUNTS + ";");
4911        db.execSQL("DELETE FROM " + Tables.CONTACTS + ";");
4912        db.execSQL("DELETE FROM " + Tables.RAW_CONTACTS + ";");
4913        db.execSQL("DELETE FROM " + Tables.STREAM_ITEMS + ";");
4914        db.execSQL("DELETE FROM " + Tables.STREAM_ITEM_PHOTOS + ";");
4915        db.execSQL("DELETE FROM " + Tables.PHOTO_FILES + ";");
4916        db.execSQL("DELETE FROM " + Tables.DATA + ";");
4917        db.execSQL("DELETE FROM " + Tables.PHONE_LOOKUP + ";");
4918        db.execSQL("DELETE FROM " + Tables.NAME_LOOKUP + ";");
4919        db.execSQL("DELETE FROM " + Tables.GROUPS + ";");
4920        db.execSQL("DELETE FROM " + Tables.AGGREGATION_EXCEPTIONS + ";");
4921        db.execSQL("DELETE FROM " + Tables.SETTINGS + ";");
4922        db.execSQL("DELETE FROM " + Tables.CALLS + ";");
4923        db.execSQL("DELETE FROM " + Tables.DIRECTORIES + ";");
4924        db.execSQL("DELETE FROM " + Tables.SEARCH_INDEX + ";");
4925        db.execSQL("DELETE FROM " + Tables.DELETED_CONTACTS + ";");
4926        db.execSQL("DELETE FROM " + Tables.MIMETYPES + ";");
4927        db.execSQL("DELETE FROM " + Tables.PACKAGES + ";");
4928
4929        initializeCache(db);
4930
4931        // Note: we are not removing reference data from Tables.NICKNAME_LOOKUP
4932    }
4933
4934    public NameSplitter createNameSplitter() {
4935        return createNameSplitter(Locale.getDefault());
4936    }
4937
4938    public NameSplitter createNameSplitter(Locale locale) {
4939        mNameSplitter = new NameSplitter(
4940                mContext.getString(com.android.internal.R.string.common_name_prefixes),
4941                mContext.getString(com.android.internal.R.string.common_last_name_prefixes),
4942                mContext.getString(com.android.internal.R.string.common_name_suffixes),
4943                mContext.getString(com.android.internal.R.string.common_name_conjunctions),
4944                locale);
4945        return mNameSplitter;
4946    }
4947
4948    /**
4949     * Return the {@link ApplicationInfo#uid} for the given package name.
4950     */
4951    public static int getUidForPackageName(PackageManager pm, String packageName) {
4952        try {
4953            ApplicationInfo clientInfo = pm.getApplicationInfo(packageName, 0 /* no flags */);
4954            return clientInfo.uid;
4955        } catch (NameNotFoundException e) {
4956            throw new RuntimeException(e);
4957        }
4958    }
4959
4960    /**
4961     * Internal method used by {@link #getPackageId} and {@link #getMimeTypeId}.
4962     *
4963     * Note in the contacts provider we avoid using synchronization because it could risk deadlocks.
4964     * So here, instead of using locks, we use ConcurrentHashMap + retry.
4965     *
4966     * Note we can't use a transaction here becuause this method is called from
4967     * onCommitTransaction() too, unfortunately.
4968     */
4969    private static long getIdCached(SQLiteDatabase db, ConcurrentHashMap<String, Long> cache,
4970            String querySql, String insertSql, String value) {
4971        // First, try the in-memory cache.
4972        if (cache.containsKey(value)) {
4973            return cache.get(value);
4974        }
4975
4976        // Then, try the database.
4977        long id = queryIdWithOneArg(db, querySql, value);
4978        if (id >= 0) {
4979            cache.put(value, id);
4980            return id;
4981        }
4982
4983        // Not found in the database.  Try inserting.
4984        id = insertWithOneArgAndReturnId(db, insertSql, value);
4985        if (id >= 0) {
4986            cache.put(value, id);
4987            return id;
4988        }
4989
4990        // Insert failed, which means a race.  Let's retry...
4991
4992        // We log here to detect an infinity loop (which shouldn't happen).
4993        // Conflicts should be pretty rare, so it shouldn't spam logcat.
4994        Log.i(TAG, "Cache conflict detected: value=" + value);
4995        try {
4996            Thread.sleep(1); // Just wait a little bit before retry.
4997        } catch (InterruptedException ignore) {
4998        }
4999        return getIdCached(db, cache, querySql, insertSql, value);
5000    }
5001
5002    @VisibleForTesting
5003    static long queryIdWithOneArg(SQLiteDatabase db, String sql, String sqlArgument) {
5004        final SQLiteStatement query = db.compileStatement(sql);
5005        try {
5006            DatabaseUtils.bindObjectToProgram(query, 1, sqlArgument);
5007            try {
5008                return query.simpleQueryForLong();
5009            } catch (SQLiteDoneException notFound) {
5010                return -1;
5011            }
5012        } finally {
5013            query.close();
5014        }
5015    }
5016
5017    @VisibleForTesting
5018    static long insertWithOneArgAndReturnId(SQLiteDatabase db, String sql, String sqlArgument) {
5019        final SQLiteStatement insert = db.compileStatement(sql);
5020        try {
5021            DatabaseUtils.bindObjectToProgram(insert, 1, sqlArgument);
5022            try {
5023                return insert.executeInsert();
5024            } catch (SQLiteConstraintException conflict) {
5025                return -1;
5026            }
5027        } finally {
5028            insert.close();
5029        }
5030    }
5031
5032    /**
5033     * Convert a package name into an integer, using {@link Tables#PACKAGES} for
5034     * lookups and possible allocation of new IDs as needed.
5035     */
5036    public long getPackageId(String packageName) {
5037        final String query =
5038                "SELECT " + PackagesColumns._ID +
5039                " FROM " + Tables.PACKAGES +
5040                " WHERE " + PackagesColumns.PACKAGE + "=?";
5041
5042        final String insert =
5043                "INSERT INTO " + Tables.PACKAGES + "("
5044                        + PackagesColumns.PACKAGE +
5045                ") VALUES (?)";
5046        return getIdCached(getWritableDatabase(), mPackageCache, query, insert, packageName);
5047    }
5048
5049    /**
5050     * Convert a mimetype into an integer, using {@link Tables#MIMETYPES} for
5051     * lookups and possible allocation of new IDs as needed.
5052     */
5053    public long getMimeTypeId(String mimetype) {
5054        return lookupMimeTypeId(mimetype, getWritableDatabase());
5055    }
5056
5057    private long lookupMimeTypeId(String mimetype, SQLiteDatabase db) {
5058        final String query =
5059                "SELECT " + MimetypesColumns._ID +
5060                " FROM " + Tables.MIMETYPES +
5061                " WHERE " + MimetypesColumns.MIMETYPE + "=?";
5062
5063        final String insert =
5064                "INSERT INTO " + Tables.MIMETYPES + "("
5065                        + MimetypesColumns.MIMETYPE +
5066                ") VALUES (?)";
5067
5068        return getIdCached(db, mMimetypeCache, query, insert, mimetype);
5069    }
5070
5071    public long getMimeTypeIdForStructuredName() {
5072        return mMimeTypeIdStructuredName;
5073    }
5074
5075    public long getMimeTypeIdForStructuredPostal() {
5076        return mMimeTypeIdStructuredPostal;
5077    }
5078
5079    public long getMimeTypeIdForOrganization() {
5080        return mMimeTypeIdOrganization;
5081    }
5082
5083    public long getMimeTypeIdForIm() {
5084        return mMimeTypeIdIm;
5085    }
5086
5087    public long getMimeTypeIdForEmail() {
5088        return mMimeTypeIdEmail;
5089    }
5090
5091    public long getMimeTypeIdForPhone() {
5092        return mMimeTypeIdPhone;
5093    }
5094
5095    public long getMimeTypeIdForSip() {
5096        return mMimeTypeIdSip;
5097    }
5098
5099    /**
5100     * Returns a {@link ContactsContract.DisplayNameSources} value based on {@param mimeTypeId}.
5101     * This does not return {@link ContactsContract.DisplayNameSources#STRUCTURED_PHONETIC_NAME}.
5102     * The calling client needs to inspect the structured name itself to distinguish between
5103     * {@link ContactsContract.DisplayNameSources#STRUCTURED_NAME} and
5104     * {@code STRUCTURED_PHONETIC_NAME}.
5105     */
5106    private int getDisplayNameSourceForMimeTypeId(int mimeTypeId) {
5107        if (mimeTypeId == mMimeTypeIdStructuredName) {
5108            return DisplayNameSources.STRUCTURED_NAME;
5109        }
5110        if (mimeTypeId == mMimeTypeIdEmail) {
5111            return DisplayNameSources.EMAIL;
5112        }
5113        if (mimeTypeId == mMimeTypeIdPhone) {
5114            return DisplayNameSources.PHONE;
5115        }
5116        if (mimeTypeId == mMimeTypeIdOrganization) {
5117            return DisplayNameSources.ORGANIZATION;
5118        }
5119        if (mimeTypeId == mMimeTypeIdNickname) {
5120            return DisplayNameSources.NICKNAME;
5121        }
5122        return DisplayNameSources.UNDEFINED;
5123    }
5124
5125    /**
5126     * Find the mimetype for the given {@link Data#_ID}.
5127     */
5128    public String getDataMimeType(long dataId) {
5129        if (mDataMimetypeQuery == null) {
5130            mDataMimetypeQuery = getWritableDatabase().compileStatement(
5131                    "SELECT " + MimetypesColumns.MIMETYPE +
5132                    " FROM " + Tables.DATA_JOIN_MIMETYPES +
5133                    " WHERE " + Tables.DATA + "." + Data._ID + "=?");
5134        }
5135        try {
5136            // Try database query to find mimetype
5137            DatabaseUtils.bindObjectToProgram(mDataMimetypeQuery, 1, dataId);
5138            String mimetype = mDataMimetypeQuery.simpleQueryForString();
5139            return mimetype;
5140        } catch (SQLiteDoneException e) {
5141            // No valid mapping found, so return null
5142            return null;
5143        }
5144    }
5145
5146    public void invalidateAllCache() {
5147        Log.w(TAG, "invalidateAllCache: [" + getClass().getSimpleName() + "]");
5148
5149        mMimetypeCache.clear();
5150        mPackageCache.clear();
5151    }
5152
5153    /**
5154     * Gets all accounts in the accounts table.
5155     */
5156    public Set<AccountWithDataSet> getAllAccountsWithDataSets() {
5157        final Set<AccountWithDataSet> result = Sets.newHashSet();
5158        Cursor c = getReadableDatabase().rawQuery(
5159                "SELECT DISTINCT " +  AccountsColumns._ID + "," + AccountsColumns.ACCOUNT_NAME +
5160                "," + AccountsColumns.ACCOUNT_TYPE + "," + AccountsColumns.DATA_SET +
5161                " FROM " + Tables.ACCOUNTS, null);
5162        try {
5163            while (c.moveToNext()) {
5164                result.add(AccountWithDataSet.get(c.getString(1), c.getString(2), c.getString(3)));
5165            }
5166        } finally {
5167            c.close();
5168        }
5169        return result;
5170    }
5171
5172    /**
5173     * @return ID of the specified account, or null if the account doesn't exist.
5174     */
5175    public Long getAccountIdOrNull(AccountWithDataSet accountWithDataSet) {
5176        if (accountWithDataSet == null) {
5177            accountWithDataSet = AccountWithDataSet.LOCAL;
5178        }
5179        final SQLiteStatement select = getWritableDatabase().compileStatement(
5180                "SELECT " + AccountsColumns._ID +
5181                        " FROM " + Tables.ACCOUNTS +
5182                        " WHERE " +
5183                        "((?1 IS NULL AND " + AccountsColumns.ACCOUNT_NAME + " IS NULL) OR " +
5184                        "(" + AccountsColumns.ACCOUNT_NAME + "=?1)) AND " +
5185                        "((?2 IS NULL AND " + AccountsColumns.ACCOUNT_TYPE + " IS NULL) OR " +
5186                        "(" + AccountsColumns.ACCOUNT_TYPE + "=?2)) AND " +
5187                        "((?3 IS NULL AND " + AccountsColumns.DATA_SET + " IS NULL) OR " +
5188                        "(" + AccountsColumns.DATA_SET + "=?3))");
5189        try {
5190            DatabaseUtils.bindObjectToProgram(select, 1, accountWithDataSet.getAccountName());
5191            DatabaseUtils.bindObjectToProgram(select, 2, accountWithDataSet.getAccountType());
5192            DatabaseUtils.bindObjectToProgram(select, 3, accountWithDataSet.getDataSet());
5193            try {
5194                return select.simpleQueryForLong();
5195            } catch (SQLiteDoneException notFound) {
5196                return null;
5197            }
5198        } finally {
5199            select.close();
5200        }
5201    }
5202
5203    /**
5204     * @return ID of the specified account.  This method will create a record in the accounts table
5205     *     if the account doesn't exist in the accounts table.
5206     *
5207     * This must be used in a transaction, so there's no need for synchronization.
5208     */
5209    public long getOrCreateAccountIdInTransaction(AccountWithDataSet accountWithDataSet) {
5210        if (accountWithDataSet == null) {
5211            accountWithDataSet = AccountWithDataSet.LOCAL;
5212        }
5213        Long id = getAccountIdOrNull(accountWithDataSet);
5214        if (id != null) {
5215            return id;
5216        }
5217        final SQLiteStatement insert = getWritableDatabase().compileStatement(
5218                "INSERT INTO " + Tables.ACCOUNTS +
5219                " (" + AccountsColumns.ACCOUNT_NAME + ", " +
5220                AccountsColumns.ACCOUNT_TYPE + ", " +
5221                AccountsColumns.DATA_SET + ") VALUES (?, ?, ?)");
5222        try {
5223            DatabaseUtils.bindObjectToProgram(insert, 1, accountWithDataSet.getAccountName());
5224            DatabaseUtils.bindObjectToProgram(insert, 2, accountWithDataSet.getAccountType());
5225            DatabaseUtils.bindObjectToProgram(insert, 3, accountWithDataSet.getDataSet());
5226            id = insert.executeInsert();
5227        } finally {
5228            insert.close();
5229        }
5230
5231        return id;
5232    }
5233
5234    /**
5235     * Update {@link Contacts#IN_VISIBLE_GROUP} for all contacts.
5236     */
5237    public void updateAllVisible() {
5238        updateCustomContactVisibility(getWritableDatabase(), -1);
5239    }
5240
5241    /**
5242     * Updates contact visibility and return true iff the visibility was actually changed.
5243     */
5244    public boolean updateContactVisibleOnlyIfChanged(TransactionContext txContext, long contactId) {
5245        return updateContactVisible(txContext, contactId, true);
5246    }
5247
5248    /**
5249     * Update {@link Contacts#IN_VISIBLE_GROUP} and
5250     * {@link Tables#DEFAULT_DIRECTORY} for a specific contact.
5251     */
5252    public void updateContactVisible(TransactionContext txContext, long contactId) {
5253        updateContactVisible(txContext, contactId, false);
5254    }
5255
5256    public boolean updateContactVisible(
5257            TransactionContext txContext, long contactId, boolean onlyIfChanged) {
5258        SQLiteDatabase db = getWritableDatabase();
5259        updateCustomContactVisibility(db, contactId);
5260
5261        String contactIdAsString = String.valueOf(contactId);
5262        long mimetype = getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
5263
5264        // The contact will be included in the default directory if contains a raw contact that is
5265        // in any group or in an account that does not have any AUTO_ADD groups.
5266        boolean newVisibility = DatabaseUtils.longForQuery(db,
5267                "SELECT EXISTS (" +
5268                    "SELECT " + RawContacts.CONTACT_ID +
5269                    " FROM " + Tables.RAW_CONTACTS +
5270                    " JOIN " + Tables.DATA +
5271                    "   ON (" + RawContactsColumns.CONCRETE_ID + "="
5272                            + Data.RAW_CONTACT_ID + ")" +
5273                    " WHERE " + RawContacts.CONTACT_ID + "=?1" +
5274                    "   AND " + DataColumns.MIMETYPE_ID + "=?2" +
5275                ") OR EXISTS (" +
5276                    "SELECT " + RawContacts._ID +
5277                    " FROM " + Tables.RAW_CONTACTS +
5278                    " WHERE " + RawContacts.CONTACT_ID + "=?1" +
5279                    "   AND NOT EXISTS" +
5280                        " (SELECT " + Groups._ID +
5281                        "  FROM " + Tables.GROUPS +
5282                        "  WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID + " = "
5283                                + GroupsColumns.CONCRETE_ACCOUNT_ID +
5284                        "  AND " + Groups.AUTO_ADD + " != 0" +
5285                        ")" +
5286                ") OR EXISTS (" +
5287                    "SELECT " + RawContacts._ID +
5288                    " FROM " + Tables.RAW_CONTACTS +
5289                    " WHERE " + RawContacts.CONTACT_ID + "=?1" +
5290                    "   AND " + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" +
5291                        Clauses.LOCAL_ACCOUNT_ID +
5292                ")",
5293                new String[] {
5294                    contactIdAsString,
5295                    String.valueOf(mimetype)
5296                }) != 0;
5297
5298        if (onlyIfChanged) {
5299            boolean oldVisibility = isContactInDefaultDirectory(db, contactId);
5300            if (oldVisibility == newVisibility) {
5301                return false;
5302            }
5303        }
5304
5305        if (newVisibility) {
5306            db.execSQL("INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY + " VALUES(?)",
5307                    new String[] {contactIdAsString});
5308            txContext.invalidateSearchIndexForContact(contactId);
5309        } else {
5310            db.execSQL("DELETE FROM " + Tables.DEFAULT_DIRECTORY +
5311                        " WHERE " + Contacts._ID + "=?",
5312                    new String[] {contactIdAsString});
5313            db.execSQL("DELETE FROM " + Tables.SEARCH_INDEX +
5314                        " WHERE " + SearchIndexColumns.CONTACT_ID + "=CAST(? AS int)",
5315                    new String[] {contactIdAsString});
5316        }
5317        return true;
5318    }
5319
5320    public boolean isContactInDefaultDirectory(SQLiteDatabase db, long contactId) {
5321        if (mContactInDefaultDirectoryQuery == null) {
5322            mContactInDefaultDirectoryQuery = db.compileStatement(
5323                    "SELECT EXISTS (" +
5324                            "SELECT 1 FROM " + Tables.DEFAULT_DIRECTORY +
5325                            " WHERE " + Contacts._ID + "=?)");
5326        }
5327        mContactInDefaultDirectoryQuery.bindLong(1, contactId);
5328        return mContactInDefaultDirectoryQuery.simpleQueryForLong() != 0;
5329    }
5330
5331    /**
5332     * Update the visible_contacts table according to the current visibility of contacts, which
5333     * is defined by {@link Clauses#CONTACT_IS_VISIBLE}.
5334     *
5335     * If {@code optionalContactId} is non-negative, it'll update only for the specified contact.
5336     */
5337    private void updateCustomContactVisibility(SQLiteDatabase db, long optionalContactId) {
5338        final long groupMembershipMimetypeId = getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
5339        String[] selectionArgs = new String[] {String.valueOf(groupMembershipMimetypeId)};
5340
5341        final String contactIdSelect = (optionalContactId < 0) ? "" :
5342                (Contacts._ID + "=" + optionalContactId + " AND ");
5343
5344        // First delete what needs to be deleted, then insert what needs to be added.
5345        // Since flash writes are very expensive, this approach is much better than
5346        // delete-all-insert-all.
5347        db.execSQL(
5348                "DELETE FROM " + Tables.VISIBLE_CONTACTS +
5349                " WHERE " + Contacts._ID + " IN" +
5350                    "(SELECT " + Contacts._ID +
5351                    " FROM " + Tables.CONTACTS +
5352                    " WHERE " + contactIdSelect + "(" + Clauses.CONTACT_IS_VISIBLE + ")=0) ",
5353                selectionArgs);
5354
5355        db.execSQL(
5356                "INSERT INTO " + Tables.VISIBLE_CONTACTS +
5357                " SELECT " + Contacts._ID +
5358                " FROM " + Tables.CONTACTS +
5359                " WHERE " +
5360                    contactIdSelect +
5361                    Contacts._ID + " NOT IN " + Tables.VISIBLE_CONTACTS +
5362                    " AND (" + Clauses.CONTACT_IS_VISIBLE + ")=1 ",
5363                selectionArgs);
5364    }
5365
5366    /**
5367     * Returns contact ID for the given contact or zero if it is NULL.
5368     */
5369    public long getContactId(long rawContactId) {
5370        if (mContactIdQuery == null) {
5371            mContactIdQuery = getWritableDatabase().compileStatement(
5372                    "SELECT " + RawContacts.CONTACT_ID +
5373                    " FROM " + Tables.RAW_CONTACTS +
5374                    " WHERE " + RawContacts._ID + "=?");
5375        }
5376        try {
5377            DatabaseUtils.bindObjectToProgram(mContactIdQuery, 1, rawContactId);
5378            return mContactIdQuery.simpleQueryForLong();
5379        } catch (SQLiteDoneException e) {
5380            return 0;  // No valid mapping found.
5381        }
5382    }
5383
5384    public int getAggregationMode(long rawContactId) {
5385        if (mAggregationModeQuery == null) {
5386            mAggregationModeQuery = getWritableDatabase().compileStatement(
5387                    "SELECT " + RawContacts.AGGREGATION_MODE +
5388                    " FROM " + Tables.RAW_CONTACTS +
5389                    " WHERE " + RawContacts._ID + "=?");
5390        }
5391        try {
5392            DatabaseUtils.bindObjectToProgram(mAggregationModeQuery, 1, rawContactId);
5393            return (int)mAggregationModeQuery.simpleQueryForLong();
5394        } catch (SQLiteDoneException e) {
5395            return RawContacts.AGGREGATION_MODE_DISABLED;  // No valid row found.
5396        }
5397    }
5398
5399    public void buildPhoneLookupAndContactQuery(
5400            SQLiteQueryBuilder qb, String normalizedNumber, String numberE164) {
5401
5402        String minMatch = PhoneNumberUtils.toCallerIDMinMatch(normalizedNumber);
5403        StringBuilder sb = new StringBuilder();
5404        appendPhoneLookupTables(sb, minMatch, true);
5405        qb.setTables(sb.toString());
5406
5407        sb = new StringBuilder();
5408        appendPhoneLookupSelection(sb, normalizedNumber, numberE164);
5409        qb.appendWhere(sb.toString());
5410    }
5411
5412    /**
5413     * Phone lookup method that uses the custom SQLite function phone_number_compare_loose
5414     * that serves as a fallback in case the regular lookup does not return any results.
5415     * @param qb The query builder.
5416     * @param number The phone number to search for.
5417     */
5418    public void buildFallbackPhoneLookupAndContactQuery(SQLiteQueryBuilder qb, String number) {
5419        final String minMatch = PhoneNumberUtils.toCallerIDMinMatch(number);
5420        final StringBuilder sb = new StringBuilder();
5421        // Append lookup tables.
5422        sb.append(Tables.RAW_CONTACTS);
5423        sb.append(" JOIN " + Views.CONTACTS + " as contacts_view"
5424                + " ON (contacts_view._id = " + Tables.RAW_CONTACTS
5425                + "." + RawContacts.CONTACT_ID + ")" +
5426                " JOIN (SELECT " + PhoneLookupColumns.DATA_ID + "," +
5427                PhoneLookupColumns.NORMALIZED_NUMBER + " FROM "+ Tables.PHONE_LOOKUP + " "
5428                + "WHERE (" + Tables.PHONE_LOOKUP + "." + PhoneLookupColumns.MIN_MATCH + " = '");
5429        sb.append(minMatch);
5430        sb.append("')) AS lookup " +
5431                "ON lookup." + PhoneLookupColumns.DATA_ID + "=" + Tables.DATA + "." + Data._ID
5432                + " JOIN " + Tables.DATA + " "
5433                + "ON " + Tables.DATA + "." + Data.RAW_CONTACT_ID + "=" + Tables.RAW_CONTACTS + "."
5434                + RawContacts._ID);
5435
5436        qb.setTables(sb.toString());
5437
5438        sb.setLength(0);
5439        sb.append("PHONE_NUMBERS_EQUAL(" + Tables.DATA + "." + Phone.NUMBER + ", ");
5440        DatabaseUtils.appendEscapedSQLString(sb, number);
5441        sb.append(mUseStrictPhoneNumberComparison ? ", 1)" : ", 0)");
5442        qb.appendWhere(sb.toString());
5443    }
5444
5445    /**
5446     * Adds query for selecting the contact with the given {@code sipAddress} to the given
5447     * {@link StringBuilder}.
5448     *
5449     * @return the query arguments to be passed in with the query
5450     */
5451    public String[] buildSipContactQuery(StringBuilder sb, String sipAddress) {
5452        sb.append("upper(");
5453        sb.append(Data.DATA1);
5454        sb.append(")=upper(?) AND ");
5455        sb.append(DataColumns.MIMETYPE_ID);
5456        sb.append("=");
5457        sb.append(Long.toString(getMimeTypeIdForSip()));
5458        // Return the arguments to be passed to the query.
5459        return new String[] {sipAddress};
5460    }
5461
5462    public String buildPhoneLookupAsNestedQuery(String number) {
5463        StringBuilder sb = new StringBuilder();
5464        final String minMatch = PhoneNumberUtils.toCallerIDMinMatch(number);
5465        sb.append("(SELECT DISTINCT raw_contact_id" + " FROM ");
5466        appendPhoneLookupTables(sb, minMatch, false);
5467        sb.append(" WHERE ");
5468        appendPhoneLookupSelection(sb, number, null);
5469        sb.append(")");
5470        return sb.toString();
5471    }
5472
5473    private void appendPhoneLookupTables(
5474            StringBuilder sb, final String minMatch, boolean joinContacts) {
5475
5476        sb.append(Tables.RAW_CONTACTS);
5477        if (joinContacts) {
5478            sb.append(" JOIN " + Views.CONTACTS + " contacts_view"
5479                    + " ON (contacts_view._id = raw_contacts.contact_id)");
5480        }
5481        sb.append(", (SELECT data_id, normalized_number, length(normalized_number) as len "
5482                + " FROM phone_lookup " + " WHERE (" + Tables.PHONE_LOOKUP + "."
5483                + PhoneLookupColumns.MIN_MATCH + " = '");
5484        sb.append(minMatch);
5485        sb.append("')) AS lookup, " + Tables.DATA);
5486    }
5487
5488    private void appendPhoneLookupSelection(StringBuilder sb, String number, String numberE164) {
5489        sb.append("lookup.data_id=data._id AND data.raw_contact_id=raw_contacts._id");
5490        boolean hasNumberE164 = !TextUtils.isEmpty(numberE164);
5491        boolean hasNumber = !TextUtils.isEmpty(number);
5492        if (hasNumberE164 || hasNumber) {
5493            sb.append(" AND ( ");
5494            if (hasNumberE164) {
5495                sb.append(" lookup.normalized_number = ");
5496                DatabaseUtils.appendEscapedSQLString(sb, numberE164);
5497            }
5498            if (hasNumberE164 && hasNumber) {
5499                sb.append(" OR ");
5500            }
5501            if (hasNumber) {
5502                // Skip the suffix match entirely if we are using strict number comparison.
5503                if (!mUseStrictPhoneNumberComparison) {
5504                    int numberLen = number.length();
5505                    sb.append(" lookup.len <= ");
5506                    sb.append(numberLen);
5507                    sb.append(" AND substr(");
5508                    DatabaseUtils.appendEscapedSQLString(sb, number);
5509                    sb.append(',');
5510                    sb.append(numberLen);
5511                    sb.append(" - lookup.len + 1) = lookup.normalized_number");
5512
5513                    // Some countries (e.g. Brazil) can have incoming calls which contain only
5514                    // the local number (no country calling code and no area code).  This case
5515                    // is handled below, see b/5197612.
5516                    // This also handles a Gingerbread -> ICS upgrade issue; see b/5638376.
5517                    sb.append(" OR (");
5518                    sb.append(" lookup.len > ");
5519                    sb.append(numberLen);
5520                    sb.append(" AND substr(lookup.normalized_number,");
5521                    sb.append("lookup.len + 1 - ");
5522                    sb.append(numberLen);
5523                    sb.append(") = ");
5524                    DatabaseUtils.appendEscapedSQLString(sb, number);
5525                    sb.append(")");
5526                } else {
5527                    sb.append("0");
5528                }
5529            }
5530            sb.append(')');
5531        }
5532    }
5533
5534    public String getUseStrictPhoneNumberComparisonParameter() {
5535        return mUseStrictPhoneNumberComparison ? "1" : "0";
5536    }
5537
5538    /**
5539     * Loads common nickname mappings into the database.
5540     */
5541    private void loadNicknameLookupTable(SQLiteDatabase db) {
5542        db.execSQL("DELETE FROM " + Tables.NICKNAME_LOOKUP);
5543
5544        String[] strings = mContext.getResources().getStringArray(
5545                com.android.internal.R.array.common_nicknames);
5546        if (strings == null || strings.length == 0) {
5547            return;
5548        }
5549
5550        SQLiteStatement nicknameLookupInsert = db.compileStatement("INSERT INTO "
5551                + Tables.NICKNAME_LOOKUP + "(" + NicknameLookupColumns.NAME + ","
5552                + NicknameLookupColumns.CLUSTER + ") VALUES (?,?)");
5553
5554        try {
5555            for (int clusterId = 0; clusterId < strings.length; clusterId++) {
5556                String[] names = strings[clusterId].split(",");
5557                for (String name : names) {
5558                    String normalizedName = NameNormalizer.normalize(name);
5559                    try {
5560                        DatabaseUtils.bindObjectToProgram(nicknameLookupInsert, 1, normalizedName);
5561                        DatabaseUtils.bindObjectToProgram(
5562                                nicknameLookupInsert, 2, String.valueOf(clusterId));
5563                        nicknameLookupInsert.executeInsert();
5564                    } catch (SQLiteException e) {
5565                        // Print the exception and keep going (this is not a fatal error).
5566                        Log.e(TAG, "Cannot insert nickname: " + name, e);
5567                    }
5568                }
5569            }
5570        } finally {
5571            nicknameLookupInsert.close();
5572        }
5573    }
5574
5575    public static void copyStringValue(
5576            ContentValues toValues, String toKey, ContentValues fromValues, String fromKey) {
5577
5578        if (fromValues.containsKey(fromKey)) {
5579            toValues.put(toKey, fromValues.getAsString(fromKey));
5580        }
5581    }
5582
5583    public static void copyLongValue(
5584            ContentValues toValues, String toKey, ContentValues fromValues, String fromKey) {
5585
5586        if (fromValues.containsKey(fromKey)) {
5587            long longValue;
5588            Object value = fromValues.get(fromKey);
5589            if (value instanceof Boolean) {
5590                longValue = (Boolean) value ? 1 : 0;
5591            } else if (value instanceof String) {
5592                longValue = Long.parseLong((String)value);
5593            } else {
5594                longValue = ((Number)value).longValue();
5595            }
5596            toValues.put(toKey, longValue);
5597        }
5598    }
5599
5600    public SyncStateContentProviderHelper getSyncState() {
5601        return mSyncState;
5602    }
5603
5604    /**
5605     * Returns the value from the {@link Tables#PROPERTIES} table.
5606     */
5607    public String getProperty(String key, String defaultValue) {
5608        return getProperty(getReadableDatabase(), key, defaultValue);
5609    }
5610
5611    public String getProperty(SQLiteDatabase db, String key, String defaultValue) {
5612        Cursor cursor = db.query(Tables.PROPERTIES,
5613                new String[] {PropertiesColumns.PROPERTY_VALUE},
5614                PropertiesColumns.PROPERTY_KEY + "=?",
5615                new String[] {key}, null, null, null);
5616        String value = null;
5617        try {
5618            if (cursor.moveToFirst()) {
5619                value = cursor.getString(0);
5620            }
5621        } finally {
5622            cursor.close();
5623        }
5624
5625        return value != null ? value : defaultValue;
5626    }
5627
5628    /**
5629     * Stores a key-value pair in the {@link Tables#PROPERTIES} table.
5630     */
5631    public void setProperty(String key, String value) {
5632        setProperty(getWritableDatabase(), key, value);
5633    }
5634
5635    private void setProperty(SQLiteDatabase db, String key, String value) {
5636        ContentValues values = new ContentValues();
5637        values.put(PropertiesColumns.PROPERTY_KEY, key);
5638        values.put(PropertiesColumns.PROPERTY_VALUE, value);
5639        db.replace(Tables.PROPERTIES, null, values);
5640    }
5641
5642    /**
5643     * Test if the given column appears in the given projection.
5644     */
5645    public static boolean isInProjection(String[] projection, String column) {
5646        if (projection == null) {
5647            return true;  // Null means "all columns".  We can't really tell if it's in there.
5648        }
5649        for (String test : projection) {
5650            if (column.equals(test)) {
5651                return true;
5652            }
5653        }
5654        return false;
5655    }
5656
5657    /**
5658     * Tests if any of the columns appear in the given projection.
5659     */
5660    public static boolean isInProjection(String[] projection, String... columns) {
5661        if (projection == null) {
5662            return true;
5663        }
5664
5665        // Optimized for a single-column test
5666        if (columns.length == 1) {
5667            return isInProjection(projection, columns[0]);
5668        }
5669        for (String test : projection) {
5670            for (String column : columns) {
5671                if (column.equals(test)) {
5672                    return true;
5673                }
5674            }
5675        }
5676        return false;
5677    }
5678
5679    /**
5680     * Returns a detailed exception message for the supplied URI.  It includes the calling
5681     * user and calling package(s).
5682     */
5683    public String exceptionMessage(Uri uri) {
5684        return exceptionMessage(null, uri);
5685    }
5686
5687    /**
5688     * Returns a detailed exception message for the supplied URI.  It includes the calling
5689     * user and calling package(s).
5690     */
5691    public String exceptionMessage(String message, Uri uri) {
5692        StringBuilder sb = new StringBuilder();
5693        if (message != null) {
5694            sb.append(message).append("; ");
5695        }
5696        sb.append("URI: ").append(uri);
5697        final PackageManager pm = mContext.getPackageManager();
5698        int callingUid = Binder.getCallingUid();
5699        sb.append(", calling user: ");
5700        String userName = pm.getNameForUid(callingUid);
5701        sb.append(userName == null ? callingUid : userName);
5702
5703        final String[] callerPackages = pm.getPackagesForUid(callingUid);
5704        if (callerPackages != null && callerPackages.length > 0) {
5705            if (callerPackages.length == 1) {
5706                sb.append(", calling package:");
5707                sb.append(callerPackages[0]);
5708            } else {
5709                sb.append(", calling package is one of: [");
5710                for (int i = 0; i < callerPackages.length; i++) {
5711                    if (i != 0) {
5712                        sb.append(", ");
5713                    }
5714                    sb.append(callerPackages[i]);
5715                }
5716                sb.append("]");
5717            }
5718        }
5719        return sb.toString();
5720    }
5721
5722    public void deleteStatusUpdate(long dataId) {
5723        if (mStatusUpdateDelete == null) {
5724            mStatusUpdateDelete = getWritableDatabase().compileStatement(
5725                    "DELETE FROM " + Tables.STATUS_UPDATES +
5726                    " WHERE " + StatusUpdatesColumns.DATA_ID + "=?");
5727        }
5728        mStatusUpdateDelete.bindLong(1, dataId);
5729        mStatusUpdateDelete.execute();
5730    }
5731
5732    public void replaceStatusUpdate(Long dataId, long timestamp, String status, String resPackage,
5733            Integer iconResource, Integer labelResource) {
5734        if (mStatusUpdateReplace == null) {
5735            mStatusUpdateReplace = getWritableDatabase().compileStatement(
5736                    "INSERT OR REPLACE INTO " + Tables.STATUS_UPDATES + "("
5737                            + StatusUpdatesColumns.DATA_ID + ", "
5738                            + StatusUpdates.STATUS_TIMESTAMP + ","
5739                            + StatusUpdates.STATUS + ","
5740                            + StatusUpdates.STATUS_RES_PACKAGE + ","
5741                            + StatusUpdates.STATUS_ICON + ","
5742                            + StatusUpdates.STATUS_LABEL + ")" +
5743                    " VALUES (?,?,?,?,?,?)");
5744        }
5745        mStatusUpdateReplace.bindLong(1, dataId);
5746        mStatusUpdateReplace.bindLong(2, timestamp);
5747        bindString(mStatusUpdateReplace, 3, status);
5748        bindString(mStatusUpdateReplace, 4, resPackage);
5749        bindLong(mStatusUpdateReplace, 5, iconResource);
5750        bindLong(mStatusUpdateReplace, 6, labelResource);
5751        mStatusUpdateReplace.execute();
5752    }
5753
5754    public void insertStatusUpdate(Long dataId, String status, String resPackage,
5755            Integer iconResource, Integer labelResource) {
5756        if (mStatusUpdateInsert == null) {
5757            mStatusUpdateInsert = getWritableDatabase().compileStatement(
5758                    "INSERT INTO " + Tables.STATUS_UPDATES + "("
5759                            + StatusUpdatesColumns.DATA_ID + ", "
5760                            + StatusUpdates.STATUS + ","
5761                            + StatusUpdates.STATUS_RES_PACKAGE + ","
5762                            + StatusUpdates.STATUS_ICON + ","
5763                            + StatusUpdates.STATUS_LABEL + ")" +
5764                    " VALUES (?,?,?,?,?)");
5765        }
5766        try {
5767            mStatusUpdateInsert.bindLong(1, dataId);
5768            bindString(mStatusUpdateInsert, 2, status);
5769            bindString(mStatusUpdateInsert, 3, resPackage);
5770            bindLong(mStatusUpdateInsert, 4, iconResource);
5771            bindLong(mStatusUpdateInsert, 5, labelResource);
5772            mStatusUpdateInsert.executeInsert();
5773        } catch (SQLiteConstraintException e) {
5774            // The row already exists - update it
5775            if (mStatusUpdateAutoTimestamp == null) {
5776                mStatusUpdateAutoTimestamp = getWritableDatabase().compileStatement(
5777                        "UPDATE " + Tables.STATUS_UPDATES +
5778                        " SET " + StatusUpdates.STATUS_TIMESTAMP + "=?,"
5779                                + StatusUpdates.STATUS + "=?" +
5780                        " WHERE " + StatusUpdatesColumns.DATA_ID + "=?"
5781                                + " AND " + StatusUpdates.STATUS + "!=?");
5782            }
5783
5784            long timestamp = System.currentTimeMillis();
5785            mStatusUpdateAutoTimestamp.bindLong(1, timestamp);
5786            bindString(mStatusUpdateAutoTimestamp, 2, status);
5787            mStatusUpdateAutoTimestamp.bindLong(3, dataId);
5788            bindString(mStatusUpdateAutoTimestamp, 4, status);
5789            mStatusUpdateAutoTimestamp.execute();
5790
5791            if (mStatusAttributionUpdate == null) {
5792                mStatusAttributionUpdate = getWritableDatabase().compileStatement(
5793                        "UPDATE " + Tables.STATUS_UPDATES +
5794                        " SET " + StatusUpdates.STATUS_RES_PACKAGE + "=?,"
5795                                + StatusUpdates.STATUS_ICON + "=?,"
5796                                + StatusUpdates.STATUS_LABEL + "=?" +
5797                        " WHERE " + StatusUpdatesColumns.DATA_ID + "=?");
5798            }
5799            bindString(mStatusAttributionUpdate, 1, resPackage);
5800            bindLong(mStatusAttributionUpdate, 2, iconResource);
5801            bindLong(mStatusAttributionUpdate, 3, labelResource);
5802            mStatusAttributionUpdate.bindLong(4, dataId);
5803            mStatusAttributionUpdate.execute();
5804        }
5805    }
5806
5807    /**
5808     * Updates a raw contact display name based on data rows, e.g. structured name,
5809     * organization, email etc.
5810     */
5811    public void updateRawContactDisplayName(SQLiteDatabase db, long rawContactId) {
5812        if (mNameSplitter == null) {
5813            createNameSplitter();
5814        }
5815
5816        int bestDisplayNameSource = DisplayNameSources.UNDEFINED;
5817        NameSplitter.Name bestName = null;
5818        String bestDisplayName = null;
5819        String bestPhoneticName = null;
5820        int bestPhoneticNameStyle = PhoneticNameStyle.UNDEFINED;
5821
5822        mSelectionArgs1[0] = String.valueOf(rawContactId);
5823        Cursor c = db.rawQuery(RawContactNameQuery.RAW_SQL, mSelectionArgs1);
5824        try {
5825            while (c.moveToNext()) {
5826                int mimeType = c.getInt(RawContactNameQuery.MIMETYPE);
5827                int source = getDisplayNameSourceForMimeTypeId(mimeType);
5828
5829                if (source == DisplayNameSources.STRUCTURED_NAME) {
5830                    final String given = c.getString(RawContactNameQuery.GIVEN_NAME);
5831                    final String middle = c.getString(RawContactNameQuery.MIDDLE_NAME);
5832                    final String family = c.getString(RawContactNameQuery.FAMILY_NAME);
5833                    final String suffix = c.getString(RawContactNameQuery.SUFFIX);
5834                    final String prefix = c.getString(RawContactNameQuery.PREFIX);
5835                    if (TextUtils.isEmpty(given) && TextUtils.isEmpty(middle)
5836                            && TextUtils.isEmpty(family) && TextUtils.isEmpty(suffix)
5837                            && TextUtils.isEmpty(prefix)) {
5838                        // Every non-phonetic name component is empty. Therefore, lets lower the
5839                        // source score to STRUCTURED_PHONETIC_NAME.
5840                        source = DisplayNameSources.STRUCTURED_PHONETIC_NAME;
5841                    }
5842                }
5843
5844                if (source < bestDisplayNameSource || source == DisplayNameSources.UNDEFINED) {
5845                    continue;
5846                }
5847
5848                if (source == bestDisplayNameSource
5849                        && c.getInt(RawContactNameQuery.IS_PRIMARY) == 0) {
5850                    continue;
5851                }
5852
5853                if (mimeType == getMimeTypeIdForStructuredName()) {
5854                    NameSplitter.Name name;
5855                    if (bestName != null) {
5856                        name = new NameSplitter.Name();
5857                    } else {
5858                        name = mName;
5859                        name.clear();
5860                    }
5861                    name.prefix = c.getString(RawContactNameQuery.PREFIX);
5862                    name.givenNames = c.getString(RawContactNameQuery.GIVEN_NAME);
5863                    name.middleName = c.getString(RawContactNameQuery.MIDDLE_NAME);
5864                    name.familyName = c.getString(RawContactNameQuery.FAMILY_NAME);
5865                    name.suffix = c.getString(RawContactNameQuery.SUFFIX);
5866                    name.fullNameStyle = c.isNull(RawContactNameQuery.FULL_NAME_STYLE)
5867                            ? FullNameStyle.UNDEFINED
5868                            : c.getInt(RawContactNameQuery.FULL_NAME_STYLE);
5869                    name.phoneticFamilyName = c.getString(RawContactNameQuery.PHONETIC_FAMILY_NAME);
5870                    name.phoneticMiddleName = c.getString(RawContactNameQuery.PHONETIC_MIDDLE_NAME);
5871                    name.phoneticGivenName = c.getString(RawContactNameQuery.PHONETIC_GIVEN_NAME);
5872                    name.phoneticNameStyle = c.isNull(RawContactNameQuery.PHONETIC_NAME_STYLE)
5873                            ? PhoneticNameStyle.UNDEFINED
5874                            : c.getInt(RawContactNameQuery.PHONETIC_NAME_STYLE);
5875                    if (!name.isEmpty()) {
5876                        bestDisplayNameSource = source;
5877                        bestName = name;
5878                    }
5879                } else if (mimeType == getMimeTypeIdForOrganization()) {
5880                    mCharArrayBuffer.sizeCopied = 0;
5881                    c.copyStringToBuffer(RawContactNameQuery.DATA1, mCharArrayBuffer);
5882                    if (mCharArrayBuffer.sizeCopied != 0) {
5883                        bestDisplayNameSource = source;
5884                        bestDisplayName = new String(mCharArrayBuffer.data, 0,
5885                                mCharArrayBuffer.sizeCopied);
5886                        bestPhoneticName = c.getString(
5887                                RawContactNameQuery.ORGANIZATION_PHONETIC_NAME);
5888                        bestPhoneticNameStyle =
5889                                c.isNull(RawContactNameQuery.ORGANIZATION_PHONETIC_NAME_STYLE)
5890                                   ? PhoneticNameStyle.UNDEFINED
5891                                   : c.getInt(RawContactNameQuery.ORGANIZATION_PHONETIC_NAME_STYLE);
5892                    } else {
5893                        c.copyStringToBuffer(RawContactNameQuery.TITLE, mCharArrayBuffer);
5894                        if (mCharArrayBuffer.sizeCopied != 0) {
5895                            bestDisplayNameSource = source;
5896                            bestDisplayName = new String(mCharArrayBuffer.data, 0,
5897                                    mCharArrayBuffer.sizeCopied);
5898                            bestPhoneticName = null;
5899                            bestPhoneticNameStyle = PhoneticNameStyle.UNDEFINED;
5900                        }
5901                    }
5902                } else {
5903                    // Display name is at DATA1 in all other types.
5904                    // This is ensured in the constructor.
5905
5906                    mCharArrayBuffer.sizeCopied = 0;
5907                    c.copyStringToBuffer(RawContactNameQuery.DATA1, mCharArrayBuffer);
5908                    if (mCharArrayBuffer.sizeCopied != 0) {
5909                        bestDisplayNameSource = source;
5910                        bestDisplayName = new String(mCharArrayBuffer.data, 0,
5911                                mCharArrayBuffer.sizeCopied);
5912                        bestPhoneticName = null;
5913                        bestPhoneticNameStyle = PhoneticNameStyle.UNDEFINED;
5914                    }
5915                }
5916            }
5917
5918        } finally {
5919            c.close();
5920        }
5921
5922        String displayNamePrimary;
5923        String displayNameAlternative;
5924        String sortNamePrimary;
5925        String sortNameAlternative;
5926        String sortKeyPrimary = null;
5927        String sortKeyAlternative = null;
5928        int displayNameStyle = FullNameStyle.UNDEFINED;
5929
5930        if (bestDisplayNameSource == DisplayNameSources.STRUCTURED_NAME
5931                || bestDisplayNameSource == DisplayNameSources.STRUCTURED_PHONETIC_NAME) {
5932            displayNameStyle = bestName.fullNameStyle;
5933            if (displayNameStyle == FullNameStyle.CJK
5934                    || displayNameStyle == FullNameStyle.UNDEFINED) {
5935                displayNameStyle = mNameSplitter.getAdjustedFullNameStyle(displayNameStyle);
5936                bestName.fullNameStyle = displayNameStyle;
5937            }
5938
5939            displayNamePrimary = mNameSplitter.join(bestName, true, true);
5940            displayNameAlternative = mNameSplitter.join(bestName, false, true);
5941
5942            if (TextUtils.isEmpty(bestName.prefix)) {
5943                sortNamePrimary = displayNamePrimary;
5944                sortNameAlternative = displayNameAlternative;
5945            } else {
5946                sortNamePrimary = mNameSplitter.join(bestName, true, false);
5947                sortNameAlternative = mNameSplitter.join(bestName, false, false);
5948            }
5949
5950            bestPhoneticName = mNameSplitter.joinPhoneticName(bestName);
5951            bestPhoneticNameStyle = bestName.phoneticNameStyle;
5952        } else {
5953            displayNamePrimary = displayNameAlternative = bestDisplayName;
5954            sortNamePrimary = sortNameAlternative = bestDisplayName;
5955        }
5956
5957        if (bestPhoneticName != null) {
5958            if (displayNamePrimary == null) {
5959                displayNamePrimary = bestPhoneticName;
5960            }
5961            if (displayNameAlternative == null) {
5962                displayNameAlternative = bestPhoneticName;
5963            }
5964            // Phonetic names disregard name order so displayNamePrimary and displayNameAlternative
5965            // are the same.
5966            sortKeyPrimary = sortKeyAlternative = bestPhoneticName;
5967            if (bestPhoneticNameStyle == PhoneticNameStyle.UNDEFINED) {
5968                bestPhoneticNameStyle = mNameSplitter.guessPhoneticNameStyle(bestPhoneticName);
5969            }
5970        } else {
5971            bestPhoneticNameStyle = PhoneticNameStyle.UNDEFINED;
5972            if (displayNameStyle == FullNameStyle.UNDEFINED) {
5973                displayNameStyle = mNameSplitter.guessFullNameStyle(bestDisplayName);
5974                if (displayNameStyle == FullNameStyle.UNDEFINED
5975                        || displayNameStyle == FullNameStyle.CJK) {
5976                    displayNameStyle = mNameSplitter.getAdjustedNameStyleBasedOnPhoneticNameStyle(
5977                            displayNameStyle, bestPhoneticNameStyle);
5978                }
5979                displayNameStyle = mNameSplitter.getAdjustedFullNameStyle(displayNameStyle);
5980            }
5981            if (displayNameStyle == FullNameStyle.CHINESE ||
5982                    displayNameStyle == FullNameStyle.CJK) {
5983                sortKeyPrimary = sortKeyAlternative = sortNamePrimary;
5984            }
5985        }
5986
5987        if (sortKeyPrimary == null) {
5988            sortKeyPrimary = sortNamePrimary;
5989            sortKeyAlternative = sortNameAlternative;
5990        }
5991
5992        String phonebookLabelPrimary = "";
5993        String phonebookLabelAlternative = "";
5994        int phonebookBucketPrimary = 0;
5995        int phonebookBucketAlternative = 0;
5996        ContactLocaleUtils localeUtils = ContactLocaleUtils.getInstance();
5997
5998        if (sortKeyPrimary != null) {
5999            phonebookBucketPrimary = localeUtils.getBucketIndex(sortKeyPrimary);
6000            phonebookLabelPrimary = localeUtils.getBucketLabel(phonebookBucketPrimary);
6001        }
6002        if (sortKeyAlternative != null) {
6003            phonebookBucketAlternative = localeUtils.getBucketIndex(sortKeyAlternative);
6004            phonebookLabelAlternative = localeUtils.getBucketLabel(phonebookBucketAlternative);
6005        }
6006
6007        if (mRawContactDisplayNameUpdate == null) {
6008            mRawContactDisplayNameUpdate = db.compileStatement(
6009                    "UPDATE " + Tables.RAW_CONTACTS +
6010                    " SET " +
6011                            RawContacts.DISPLAY_NAME_SOURCE + "=?," +
6012                            RawContacts.DISPLAY_NAME_PRIMARY + "=?," +
6013                            RawContacts.DISPLAY_NAME_ALTERNATIVE + "=?," +
6014                            RawContacts.PHONETIC_NAME + "=?," +
6015                            RawContacts.PHONETIC_NAME_STYLE + "=?," +
6016                            RawContacts.SORT_KEY_PRIMARY + "=?," +
6017                            RawContactsColumns.PHONEBOOK_LABEL_PRIMARY + "=?," +
6018                            RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY + "=?," +
6019                            RawContacts.SORT_KEY_ALTERNATIVE + "=?," +
6020                            RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + "=?," +
6021                            RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE + "=?" +
6022                    " WHERE " + RawContacts._ID + "=?");
6023        }
6024
6025        mRawContactDisplayNameUpdate.bindLong(1, bestDisplayNameSource);
6026        bindString(mRawContactDisplayNameUpdate, 2, displayNamePrimary);
6027        bindString(mRawContactDisplayNameUpdate, 3, displayNameAlternative);
6028        bindString(mRawContactDisplayNameUpdate, 4, bestPhoneticName);
6029        mRawContactDisplayNameUpdate.bindLong(5, bestPhoneticNameStyle);
6030        bindString(mRawContactDisplayNameUpdate, 6, sortKeyPrimary);
6031        bindString(mRawContactDisplayNameUpdate, 7, phonebookLabelPrimary);
6032        mRawContactDisplayNameUpdate.bindLong(8, phonebookBucketPrimary);
6033        bindString(mRawContactDisplayNameUpdate, 9, sortKeyAlternative);
6034        bindString(mRawContactDisplayNameUpdate, 10, phonebookLabelAlternative);
6035        mRawContactDisplayNameUpdate.bindLong(11, phonebookBucketAlternative);
6036        mRawContactDisplayNameUpdate.bindLong(12, rawContactId);
6037        mRawContactDisplayNameUpdate.execute();
6038    }
6039
6040    /**
6041     * Sets the given dataId record in the "data" table to primary, and resets all data records of
6042     * the same mimetype and under the same contact to not be primary.
6043     *
6044     * @param dataId the id of the data record to be set to primary. Pass -1 to clear the primary
6045     * flag of all data items of this raw contacts
6046     */
6047    public void setIsPrimary(long rawContactId, long dataId, long mimeTypeId) {
6048        if (mSetPrimaryStatement == null) {
6049            mSetPrimaryStatement = getWritableDatabase().compileStatement(
6050                    "UPDATE " + Tables.DATA +
6051                    " SET " + Data.IS_PRIMARY + "=(_id=?)" +
6052                    " WHERE " + DataColumns.MIMETYPE_ID + "=?" +
6053                    "   AND " + Data.RAW_CONTACT_ID + "=?");
6054        }
6055        mSetPrimaryStatement.bindLong(1, dataId);
6056        mSetPrimaryStatement.bindLong(2, mimeTypeId);
6057        mSetPrimaryStatement.bindLong(3, rawContactId);
6058        mSetPrimaryStatement.execute();
6059    }
6060
6061    /**
6062     * Clears the super primary of all data items of the given raw contact. does not touch
6063     * other raw contacts of the same joined aggregate
6064     */
6065    public void clearSuperPrimary(long rawContactId, long mimeTypeId) {
6066        if (mClearSuperPrimaryStatement == null) {
6067            mClearSuperPrimaryStatement = getWritableDatabase().compileStatement(
6068                    "UPDATE " + Tables.DATA +
6069                    " SET " + Data.IS_SUPER_PRIMARY + "=0" +
6070                    " WHERE " + DataColumns.MIMETYPE_ID + "=?" +
6071                    "   AND " + Data.RAW_CONTACT_ID + "=?");
6072        }
6073        mClearSuperPrimaryStatement.bindLong(1, mimeTypeId);
6074        mClearSuperPrimaryStatement.bindLong(2, rawContactId);
6075        mClearSuperPrimaryStatement.execute();
6076    }
6077
6078    /**
6079     * Sets the given dataId record in the "data" table to "super primary", and resets all data
6080     * records of the same mimetype and under the same aggregate to not be "super primary".
6081     *
6082     * @param dataId the id of the data record to be set to primary.
6083     */
6084    public void setIsSuperPrimary(long rawContactId, long dataId, long mimeTypeId) {
6085        if (mSetSuperPrimaryStatement == null) {
6086            mSetSuperPrimaryStatement = getWritableDatabase().compileStatement(
6087                    "UPDATE " + Tables.DATA +
6088                    " SET " + Data.IS_SUPER_PRIMARY + "=(" + Data._ID + "=?)" +
6089                    " WHERE " + DataColumns.MIMETYPE_ID + "=?" +
6090                    "   AND " + Data.RAW_CONTACT_ID + " IN (" +
6091                            "SELECT " + RawContacts._ID +
6092                            " FROM " + Tables.RAW_CONTACTS +
6093                            " WHERE " + RawContacts.CONTACT_ID + " =(" +
6094                                    "SELECT " + RawContacts.CONTACT_ID +
6095                                    " FROM " + Tables.RAW_CONTACTS +
6096                                    " WHERE " + RawContacts._ID + "=?))");
6097        }
6098        mSetSuperPrimaryStatement.bindLong(1, dataId);
6099        mSetSuperPrimaryStatement.bindLong(2, mimeTypeId);
6100        mSetSuperPrimaryStatement.bindLong(3, rawContactId);
6101        mSetSuperPrimaryStatement.execute();
6102    }
6103
6104    /**
6105     * Inserts a record in the {@link Tables#NAME_LOOKUP} table.
6106     */
6107    public void insertNameLookup(long rawContactId, long dataId, int lookupType, String name) {
6108        if (TextUtils.isEmpty(name)) {
6109            return;
6110        }
6111
6112        if (mNameLookupInsert == null) {
6113            mNameLookupInsert = getWritableDatabase().compileStatement(
6114                    "INSERT OR IGNORE INTO " + Tables.NAME_LOOKUP + "("
6115                            + NameLookupColumns.RAW_CONTACT_ID + ","
6116                            + NameLookupColumns.DATA_ID + ","
6117                            + NameLookupColumns.NAME_TYPE + ","
6118                            + NameLookupColumns.NORMALIZED_NAME
6119                    + ") VALUES (?,?,?,?)");
6120        }
6121        mNameLookupInsert.bindLong(1, rawContactId);
6122        mNameLookupInsert.bindLong(2, dataId);
6123        mNameLookupInsert.bindLong(3, lookupType);
6124        bindString(mNameLookupInsert, 4, name);
6125        mNameLookupInsert.executeInsert();
6126    }
6127
6128    /**
6129     * Deletes all {@link Tables#NAME_LOOKUP} table rows associated with the specified data element.
6130     */
6131    public void deleteNameLookup(long dataId) {
6132        if (mNameLookupDelete == null) {
6133            mNameLookupDelete = getWritableDatabase().compileStatement(
6134                    "DELETE FROM " + Tables.NAME_LOOKUP +
6135                    " WHERE " + NameLookupColumns.DATA_ID + "=?");
6136        }
6137        mNameLookupDelete.bindLong(1, dataId);
6138        mNameLookupDelete.execute();
6139    }
6140
6141    public String insertNameLookupForEmail(long rawContactId, long dataId, String email) {
6142        if (TextUtils.isEmpty(email)) {
6143            return null;
6144        }
6145
6146        String address = extractHandleFromEmailAddress(email);
6147        if (address == null) {
6148            return null;
6149        }
6150
6151        insertNameLookup(rawContactId, dataId,
6152                NameLookupType.EMAIL_BASED_NICKNAME, NameNormalizer.normalize(address));
6153        return address;
6154    }
6155
6156    /**
6157     * Normalizes the nickname and inserts it in the name lookup table.
6158     */
6159    public void insertNameLookupForNickname(long rawContactId, long dataId, String nickname) {
6160        if (!TextUtils.isEmpty(nickname)) {
6161            insertNameLookup(rawContactId, dataId,
6162                    NameLookupType.NICKNAME, NameNormalizer.normalize(nickname));
6163        }
6164    }
6165
6166    /**
6167     * Performs a query and returns true if any Data item of the raw contact with the given
6168     * id and mimetype is marked as super-primary
6169     */
6170    public boolean rawContactHasSuperPrimary(long rawContactId, long mimeTypeId) {
6171        final Cursor existsCursor = getReadableDatabase().rawQuery(
6172                "SELECT EXISTS(SELECT 1 FROM " + Tables.DATA +
6173                " WHERE " + Data.RAW_CONTACT_ID + "=?" +
6174                " AND " + DataColumns.MIMETYPE_ID + "=?" +
6175                " AND " + Data.IS_SUPER_PRIMARY + "<>0)",
6176                new String[] {String.valueOf(rawContactId), String.valueOf(mimeTypeId)});
6177        try {
6178            if (!existsCursor.moveToFirst()) throw new IllegalStateException();
6179            return existsCursor.getInt(0) != 0;
6180        } finally {
6181            existsCursor.close();
6182        }
6183    }
6184
6185    public String getCurrentCountryIso() {
6186        return mCountryMonitor.getCountryIso();
6187    }
6188
6189    @NeededForTesting
6190    /* package */ void setUseStrictPhoneNumberComparisonForTest(boolean useStrict) {
6191        mUseStrictPhoneNumberComparison = useStrict;
6192    }
6193
6194    @NeededForTesting
6195    /* package */ boolean getUseStrictPhoneNumberComparisonForTest() {
6196        return mUseStrictPhoneNumberComparison;
6197    }
6198
6199    @NeededForTesting
6200    /* package */ String querySearchIndexContentForTest(long contactId) {
6201        return DatabaseUtils.stringForQuery(getReadableDatabase(),
6202                "SELECT " + SearchIndexColumns.CONTENT +
6203                " FROM " + Tables.SEARCH_INDEX +
6204                " WHERE " + SearchIndexColumns.CONTACT_ID + "=CAST(? AS int)",
6205                new String[] {String.valueOf(contactId)});
6206    }
6207
6208    @NeededForTesting
6209    /* package */ String querySearchIndexTokensForTest(long contactId) {
6210        return DatabaseUtils.stringForQuery(getReadableDatabase(),
6211                "SELECT " + SearchIndexColumns.TOKENS +
6212                " FROM " + Tables.SEARCH_INDEX +
6213                " WHERE " + SearchIndexColumns.CONTACT_ID + "=CAST(? AS int)",
6214                new String[] {String.valueOf(contactId)});
6215    }
6216
6217    public long upsertMetadataSync(String backupId, Long accountId, String data, Integer deleted) {
6218        if (mMetadataSyncInsert == null) {
6219            mMetadataSyncInsert = getWritableDatabase().compileStatement(
6220                    "INSERT OR REPLACE INTO " + Tables.METADATA_SYNC + "("
6221                            + MetadataSync.RAW_CONTACT_BACKUP_ID + ", "
6222                            + MetadataSyncColumns.ACCOUNT_ID + ", "
6223                            + MetadataSync.DATA + ","
6224                            + MetadataSync.DELETED + ")" +
6225                            " VALUES (?,?,?,?)");
6226        }
6227        mMetadataSyncInsert.bindString(1, backupId);
6228        mMetadataSyncInsert.bindLong(2, accountId);
6229        data = (data == null) ? "" : data;
6230        mMetadataSyncInsert.bindString(3, data);
6231        mMetadataSyncInsert.bindLong(4, deleted);
6232        return mMetadataSyncInsert.executeInsert();
6233    }
6234}
6235