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