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