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