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