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