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