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