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