Home | History | Annotate | Download | only in contacts
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.providers.contacts;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.pm.ApplicationInfo;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.PackageManager.NameNotFoundException;
     26 import android.content.res.Resources;
     27 import android.database.CharArrayBuffer;
     28 import android.database.Cursor;
     29 import android.database.DatabaseUtils;
     30 import android.database.SQLException;
     31 import android.database.sqlite.SQLiteConstraintException;
     32 import android.database.sqlite.SQLiteDatabase;
     33 import android.database.sqlite.SQLiteDoneException;
     34 import android.database.sqlite.SQLiteException;
     35 import android.database.sqlite.SQLiteOpenHelper;
     36 import android.database.sqlite.SQLiteQueryBuilder;
     37 import android.database.sqlite.SQLiteStatement;
     38 import android.location.CountryDetector;
     39 import android.net.Uri;
     40 import android.os.Binder;
     41 import android.os.Bundle;
     42 import android.os.SystemClock;
     43 import android.provider.BaseColumns;
     44 import android.provider.CallLog.Calls;
     45 import android.provider.ContactsContract;
     46 import android.provider.ContactsContract.AggregationExceptions;
     47 import android.provider.ContactsContract.CommonDataKinds.Email;
     48 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
     49 import android.provider.ContactsContract.CommonDataKinds.Im;
     50 import android.provider.ContactsContract.CommonDataKinds.Nickname;
     51 import android.provider.ContactsContract.CommonDataKinds.Organization;
     52 import android.provider.ContactsContract.CommonDataKinds.Phone;
     53 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
     54 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
     55 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
     56 import android.provider.ContactsContract.Contacts;
     57 import android.provider.ContactsContract.Contacts.Photo;
     58 import android.provider.ContactsContract.Data;
     59 import android.provider.ContactsContract.Directory;
     60 import android.provider.ContactsContract.DisplayNameSources;
     61 import android.provider.ContactsContract.DisplayPhoto;
     62 import android.provider.ContactsContract.FullNameStyle;
     63 import android.provider.ContactsContract.Groups;
     64 import android.provider.ContactsContract.PhoneticNameStyle;
     65 import android.provider.ContactsContract.PhotoFiles;
     66 import android.provider.ContactsContract.RawContacts;
     67 import android.provider.ContactsContract.Settings;
     68 import android.provider.ContactsContract.StatusUpdates;
     69 import android.provider.ContactsContract.StreamItemPhotos;
     70 import android.provider.ContactsContract.StreamItems;
     71 import android.provider.VoicemailContract;
     72 import android.provider.VoicemailContract.Voicemails;
     73 import android.telephony.PhoneNumberUtils;
     74 import android.text.TextUtils;
     75 import android.text.util.Rfc822Token;
     76 import android.text.util.Rfc822Tokenizer;
     77 import android.util.Log;
     78 
     79 import com.android.common.content.SyncStateContentProviderHelper;
     80 import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
     81 import com.android.providers.contacts.database.ContactsTableUtil;
     82 import com.android.providers.contacts.database.DeletedContactsTableUtil;
     83 import com.android.providers.contacts.database.MoreDatabaseUtils;
     84 import com.android.providers.contacts.util.NeededForTesting;
     85 import com.google.android.collect.Sets;
     86 
     87 import java.util.HashMap;
     88 import java.util.Locale;
     89 import java.util.Set;
     90 
     91 import libcore.icu.ICU;
     92 
     93 /**
     94  * Database helper for contacts. Designed as a singleton to make sure that all
     95  * {@link android.content.ContentProvider} users get the same reference.
     96  * Provides handy methods for maintaining package and mime-type lookup tables.
     97  */
     98 public class ContactsDatabaseHelper extends SQLiteOpenHelper {
     99     private static final String TAG = "ContactsDatabaseHelper";
    100 
    101     /**
    102      * Contacts DB version ranges:
    103      * <pre>
    104      *   0-98    Cupcake/Donut
    105      *   100-199 Eclair
    106      *   200-299 Eclair-MR1
    107      *   300-349 Froyo
    108      *   350-399 Gingerbread
    109      *   400-499 Honeycomb
    110      *   500-549 Honeycomb-MR1
    111      *   550-599 Honeycomb-MR2
    112      *   600-699 Ice Cream Sandwich
    113      *   700-799 Jelly Bean
    114      * </pre>
    115      */
    116     static final int DATABASE_VERSION = 710;
    117 
    118     private static final String DATABASE_NAME = "contacts2.db";
    119     private static final String DATABASE_PRESENCE = "presence_db";
    120 
    121     public interface Tables {
    122         public static final String CONTACTS = "contacts";
    123         public static final String DELETED_CONTACTS = "deleted_contacts";
    124         public static final String RAW_CONTACTS = "raw_contacts";
    125         public static final String STREAM_ITEMS = "stream_items";
    126         public static final String STREAM_ITEM_PHOTOS = "stream_item_photos";
    127         public static final String PHOTO_FILES = "photo_files";
    128         public static final String PACKAGES = "packages";
    129         public static final String MIMETYPES = "mimetypes";
    130         public static final String PHONE_LOOKUP = "phone_lookup";
    131         public static final String NAME_LOOKUP = "name_lookup";
    132         public static final String AGGREGATION_EXCEPTIONS = "agg_exceptions";
    133         public static final String SETTINGS = "settings";
    134         public static final String DATA = "data";
    135         public static final String GROUPS = "groups";
    136         public static final String PRESENCE = "presence";
    137         public static final String AGGREGATED_PRESENCE = "agg_presence";
    138         public static final String NICKNAME_LOOKUP = "nickname_lookup";
    139         public static final String CALLS = "calls";
    140         public static final String STATUS_UPDATES = "status_updates";
    141         public static final String PROPERTIES = "properties";
    142         public static final String ACCOUNTS = "accounts";
    143         public static final String VISIBLE_CONTACTS = "visible_contacts";
    144         public static final String DIRECTORIES = "directories";
    145         public static final String DEFAULT_DIRECTORY = "default_directory";
    146         public static final String SEARCH_INDEX = "search_index";
    147         public static final String VOICEMAIL_STATUS = "voicemail_status";
    148 
    149         // This list of tables contains auto-incremented sequences.
    150         public static final String[] SEQUENCE_TABLES = new String[] {
    151                 CONTACTS,
    152                 RAW_CONTACTS,
    153                 STREAM_ITEMS,
    154                 STREAM_ITEM_PHOTOS,
    155                 PHOTO_FILES,
    156                 DATA,
    157                 GROUPS,
    158                 CALLS,
    159                 DIRECTORIES
    160         };
    161 
    162         /**
    163          * For {@link ContactsContract.DataUsageFeedback}. The table structure itself
    164          * is not exposed outside.
    165          */
    166         public static final String DATA_USAGE_STAT = "data_usage_stat";
    167 
    168         public static final String DATA_JOIN_MIMETYPES = "data "
    169                 + "JOIN mimetypes ON (data.mimetype_id = mimetypes._id)";
    170 
    171         public static final String DATA_JOIN_RAW_CONTACTS = "data "
    172                 + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id)";
    173 
    174         // NOTE: If you want to refer to account name/type/data_set, AccountsColumns.CONCRETE_XXX
    175         // MUST be used, as upgraded raw_contacts may have the account info columns too.
    176         public static final String DATA_JOIN_MIMETYPE_RAW_CONTACTS = "data "
    177                 + "JOIN mimetypes ON (data.mimetype_id = mimetypes._id) "
    178                 + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id)"
    179                 + " JOIN " + Tables.ACCOUNTS + " ON ("
    180                     + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
    181                     + ")";
    182 
    183         // NOTE: This requires late binding of GroupMembership MIME-type
    184         // TODO Consolidate settings and accounts
    185         public static final String RAW_CONTACTS_JOIN_SETTINGS_DATA_GROUPS = Tables.RAW_CONTACTS
    186                 + " JOIN " + Tables.ACCOUNTS + " ON ("
    187                 +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
    188                     + ")"
    189                 + "LEFT OUTER JOIN " + Tables.SETTINGS + " ON ("
    190                     + AccountsColumns.CONCRETE_ACCOUNT_NAME + "="
    191                         + SettingsColumns.CONCRETE_ACCOUNT_NAME + " AND "
    192                     + AccountsColumns.CONCRETE_ACCOUNT_TYPE + "="
    193                         + SettingsColumns.CONCRETE_ACCOUNT_TYPE + " AND "
    194                     + "((" + AccountsColumns.CONCRETE_DATA_SET + " IS NULL AND "
    195                             + SettingsColumns.CONCRETE_DATA_SET + " IS NULL) OR ("
    196                         + AccountsColumns.CONCRETE_DATA_SET + "="
    197                             + SettingsColumns.CONCRETE_DATA_SET + "))) "
    198                 + "LEFT OUTER JOIN data ON (data.mimetype_id=? AND "
    199                     + "data.raw_contact_id = raw_contacts._id) "
    200                 + "LEFT OUTER JOIN groups ON (groups._id = data." + GroupMembership.GROUP_ROW_ID
    201                 + ")";
    202 
    203         // NOTE: This requires late binding of GroupMembership MIME-type
    204         // TODO Add missing DATA_SET join -- or just consolidate settings and accounts
    205         public static final String SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS = "settings "
    206                 + "LEFT OUTER JOIN raw_contacts ON ("
    207                     + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=(SELECT "
    208                         + AccountsColumns.CONCRETE_ID
    209                         + " FROM " + Tables.ACCOUNTS
    210                         + " WHERE "
    211                             + "(" + AccountsColumns.CONCRETE_ACCOUNT_NAME
    212                                 + "=" + SettingsColumns.CONCRETE_ACCOUNT_NAME + ") AND "
    213                             + "(" + AccountsColumns.CONCRETE_ACCOUNT_TYPE
    214                                 + "=" + SettingsColumns.CONCRETE_ACCOUNT_TYPE + ")))"
    215                 + "LEFT OUTER JOIN data ON (data.mimetype_id=? AND "
    216                     + "data.raw_contact_id = raw_contacts._id) "
    217                 + "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)";
    218 
    219         public static final String CONTACTS_JOIN_RAW_CONTACTS_DATA_FILTERED_BY_GROUPMEMBERSHIP =
    220                 Tables.CONTACTS
    221                     + " INNER JOIN " + Tables.RAW_CONTACTS
    222                         + " ON (" + RawContactsColumns.CONCRETE_CONTACT_ID + "="
    223                             + ContactsColumns.CONCRETE_ID
    224                         + ")"
    225                     + " INNER JOIN " + Tables.DATA
    226                         + " ON (" + DataColumns.CONCRETE_DATA1 + "=" + GroupsColumns.CONCRETE_ID
    227                         + " AND "
    228                         + DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID
    229                         + " AND "
    230                         + DataColumns.CONCRETE_MIMETYPE_ID + "="
    231                             + "(SELECT " + MimetypesColumns._ID
    232                             + " FROM " + Tables.MIMETYPES
    233                             + " WHERE "
    234                             + MimetypesColumns.CONCRETE_MIMETYPE + "="
    235                                 + "'" + GroupMembership.CONTENT_ITEM_TYPE + "'"
    236                             + ")"
    237                         + ")";
    238 
    239         // NOTE: If you want to refer to account name/type/data_set, AccountsColumns.CONCRETE_XXX
    240         // MUST be used, as upgraded raw_contacts may have the account info columns too.
    241         public static final String DATA_JOIN_PACKAGES_MIMETYPES_RAW_CONTACTS_GROUPS = "data "
    242                 + "JOIN mimetypes ON (data.mimetype_id = mimetypes._id) "
    243                 + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) "
    244                 + " JOIN " + Tables.ACCOUNTS + " ON ("
    245                     + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
    246                     + ")"
    247                 + "LEFT OUTER JOIN packages ON (data.package_id = packages._id) "
    248                 + "LEFT OUTER JOIN groups "
    249                 + "  ON (mimetypes.mimetype='" + GroupMembership.CONTENT_ITEM_TYPE + "' "
    250                 + "      AND groups._id = data." + GroupMembership.GROUP_ROW_ID + ") ";
    251 
    252         public static final String ACTIVITIES_JOIN_MIMETYPES = "activities "
    253                 + "LEFT OUTER JOIN mimetypes ON (activities.mimetype_id = mimetypes._id)";
    254 
    255         public static final String ACTIVITIES_JOIN_PACKAGES_MIMETYPES_RAW_CONTACTS_CONTACTS =
    256                 "activities "
    257                 + "LEFT OUTER JOIN packages ON (activities.package_id = packages._id) "
    258                 + "LEFT OUTER JOIN mimetypes ON (activities.mimetype_id = mimetypes._id) "
    259                 + "LEFT OUTER JOIN raw_contacts ON (activities.author_contact_id = " +
    260                         "raw_contacts._id) "
    261                 + "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)";
    262 
    263         public static final String NAME_LOOKUP_JOIN_RAW_CONTACTS = "name_lookup "
    264                 + "INNER JOIN view_raw_contacts ON (name_lookup.raw_contact_id = "
    265                 + "view_raw_contacts._id)";
    266 
    267         public static final String RAW_CONTACTS_JOIN_ACCOUNTS = Tables.RAW_CONTACTS
    268                 + " JOIN " + Tables.ACCOUNTS + " ON ("
    269                 + AccountsColumns.CONCRETE_ID + "=" + RawContactsColumns.CONCRETE_ACCOUNT_ID
    270                 + ")";
    271     }
    272 
    273     public interface Joins {
    274         /**
    275          * Join string intended to be used with the GROUPS table/view.  The main table must be named
    276          * as "groups".
    277          *
    278          * Adds the "group_member_count column" to the query, which will be null if a group has
    279          * no members.  Use ifnull(group_member_count, 0) if 0 is needed instead.
    280          */
    281         public static final String GROUP_MEMBER_COUNT =
    282                 " LEFT OUTER JOIN (SELECT "
    283                         + "data.data1 AS member_count_group_id, "
    284                         + "COUNT(data.raw_contact_id) AS group_member_count "
    285                     + "FROM data "
    286                     + "WHERE "
    287                         + "data.mimetype_id = (SELECT _id FROM mimetypes WHERE "
    288                             + "mimetypes.mimetype = '" + GroupMembership.CONTENT_ITEM_TYPE + "')"
    289                     + "GROUP BY member_count_group_id) AS member_count_table" // End of inner query
    290                 + " ON (groups._id = member_count_table.member_count_group_id)";
    291     }
    292 
    293     public interface Views {
    294         public static final String DATA = "view_data";
    295         public static final String RAW_CONTACTS = "view_raw_contacts";
    296         public static final String CONTACTS = "view_contacts";
    297         public static final String ENTITIES = "view_entities";
    298         public static final String RAW_ENTITIES = "view_raw_entities";
    299         public static final String GROUPS = "view_groups";
    300         public static final String DATA_USAGE_STAT = "view_data_usage_stat";
    301         public static final String STREAM_ITEMS = "view_stream_items";
    302     }
    303 
    304     public interface Clauses {
    305         final String HAVING_NO_GROUPS = "COUNT(" + DataColumns.CONCRETE_GROUP_ID + ") == 0";
    306 
    307         final String GROUP_BY_ACCOUNT_CONTACT_ID = SettingsColumns.CONCRETE_ACCOUNT_NAME + ","
    308                 + SettingsColumns.CONCRETE_ACCOUNT_TYPE + "," + RawContacts.CONTACT_ID;
    309 
    310         String LOCAL_ACCOUNT_ID =
    311                 "(SELECT " + AccountsColumns._ID +
    312                 " FROM " + Tables.ACCOUNTS +
    313                 " WHERE " +
    314                     AccountsColumns.ACCOUNT_NAME + " IS NULL AND " +
    315                     AccountsColumns.ACCOUNT_TYPE + " IS NULL AND " +
    316                     AccountsColumns.DATA_SET + " IS NULL)";
    317 
    318         final String RAW_CONTACT_IS_LOCAL = RawContactsColumns.CONCRETE_ACCOUNT_ID
    319                 + "=" + LOCAL_ACCOUNT_ID;
    320 
    321         final String ZERO_GROUP_MEMBERSHIPS = "COUNT(" + GroupsColumns.CONCRETE_ID + ")=0";
    322 
    323         final String OUTER_RAW_CONTACTS = "outer_raw_contacts";
    324         final String OUTER_RAW_CONTACTS_ID = OUTER_RAW_CONTACTS + "." + RawContacts._ID;
    325 
    326         final String CONTACT_IS_VISIBLE =
    327                 "SELECT " +
    328                     "MAX((SELECT (CASE WHEN " +
    329                         "(CASE" +
    330                             " WHEN " + RAW_CONTACT_IS_LOCAL +
    331                             " THEN 1 " +
    332                             " WHEN " + ZERO_GROUP_MEMBERSHIPS +
    333                             " THEN " + Settings.UNGROUPED_VISIBLE +
    334                             " ELSE MAX(" + Groups.GROUP_VISIBLE + ")" +
    335                          "END)=1 THEN 1 ELSE 0 END)" +
    336                 " FROM " + Tables.RAW_CONTACTS_JOIN_SETTINGS_DATA_GROUPS +
    337                 " WHERE " + RawContactsColumns.CONCRETE_ID + "=" + OUTER_RAW_CONTACTS_ID + "))" +
    338                 " FROM " + Tables.RAW_CONTACTS + " AS " + OUTER_RAW_CONTACTS +
    339                 " WHERE " + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID +
    340                 " GROUP BY " + RawContacts.CONTACT_ID;
    341 
    342         final String GROUP_HAS_ACCOUNT_AND_SOURCE_ID = Groups.SOURCE_ID + "=? AND "
    343                 + GroupsColumns.ACCOUNT_ID + "=?";
    344 
    345         public static final String CONTACT_VISIBLE =
    346             "EXISTS (SELECT _id FROM " + Tables.VISIBLE_CONTACTS
    347                 + " WHERE " + Tables.CONTACTS +"." + Contacts._ID
    348                         + "=" + Tables.VISIBLE_CONTACTS +"." + Contacts._ID + ")";
    349     }
    350 
    351     public interface ContactsColumns {
    352         public static final String LAST_STATUS_UPDATE_ID = "status_update_id";
    353 
    354         public static final String CONCRETE_ID = Tables.CONTACTS + "." + BaseColumns._ID;
    355 
    356         public static final String CONCRETE_PHOTO_FILE_ID = Tables.CONTACTS + "."
    357                 + Contacts.PHOTO_FILE_ID;
    358         public static final String CONCRETE_TIMES_CONTACTED = Tables.CONTACTS + "."
    359                 + Contacts.TIMES_CONTACTED;
    360         public static final String CONCRETE_LAST_TIME_CONTACTED = Tables.CONTACTS + "."
    361                 + Contacts.LAST_TIME_CONTACTED;
    362         public static final String CONCRETE_STARRED = Tables.CONTACTS + "." + Contacts.STARRED;
    363         public static final String CONCRETE_CUSTOM_RINGTONE = Tables.CONTACTS + "."
    364                 + Contacts.CUSTOM_RINGTONE;
    365         public static final String CONCRETE_SEND_TO_VOICEMAIL = Tables.CONTACTS + "."
    366                 + Contacts.SEND_TO_VOICEMAIL;
    367         public static final String CONCRETE_LOOKUP_KEY = Tables.CONTACTS + "."
    368                 + Contacts.LOOKUP_KEY;
    369         public static final String CONCRETE_CONTACT_LAST_UPDATED_TIMESTAMP = Tables.CONTACTS + "."
    370                 + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP;
    371         public static final String PHONEBOOK_LABEL_PRIMARY = "phonebook_label";
    372         public static final String PHONEBOOK_BUCKET_PRIMARY = "phonebook_bucket";
    373         public static final String PHONEBOOK_LABEL_ALTERNATIVE = "phonebook_label_alt";
    374         public static final String PHONEBOOK_BUCKET_ALTERNATIVE = "phonebook_bucket_alt";
    375     }
    376 
    377     public interface RawContactsColumns {
    378         public static final String CONCRETE_ID =
    379                 Tables.RAW_CONTACTS + "." + BaseColumns._ID;
    380 
    381         public static final String ACCOUNT_ID = "account_id";
    382         public static final String CONCRETE_ACCOUNT_ID = Tables.RAW_CONTACTS + "." + ACCOUNT_ID;
    383         public static final String CONCRETE_SOURCE_ID =
    384                 Tables.RAW_CONTACTS + "." + RawContacts.SOURCE_ID;
    385         public static final String CONCRETE_VERSION =
    386                 Tables.RAW_CONTACTS + "." + RawContacts.VERSION;
    387         public static final String CONCRETE_DIRTY =
    388                 Tables.RAW_CONTACTS + "." + RawContacts.DIRTY;
    389         public static final String CONCRETE_DELETED =
    390                 Tables.RAW_CONTACTS + "." + RawContacts.DELETED;
    391         public static final String CONCRETE_SYNC1 =
    392                 Tables.RAW_CONTACTS + "." + RawContacts.SYNC1;
    393         public static final String CONCRETE_SYNC2 =
    394                 Tables.RAW_CONTACTS + "." + RawContacts.SYNC2;
    395         public static final String CONCRETE_SYNC3 =
    396                 Tables.RAW_CONTACTS + "." + RawContacts.SYNC3;
    397         public static final String CONCRETE_SYNC4 =
    398                 Tables.RAW_CONTACTS + "." + RawContacts.SYNC4;
    399         public static final String CONCRETE_CUSTOM_RINGTONE =
    400                 Tables.RAW_CONTACTS + "." + RawContacts.CUSTOM_RINGTONE;
    401         public static final String CONCRETE_SEND_TO_VOICEMAIL =
    402                 Tables.RAW_CONTACTS + "." + RawContacts.SEND_TO_VOICEMAIL;
    403         public static final String CONCRETE_LAST_TIME_CONTACTED =
    404                 Tables.RAW_CONTACTS + "." + RawContacts.LAST_TIME_CONTACTED;
    405         public static final String CONCRETE_TIMES_CONTACTED =
    406                 Tables.RAW_CONTACTS + "." + RawContacts.TIMES_CONTACTED;
    407         public static final String CONCRETE_STARRED =
    408                 Tables.RAW_CONTACTS + "." + RawContacts.STARRED;
    409 
    410         public static final String DISPLAY_NAME = RawContacts.DISPLAY_NAME_PRIMARY;
    411         public static final String DISPLAY_NAME_SOURCE = RawContacts.DISPLAY_NAME_SOURCE;
    412         public static final String AGGREGATION_NEEDED = "aggregation_needed";
    413 
    414         public static final String CONCRETE_DISPLAY_NAME =
    415                 Tables.RAW_CONTACTS + "." + DISPLAY_NAME;
    416         public static final String CONCRETE_CONTACT_ID =
    417                 Tables.RAW_CONTACTS + "." + RawContacts.CONTACT_ID;
    418         public static final String CONCRETE_NAME_VERIFIED =
    419             Tables.RAW_CONTACTS + "." + RawContacts.NAME_VERIFIED;
    420         public static final String PHONEBOOK_LABEL_PRIMARY =
    421             ContactsColumns.PHONEBOOK_LABEL_PRIMARY;
    422         public static final String PHONEBOOK_BUCKET_PRIMARY =
    423             ContactsColumns.PHONEBOOK_BUCKET_PRIMARY;
    424         public static final String PHONEBOOK_LABEL_ALTERNATIVE =
    425             ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE;
    426         public static final String PHONEBOOK_BUCKET_ALTERNATIVE =
    427             ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE;
    428     }
    429 
    430     public interface ViewRawContactsColumns {
    431         String CONCRETE_ACCOUNT_NAME = Views.RAW_CONTACTS + "." + RawContacts.ACCOUNT_NAME;
    432         String CONCRETE_ACCOUNT_TYPE = Views.RAW_CONTACTS + "." + RawContacts.ACCOUNT_TYPE;
    433         String CONCRETE_DATA_SET = Views.RAW_CONTACTS + "." + RawContacts.DATA_SET;
    434     }
    435 
    436     public interface DataColumns {
    437         public static final String PACKAGE_ID = "package_id";
    438         public static final String MIMETYPE_ID = "mimetype_id";
    439 
    440         public static final String CONCRETE_ID = Tables.DATA + "." + BaseColumns._ID;
    441         public static final String CONCRETE_MIMETYPE_ID = Tables.DATA + "." + MIMETYPE_ID;
    442         public static final String CONCRETE_RAW_CONTACT_ID = Tables.DATA + "."
    443                 + Data.RAW_CONTACT_ID;
    444         public static final String CONCRETE_GROUP_ID = Tables.DATA + "."
    445                 + GroupMembership.GROUP_ROW_ID;
    446 
    447         public static final String CONCRETE_DATA1 = Tables.DATA + "." + Data.DATA1;
    448         public static final String CONCRETE_DATA2 = Tables.DATA + "." + Data.DATA2;
    449         public static final String CONCRETE_DATA3 = Tables.DATA + "." + Data.DATA3;
    450         public static final String CONCRETE_DATA4 = Tables.DATA + "." + Data.DATA4;
    451         public static final String CONCRETE_DATA5 = Tables.DATA + "." + Data.DATA5;
    452         public static final String CONCRETE_DATA6 = Tables.DATA + "." + Data.DATA6;
    453         public static final String CONCRETE_DATA7 = Tables.DATA + "." + Data.DATA7;
    454         public static final String CONCRETE_DATA8 = Tables.DATA + "." + Data.DATA8;
    455         public static final String CONCRETE_DATA9 = Tables.DATA + "." + Data.DATA9;
    456         public static final String CONCRETE_DATA10 = Tables.DATA + "." + Data.DATA10;
    457         public static final String CONCRETE_DATA11 = Tables.DATA + "." + Data.DATA11;
    458         public static final String CONCRETE_DATA12 = Tables.DATA + "." + Data.DATA12;
    459         public static final String CONCRETE_DATA13 = Tables.DATA + "." + Data.DATA13;
    460         public static final String CONCRETE_DATA14 = Tables.DATA + "." + Data.DATA14;
    461         public static final String CONCRETE_DATA15 = Tables.DATA + "." + Data.DATA15;
    462         public static final String CONCRETE_IS_PRIMARY = Tables.DATA + "." + Data.IS_PRIMARY;
    463         public static final String CONCRETE_PACKAGE_ID = Tables.DATA + "." + PACKAGE_ID;
    464     }
    465 
    466     // Used only for legacy API support
    467     public interface ExtensionsColumns {
    468         public static final String NAME = Data.DATA1;
    469         public static final String VALUE = Data.DATA2;
    470     }
    471 
    472     public interface GroupMembershipColumns {
    473         public static final String RAW_CONTACT_ID = Data.RAW_CONTACT_ID;
    474         public static final String GROUP_ROW_ID = GroupMembership.GROUP_ROW_ID;
    475     }
    476 
    477     public interface GroupsColumns {
    478         public static final String PACKAGE_ID = "package_id";
    479         public static final String CONCRETE_PACKAGE_ID = Tables.GROUPS + "." + PACKAGE_ID;
    480 
    481         public static final String CONCRETE_ID = Tables.GROUPS + "." + BaseColumns._ID;
    482         public static final String CONCRETE_SOURCE_ID = Tables.GROUPS + "." + Groups.SOURCE_ID;
    483 
    484         public static final String ACCOUNT_ID = "account_id";
    485         public static final String CONCRETE_ACCOUNT_ID = Tables.GROUPS + "." + ACCOUNT_ID;
    486     }
    487 
    488     public interface ViewGroupsColumns {
    489         String CONCRETE_ACCOUNT_NAME = Views.GROUPS + "." + Groups.ACCOUNT_NAME;
    490         String CONCRETE_ACCOUNT_TYPE = Views.GROUPS + "." + Groups.ACCOUNT_TYPE;
    491         String CONCRETE_DATA_SET = Views.GROUPS + "." + Groups.DATA_SET;
    492     }
    493 
    494     public interface ActivitiesColumns {
    495         public static final String PACKAGE_ID = "package_id";
    496         public static final String MIMETYPE_ID = "mimetype_id";
    497     }
    498 
    499     public interface PhoneLookupColumns {
    500         public static final String _ID = BaseColumns._ID;
    501         public static final String DATA_ID = "data_id";
    502         public static final String RAW_CONTACT_ID = "raw_contact_id";
    503         public static final String NORMALIZED_NUMBER = "normalized_number";
    504         public static final String MIN_MATCH = "min_match";
    505     }
    506 
    507     public interface NameLookupColumns {
    508         public static final String RAW_CONTACT_ID = "raw_contact_id";
    509         public static final String DATA_ID = "data_id";
    510         public static final String NORMALIZED_NAME = "normalized_name";
    511         public static final String NAME_TYPE = "name_type";
    512     }
    513 
    514     public final static class NameLookupType {
    515         public static final int NAME_EXACT = 0;
    516         public static final int NAME_VARIANT = 1;
    517         public static final int NAME_COLLATION_KEY = 2;
    518         public static final int NICKNAME = 3;
    519         public static final int EMAIL_BASED_NICKNAME = 4;
    520 
    521         // This is the highest name lookup type code plus one
    522         public static final int TYPE_COUNT = 5;
    523 
    524         public static boolean isBasedOnStructuredName(int nameLookupType) {
    525             return nameLookupType == NameLookupType.NAME_EXACT
    526                     || nameLookupType == NameLookupType.NAME_VARIANT
    527                     || nameLookupType == NameLookupType.NAME_COLLATION_KEY;
    528         }
    529     }
    530 
    531     public interface PackagesColumns {
    532         public static final String _ID = BaseColumns._ID;
    533         public static final String PACKAGE = "package";
    534 
    535         public static final String CONCRETE_ID = Tables.PACKAGES + "." + _ID;
    536     }
    537 
    538     public interface MimetypesColumns {
    539         public static final String _ID = BaseColumns._ID;
    540         public static final String MIMETYPE = "mimetype";
    541 
    542         public static final String CONCRETE_ID = Tables.MIMETYPES + "." + BaseColumns._ID;
    543         public static final String CONCRETE_MIMETYPE = Tables.MIMETYPES + "." + MIMETYPE;
    544     }
    545 
    546     public interface AggregationExceptionColumns {
    547         public static final String _ID = BaseColumns._ID;
    548     }
    549 
    550     public interface NicknameLookupColumns {
    551         public static final String NAME = "name";
    552         public static final String CLUSTER = "cluster";
    553     }
    554 
    555     public interface SettingsColumns {
    556         public static final String CONCRETE_ACCOUNT_NAME = Tables.SETTINGS + "."
    557                 + Settings.ACCOUNT_NAME;
    558         public static final String CONCRETE_ACCOUNT_TYPE = Tables.SETTINGS + "."
    559                 + Settings.ACCOUNT_TYPE;
    560         public static final String CONCRETE_DATA_SET = Tables.SETTINGS + "."
    561                 + Settings.DATA_SET;
    562     }
    563 
    564     public interface PresenceColumns {
    565         String RAW_CONTACT_ID = "presence_raw_contact_id";
    566         String CONTACT_ID = "presence_contact_id";
    567     }
    568 
    569     public interface AggregatedPresenceColumns {
    570         String CONTACT_ID = "presence_contact_id";
    571 
    572         String CONCRETE_CONTACT_ID = Tables.AGGREGATED_PRESENCE + "." + CONTACT_ID;
    573     }
    574 
    575     public interface StatusUpdatesColumns {
    576         String DATA_ID = "status_update_data_id";
    577 
    578         String CONCRETE_DATA_ID = Tables.STATUS_UPDATES + "." + DATA_ID;
    579 
    580         String CONCRETE_PRESENCE = Tables.STATUS_UPDATES + "." + StatusUpdates.PRESENCE;
    581         String CONCRETE_STATUS = Tables.STATUS_UPDATES + "." + StatusUpdates.STATUS;
    582         String CONCRETE_STATUS_TIMESTAMP = Tables.STATUS_UPDATES + "."
    583                 + StatusUpdates.STATUS_TIMESTAMP;
    584         String CONCRETE_STATUS_RES_PACKAGE = Tables.STATUS_UPDATES + "."
    585                 + StatusUpdates.STATUS_RES_PACKAGE;
    586         String CONCRETE_STATUS_LABEL = Tables.STATUS_UPDATES + "." + StatusUpdates.STATUS_LABEL;
    587         String CONCRETE_STATUS_ICON = Tables.STATUS_UPDATES + "." + StatusUpdates.STATUS_ICON;
    588     }
    589 
    590     public interface ContactsStatusUpdatesColumns {
    591         String ALIAS = "contacts_" + Tables.STATUS_UPDATES;
    592 
    593         String CONCRETE_DATA_ID = ALIAS + "." + StatusUpdatesColumns.DATA_ID;
    594 
    595         String CONCRETE_PRESENCE = ALIAS + "." + StatusUpdates.PRESENCE;
    596         String CONCRETE_STATUS = ALIAS + "." + StatusUpdates.STATUS;
    597         String CONCRETE_STATUS_TIMESTAMP = ALIAS + "." + StatusUpdates.STATUS_TIMESTAMP;
    598         String CONCRETE_STATUS_RES_PACKAGE = ALIAS + "." + StatusUpdates.STATUS_RES_PACKAGE;
    599         String CONCRETE_STATUS_LABEL = ALIAS + "." + StatusUpdates.STATUS_LABEL;
    600         String CONCRETE_STATUS_ICON = ALIAS + "." + StatusUpdates.STATUS_ICON;
    601     }
    602 
    603     public interface StreamItemsColumns {
    604         final String CONCRETE_ID = Tables.STREAM_ITEMS + "." + BaseColumns._ID;
    605         final String CONCRETE_RAW_CONTACT_ID =
    606                 Tables.STREAM_ITEMS + "." + StreamItems.RAW_CONTACT_ID;
    607         final String CONCRETE_PACKAGE = Tables.STREAM_ITEMS + "." + StreamItems.RES_PACKAGE;
    608         final String CONCRETE_ICON = Tables.STREAM_ITEMS + "." + StreamItems.RES_ICON;
    609         final String CONCRETE_LABEL = Tables.STREAM_ITEMS + "." + StreamItems.RES_LABEL;
    610         final String CONCRETE_TEXT = Tables.STREAM_ITEMS + "." + StreamItems.TEXT;
    611         final String CONCRETE_TIMESTAMP = Tables.STREAM_ITEMS + "." + StreamItems.TIMESTAMP;
    612         final String CONCRETE_COMMENTS = Tables.STREAM_ITEMS + "." + StreamItems.COMMENTS;
    613         final String CONCRETE_SYNC1 = Tables.STREAM_ITEMS + "." + StreamItems.SYNC1;
    614         final String CONCRETE_SYNC2 = Tables.STREAM_ITEMS + "." + StreamItems.SYNC2;
    615         final String CONCRETE_SYNC3 = Tables.STREAM_ITEMS + "." + StreamItems.SYNC3;
    616         final String CONCRETE_SYNC4 = Tables.STREAM_ITEMS + "." + StreamItems.SYNC4;
    617     }
    618 
    619     public interface StreamItemPhotosColumns {
    620         final String CONCRETE_ID = Tables.STREAM_ITEM_PHOTOS + "." + BaseColumns._ID;
    621         final String CONCRETE_STREAM_ITEM_ID = Tables.STREAM_ITEM_PHOTOS + "."
    622                 + StreamItemPhotos.STREAM_ITEM_ID;
    623         final String CONCRETE_SORT_INDEX =
    624                 Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.SORT_INDEX;
    625         final String CONCRETE_PHOTO_FILE_ID = Tables.STREAM_ITEM_PHOTOS + "."
    626                 + StreamItemPhotos.PHOTO_FILE_ID;
    627         final String CONCRETE_SYNC1 = Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.SYNC1;
    628         final String CONCRETE_SYNC2 = Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.SYNC2;
    629         final String CONCRETE_SYNC3 = Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.SYNC3;
    630         final String CONCRETE_SYNC4 = Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.SYNC4;
    631     }
    632 
    633     public interface PhotoFilesColumns {
    634         String CONCRETE_ID = Tables.PHOTO_FILES + "." + BaseColumns._ID;
    635         String CONCRETE_HEIGHT = Tables.PHOTO_FILES + "." + PhotoFiles.HEIGHT;
    636         String CONCRETE_WIDTH = Tables.PHOTO_FILES + "." + PhotoFiles.WIDTH;
    637         String CONCRETE_FILESIZE = Tables.PHOTO_FILES + "." + PhotoFiles.FILESIZE;
    638     }
    639 
    640     public interface PropertiesColumns {
    641         String PROPERTY_KEY = "property_key";
    642         String PROPERTY_VALUE = "property_value";
    643     }
    644 
    645     public interface AccountsColumns extends BaseColumns {
    646         String CONCRETE_ID = Tables.ACCOUNTS + "." + BaseColumns._ID;
    647 
    648         String ACCOUNT_NAME = RawContacts.ACCOUNT_NAME;
    649         String ACCOUNT_TYPE = RawContacts.ACCOUNT_TYPE;
    650         String DATA_SET = RawContacts.DATA_SET;
    651 
    652         String CONCRETE_ACCOUNT_NAME = Tables.ACCOUNTS + "." + ACCOUNT_NAME;
    653         String CONCRETE_ACCOUNT_TYPE = Tables.ACCOUNTS + "." + ACCOUNT_TYPE;
    654         String CONCRETE_DATA_SET = Tables.ACCOUNTS + "." + DATA_SET;
    655     }
    656 
    657     public static final class DirectoryColumns {
    658         public static final String TYPE_RESOURCE_NAME = "typeResourceName";
    659     }
    660 
    661     public static final class SearchIndexColumns {
    662         public static final String CONTACT_ID = "contact_id";
    663         public static final String CONTENT = "content";
    664         public static final String NAME = "name";
    665         public static final String TOKENS = "tokens";
    666     }
    667 
    668     /**
    669      * Private table for calculating per-contact-method ranking.
    670      */
    671     public static final class DataUsageStatColumns {
    672         /** type: INTEGER (long) */
    673         public static final String _ID = "stat_id";
    674         public static final String CONCRETE_ID = Tables.DATA_USAGE_STAT + "." + _ID;
    675 
    676         /** type: INTEGER (long) */
    677         public static final String DATA_ID = "data_id";
    678         public static final String CONCRETE_DATA_ID = Tables.DATA_USAGE_STAT + "." + DATA_ID;
    679 
    680         /** type: INTEGER (long) */
    681         public static final String LAST_TIME_USED = "last_time_used";
    682         public static final String CONCRETE_LAST_TIME_USED =
    683                 Tables.DATA_USAGE_STAT + "." + LAST_TIME_USED;
    684 
    685         /** type: INTEGER */
    686         public static final String TIMES_USED = "times_used";
    687         public static final String CONCRETE_TIMES_USED =
    688                 Tables.DATA_USAGE_STAT + "." + TIMES_USED;
    689 
    690         /** type: INTEGER */
    691         public static final String USAGE_TYPE_INT = "usage_type";
    692         public static final String CONCRETE_USAGE_TYPE =
    693                 Tables.DATA_USAGE_STAT + "." + USAGE_TYPE_INT;
    694 
    695         /**
    696          * Integer values for USAGE_TYPE.
    697          *
    698          * @see ContactsContract.DataUsageFeedback#USAGE_TYPE
    699          */
    700         public static final int USAGE_TYPE_INT_CALL = 0;
    701         public static final int USAGE_TYPE_INT_LONG_TEXT = 1;
    702         public static final int USAGE_TYPE_INT_SHORT_TEXT = 2;
    703     }
    704 
    705     public interface Projections {
    706         String[] ID = new String[] {BaseColumns._ID};
    707         String[] LITERAL_ONE = new String[] {"1"};
    708     }
    709 
    710     /**
    711      * Property names for {@link ContactsDatabaseHelper#getProperty} and
    712      * {@link ContactsDatabaseHelper#setProperty}.
    713      */
    714     public interface DbProperties {
    715         String DIRECTORY_SCAN_COMPLETE = "directoryScanComplete";
    716         String AGGREGATION_ALGORITHM = "aggregation_v2";
    717         String KNOWN_ACCOUNTS = "known_accounts";
    718         String ICU_VERSION = "icu_version";
    719         String LOCALE = "locale";
    720     }
    721 
    722     /** In-memory cache of previously found MIME-type mappings */
    723     private final HashMap<String, Long> mMimetypeCache = new HashMap<String, Long>();
    724 
    725     /** In-memory cache the packages table */
    726     private final HashMap<String, Long> mPackageCache = new HashMap<String, Long>();
    727 
    728     private long mMimeTypeIdEmail;
    729     private long mMimeTypeIdIm;
    730     private long mMimeTypeIdNickname;
    731     private long mMimeTypeIdOrganization;
    732     private long mMimeTypeIdPhone;
    733     private long mMimeTypeIdSip;
    734     private long mMimeTypeIdStructuredName;
    735     private long mMimeTypeIdStructuredPostal;
    736 
    737     /** Compiled statements for querying and inserting mappings */
    738     private SQLiteStatement mContactIdQuery;
    739     private SQLiteStatement mAggregationModeQuery;
    740     private SQLiteStatement mDataMimetypeQuery;
    741 
    742     /** Precompiled sql statement for setting a data record to the primary. */
    743     private SQLiteStatement mSetPrimaryStatement;
    744     /** Precompiled sql statement for setting a data record to the super primary. */
    745     private SQLiteStatement mSetSuperPrimaryStatement;
    746     /** Precompiled sql statement for clearing super primary of a single record. */
    747     private SQLiteStatement mClearSuperPrimaryStatement;
    748     /** Precompiled sql statement for updating a contact display name */
    749     private SQLiteStatement mRawContactDisplayNameUpdate;
    750 
    751     private SQLiteStatement mNameLookupInsert;
    752     private SQLiteStatement mNameLookupDelete;
    753     private SQLiteStatement mStatusUpdateAutoTimestamp;
    754     private SQLiteStatement mStatusUpdateInsert;
    755     private SQLiteStatement mStatusUpdateReplace;
    756     private SQLiteStatement mStatusAttributionUpdate;
    757     private SQLiteStatement mStatusUpdateDelete;
    758     private SQLiteStatement mResetNameVerifiedForOtherRawContacts;
    759     private SQLiteStatement mContactInDefaultDirectoryQuery;
    760 
    761     private final Context mContext;
    762     private final boolean mDatabaseOptimizationEnabled;
    763     private final SyncStateContentProviderHelper mSyncState;
    764     private final CountryMonitor mCountryMonitor;
    765     private StringBuilder mSb = new StringBuilder();
    766 
    767     private static ContactsDatabaseHelper sSingleton = null;
    768 
    769     private boolean mUseStrictPhoneNumberComparison;
    770 
    771     private String[] mSelectionArgs1 = new String[1];
    772     private NameSplitter.Name mName = new NameSplitter.Name();
    773     private CharArrayBuffer mCharArrayBuffer = new CharArrayBuffer(128);
    774     private NameSplitter mNameSplitter;
    775 
    776     public static synchronized ContactsDatabaseHelper getInstance(Context context) {
    777         if (sSingleton == null) {
    778             sSingleton = new ContactsDatabaseHelper(context, DATABASE_NAME, true);
    779         }
    780         return sSingleton;
    781     }
    782 
    783     /**
    784      * Returns a new instance for unit tests.
    785      */
    786     @NeededForTesting
    787     static ContactsDatabaseHelper getNewInstanceForTest(Context context) {
    788         return new ContactsDatabaseHelper(context, null, false);
    789     }
    790 
    791     protected ContactsDatabaseHelper(
    792             Context context, String databaseName, boolean optimizationEnabled) {
    793         super(context, databaseName, null, DATABASE_VERSION);
    794         mDatabaseOptimizationEnabled = optimizationEnabled;
    795         Resources resources = context.getResources();
    796 
    797         mContext = context;
    798         mSyncState = new SyncStateContentProviderHelper();
    799         mCountryMonitor = new CountryMonitor(context);
    800         mUseStrictPhoneNumberComparison =
    801                 resources.getBoolean(
    802                         com.android.internal.R.bool.config_use_strict_phone_number_comparation);
    803     }
    804 
    805     public SQLiteDatabase getDatabase(boolean writable) {
    806         return writable ? getWritableDatabase() : getReadableDatabase();
    807     }
    808 
    809     /**
    810      * Clear all the cached database information and re-initialize it.
    811      *
    812      * @param db target database
    813      */
    814     private void refreshDatabaseCaches(SQLiteDatabase db) {
    815         mStatusUpdateDelete = null;
    816         mStatusUpdateReplace = null;
    817         mStatusUpdateInsert = null;
    818         mStatusUpdateAutoTimestamp = null;
    819         mStatusAttributionUpdate = null;
    820         mResetNameVerifiedForOtherRawContacts = null;
    821         mRawContactDisplayNameUpdate = null;
    822         mSetPrimaryStatement = null;
    823         mClearSuperPrimaryStatement = null;
    824         mSetSuperPrimaryStatement = null;
    825         mNameLookupInsert = null;
    826         mNameLookupDelete = null;
    827         mDataMimetypeQuery = null;
    828         mContactIdQuery = null;
    829         mAggregationModeQuery = null;
    830         mContactInDefaultDirectoryQuery = null;
    831 
    832         initializeCache(db);
    833     }
    834 
    835     /**
    836      * (Re-)initialize the cached database information.
    837      *
    838      * @param db target database
    839      */
    840     private void initializeCache(SQLiteDatabase db) {
    841         mMimetypeCache.clear();
    842         mPackageCache.clear();
    843 
    844         // TODO: This could be optimized into one query instead of 7
    845         //        Also: We shouldn't have those fields in the first place. This should just be
    846         //        in the cache
    847         mMimeTypeIdEmail = lookupMimeTypeId(Email.CONTENT_ITEM_TYPE, db);
    848         mMimeTypeIdIm = lookupMimeTypeId(Im.CONTENT_ITEM_TYPE, db);
    849         mMimeTypeIdNickname = lookupMimeTypeId(Nickname.CONTENT_ITEM_TYPE, db);
    850         mMimeTypeIdOrganization = lookupMimeTypeId(Organization.CONTENT_ITEM_TYPE, db);
    851         mMimeTypeIdPhone = lookupMimeTypeId(Phone.CONTENT_ITEM_TYPE, db);
    852         mMimeTypeIdSip = lookupMimeTypeId(SipAddress.CONTENT_ITEM_TYPE, db);
    853         mMimeTypeIdStructuredName = lookupMimeTypeId(StructuredName.CONTENT_ITEM_TYPE, db);
    854         mMimeTypeIdStructuredPostal = lookupMimeTypeId(StructuredPostal.CONTENT_ITEM_TYPE, db);
    855     }
    856 
    857     @Override
    858     public void onOpen(SQLiteDatabase db) {
    859         refreshDatabaseCaches(db);
    860 
    861         mSyncState.onDatabaseOpened(db);
    862 
    863         db.execSQL("ATTACH DATABASE ':memory:' AS " + DATABASE_PRESENCE + ";");
    864         db.execSQL("CREATE TABLE IF NOT EXISTS " + DATABASE_PRESENCE + "." + Tables.PRESENCE + " ("+
    865                 StatusUpdates.DATA_ID + " INTEGER PRIMARY KEY REFERENCES data(_id)," +
    866                 StatusUpdates.PROTOCOL + " INTEGER NOT NULL," +
    867                 StatusUpdates.CUSTOM_PROTOCOL + " TEXT," +
    868                 StatusUpdates.IM_HANDLE + " TEXT," +
    869                 StatusUpdates.IM_ACCOUNT + " TEXT," +
    870                 PresenceColumns.CONTACT_ID + " INTEGER REFERENCES contacts(_id)," +
    871                 PresenceColumns.RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)," +
    872                 StatusUpdates.PRESENCE + " INTEGER," +
    873                 StatusUpdates.CHAT_CAPABILITY + " INTEGER NOT NULL DEFAULT 0," +
    874                 "UNIQUE(" + StatusUpdates.PROTOCOL + ", " + StatusUpdates.CUSTOM_PROTOCOL
    875                     + ", " + StatusUpdates.IM_HANDLE + ", " + StatusUpdates.IM_ACCOUNT + ")" +
    876         ");");
    877 
    878         db.execSQL("CREATE INDEX IF NOT EXISTS " + DATABASE_PRESENCE + ".presenceIndex" + " ON "
    879                 + Tables.PRESENCE + " (" + PresenceColumns.RAW_CONTACT_ID + ");");
    880         db.execSQL("CREATE INDEX IF NOT EXISTS " + DATABASE_PRESENCE + ".presenceIndex2" + " ON "
    881                 + Tables.PRESENCE + " (" + PresenceColumns.CONTACT_ID + ");");
    882 
    883         db.execSQL("CREATE TABLE IF NOT EXISTS "
    884                 + DATABASE_PRESENCE + "." + Tables.AGGREGATED_PRESENCE + " ("+
    885                 AggregatedPresenceColumns.CONTACT_ID
    886                         + " INTEGER PRIMARY KEY REFERENCES contacts(_id)," +
    887                 StatusUpdates.PRESENCE + " INTEGER," +
    888                 StatusUpdates.CHAT_CAPABILITY + " INTEGER NOT NULL DEFAULT 0" +
    889         ");");
    890 
    891 
    892         db.execSQL("CREATE TRIGGER " + DATABASE_PRESENCE + "." + Tables.PRESENCE + "_deleted"
    893                 + " BEFORE DELETE ON " + DATABASE_PRESENCE + "." + Tables.PRESENCE
    894                 + " BEGIN "
    895                 + "   DELETE FROM " + Tables.AGGREGATED_PRESENCE
    896                 + "     WHERE " + AggregatedPresenceColumns.CONTACT_ID + " = " +
    897                         "(SELECT " + PresenceColumns.CONTACT_ID +
    898                         " FROM " + Tables.PRESENCE +
    899                         " WHERE " + PresenceColumns.RAW_CONTACT_ID
    900                                 + "=OLD." + PresenceColumns.RAW_CONTACT_ID +
    901                         " AND NOT EXISTS" +
    902                                 "(SELECT " + PresenceColumns.RAW_CONTACT_ID +
    903                                 " FROM " + Tables.PRESENCE +
    904                                 " WHERE " + PresenceColumns.CONTACT_ID
    905                                         + "=OLD." + PresenceColumns.CONTACT_ID +
    906                                 " AND " + PresenceColumns.RAW_CONTACT_ID
    907                                         + "!=OLD." + PresenceColumns.RAW_CONTACT_ID + "));"
    908                 + " END");
    909 
    910         final String replaceAggregatePresenceSql =
    911                 "INSERT OR REPLACE INTO " + Tables.AGGREGATED_PRESENCE + "("
    912                         + AggregatedPresenceColumns.CONTACT_ID + ", "
    913                         + StatusUpdates.PRESENCE + ", "
    914                         + StatusUpdates.CHAT_CAPABILITY + ")"
    915                 + " SELECT "
    916                         + PresenceColumns.CONTACT_ID + ","
    917                         + StatusUpdates.PRESENCE + ","
    918                         + StatusUpdates.CHAT_CAPABILITY
    919                 + " FROM " + Tables.PRESENCE
    920                 + " WHERE "
    921                     + " (ifnull(" + StatusUpdates.PRESENCE + ",0)  * 10 "
    922                             + "+ ifnull(" + StatusUpdates.CHAT_CAPABILITY + ", 0))"
    923                     + " = (SELECT "
    924                         + "MAX (ifnull(" + StatusUpdates.PRESENCE + ",0)  * 10 "
    925                                 + "+ ifnull(" + StatusUpdates.CHAT_CAPABILITY + ", 0))"
    926                         + " FROM " + Tables.PRESENCE
    927                         + " WHERE " + PresenceColumns.CONTACT_ID
    928                             + "=NEW." + PresenceColumns.CONTACT_ID
    929                     + ")"
    930                 + " AND " + PresenceColumns.CONTACT_ID + "=NEW." + PresenceColumns.CONTACT_ID + ";";
    931 
    932         db.execSQL("CREATE TRIGGER " + DATABASE_PRESENCE + "." + Tables.PRESENCE + "_inserted"
    933                 + " AFTER INSERT ON " + DATABASE_PRESENCE + "." + Tables.PRESENCE
    934                 + " BEGIN "
    935                 + replaceAggregatePresenceSql
    936                 + " END");
    937 
    938         db.execSQL("CREATE TRIGGER " + DATABASE_PRESENCE + "." + Tables.PRESENCE + "_updated"
    939                 + " AFTER UPDATE ON " + DATABASE_PRESENCE + "." + Tables.PRESENCE
    940                 + " BEGIN "
    941                 + replaceAggregatePresenceSql
    942                 + " END");
    943     }
    944 
    945     @Override
    946     public void onCreate(SQLiteDatabase db) {
    947         Log.i(TAG, "Bootstrapping database version: " + DATABASE_VERSION);
    948 
    949         mSyncState.createDatabase(db);
    950 
    951         db.execSQL("CREATE TABLE " + Tables.ACCOUNTS + " (" +
    952                 AccountsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
    953                 AccountsColumns.ACCOUNT_NAME + " TEXT, " +
    954                 AccountsColumns.ACCOUNT_TYPE + " TEXT, " +
    955                 AccountsColumns.DATA_SET + " TEXT" +
    956         ");");
    957 
    958         // One row per group of contacts corresponding to the same person
    959         db.execSQL("CREATE TABLE " + Tables.CONTACTS + " (" +
    960                 BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
    961                 Contacts.NAME_RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)," +
    962                 Contacts.PHOTO_ID + " INTEGER REFERENCES data(_id)," +
    963                 Contacts.PHOTO_FILE_ID + " INTEGER REFERENCES photo_files(_id)," +
    964                 Contacts.CUSTOM_RINGTONE + " TEXT," +
    965                 Contacts.SEND_TO_VOICEMAIL + " INTEGER NOT NULL DEFAULT 0," +
    966                 Contacts.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
    967                 Contacts.LAST_TIME_CONTACTED + " INTEGER," +
    968                 Contacts.STARRED + " INTEGER NOT NULL DEFAULT 0," +
    969                 Contacts.HAS_PHONE_NUMBER + " INTEGER NOT NULL DEFAULT 0," +
    970                 Contacts.LOOKUP_KEY + " TEXT," +
    971                 ContactsColumns.LAST_STATUS_UPDATE_ID + " INTEGER REFERENCES data(_id)," +
    972                 Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + " INTEGER" +
    973         ");");
    974 
    975         ContactsTableUtil.createIndexes(db);
    976 
    977         // deleted_contacts table
    978         DeletedContactsTableUtil.create(db);
    979 
    980         // Raw_contacts table
    981         db.execSQL("CREATE TABLE " + Tables.RAW_CONTACTS + " (" +
    982                 RawContacts._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
    983                 RawContactsColumns.ACCOUNT_ID + " INTEGER REFERENCES " +
    984                     Tables.ACCOUNTS + "(" + AccountsColumns._ID + ")," +
    985                 RawContacts.SOURCE_ID + " TEXT," +
    986                 RawContacts.RAW_CONTACT_IS_READ_ONLY + " INTEGER NOT NULL DEFAULT 0," +
    987                 RawContacts.VERSION + " INTEGER NOT NULL DEFAULT 1," +
    988                 RawContacts.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
    989                 RawContacts.DELETED + " INTEGER NOT NULL DEFAULT 0," +
    990                 RawContacts.CONTACT_ID + " INTEGER REFERENCES contacts(_id)," +
    991                 RawContacts.AGGREGATION_MODE + " INTEGER NOT NULL DEFAULT " +
    992                         RawContacts.AGGREGATION_MODE_DEFAULT + "," +
    993                 RawContactsColumns.AGGREGATION_NEEDED + " INTEGER NOT NULL DEFAULT 1," +
    994                 RawContacts.CUSTOM_RINGTONE + " TEXT," +
    995                 RawContacts.SEND_TO_VOICEMAIL + " INTEGER NOT NULL DEFAULT 0," +
    996                 RawContacts.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
    997                 RawContacts.LAST_TIME_CONTACTED + " INTEGER," +
    998                 RawContacts.STARRED + " INTEGER NOT NULL DEFAULT 0," +
    999                 RawContacts.DISPLAY_NAME_PRIMARY + " TEXT," +
   1000                 RawContacts.DISPLAY_NAME_ALTERNATIVE + " TEXT," +
   1001                 RawContacts.DISPLAY_NAME_SOURCE + " INTEGER NOT NULL DEFAULT " +
   1002                         DisplayNameSources.UNDEFINED + "," +
   1003                 RawContacts.PHONETIC_NAME + " TEXT," +
   1004                 // TODO: PHONETIC_NAME_STYLE should be INTEGER. There is a
   1005                 // mismatch between how the column is created here (TEXT) and
   1006                 // how it is created in upgradeToVersion205 (INTEGER).
   1007                 RawContacts.PHONETIC_NAME_STYLE + " TEXT," +
   1008                 RawContacts.SORT_KEY_PRIMARY + " TEXT COLLATE " +
   1009                         ContactsProvider2.PHONEBOOK_COLLATOR_NAME + "," +
   1010                 RawContactsColumns.PHONEBOOK_LABEL_PRIMARY + " TEXT," +
   1011                 RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY + " INTEGER," +
   1012                 RawContacts.SORT_KEY_ALTERNATIVE + " TEXT COLLATE " +
   1013                         ContactsProvider2.PHONEBOOK_COLLATOR_NAME + "," +
   1014                 RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + " TEXT," +
   1015                 RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE + " INTEGER," +
   1016                 RawContacts.NAME_VERIFIED + " INTEGER NOT NULL DEFAULT 0," +
   1017                 RawContacts.SYNC1 + " TEXT, " +
   1018                 RawContacts.SYNC2 + " TEXT, " +
   1019                 RawContacts.SYNC3 + " TEXT, " +
   1020                 RawContacts.SYNC4 + " TEXT " +
   1021         ");");
   1022 
   1023         db.execSQL("CREATE INDEX raw_contacts_contact_id_index ON " + Tables.RAW_CONTACTS + " (" +
   1024                 RawContacts.CONTACT_ID +
   1025         ");");
   1026 
   1027         db.execSQL("CREATE INDEX raw_contacts_source_id_account_id_index ON " +
   1028                 Tables.RAW_CONTACTS + " (" +
   1029                 RawContacts.SOURCE_ID + ", " +
   1030                 RawContactsColumns.ACCOUNT_ID +
   1031         ");");
   1032 
   1033         db.execSQL("CREATE TABLE " + Tables.STREAM_ITEMS + " (" +
   1034                 StreamItems._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
   1035                 StreamItems.RAW_CONTACT_ID + " INTEGER NOT NULL, " +
   1036                 StreamItems.RES_PACKAGE + " TEXT, " +
   1037                 StreamItems.RES_ICON + " TEXT, " +
   1038                 StreamItems.RES_LABEL + " TEXT, " +
   1039                 StreamItems.TEXT + " TEXT, " +
   1040                 StreamItems.TIMESTAMP + " INTEGER NOT NULL, " +
   1041                 StreamItems.COMMENTS + " TEXT, " +
   1042                 StreamItems.SYNC1 + " TEXT, " +
   1043                 StreamItems.SYNC2 + " TEXT, " +
   1044                 StreamItems.SYNC3 + " TEXT, " +
   1045                 StreamItems.SYNC4 + " TEXT, " +
   1046                 "FOREIGN KEY(" + StreamItems.RAW_CONTACT_ID + ") REFERENCES " +
   1047                         Tables.RAW_CONTACTS + "(" + RawContacts._ID + "));");
   1048 
   1049         db.execSQL("CREATE TABLE " + Tables.STREAM_ITEM_PHOTOS + " (" +
   1050                 StreamItemPhotos._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
   1051                 StreamItemPhotos.STREAM_ITEM_ID + " INTEGER NOT NULL, " +
   1052                 StreamItemPhotos.SORT_INDEX + " INTEGER, " +
   1053                 StreamItemPhotos.PHOTO_FILE_ID + " INTEGER NOT NULL, " +
   1054                 StreamItemPhotos.SYNC1 + " TEXT, " +
   1055                 StreamItemPhotos.SYNC2 + " TEXT, " +
   1056                 StreamItemPhotos.SYNC3 + " TEXT, " +
   1057                 StreamItemPhotos.SYNC4 + " TEXT, " +
   1058                 "FOREIGN KEY(" + StreamItemPhotos.STREAM_ITEM_ID + ") REFERENCES " +
   1059                         Tables.STREAM_ITEMS + "(" + StreamItems._ID + "));");
   1060 
   1061         db.execSQL("CREATE TABLE " + Tables.PHOTO_FILES + " (" +
   1062                 PhotoFiles._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
   1063                 PhotoFiles.HEIGHT + " INTEGER NOT NULL, " +
   1064                 PhotoFiles.WIDTH + " INTEGER NOT NULL, " +
   1065                 PhotoFiles.FILESIZE + " INTEGER NOT NULL);");
   1066 
   1067         // TODO readd the index and investigate a controlled use of it
   1068 //        db.execSQL("CREATE INDEX raw_contacts_agg_index ON " + Tables.RAW_CONTACTS + " (" +
   1069 //                RawContactsColumns.AGGREGATION_NEEDED +
   1070 //        ");");
   1071 
   1072         // Package name mapping table
   1073         db.execSQL("CREATE TABLE " + Tables.PACKAGES + " (" +
   1074                 PackagesColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   1075                 PackagesColumns.PACKAGE + " TEXT NOT NULL" +
   1076         ");");
   1077 
   1078         // Mimetype mapping table
   1079         db.execSQL("CREATE TABLE " + Tables.MIMETYPES + " (" +
   1080                 MimetypesColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   1081                 MimetypesColumns.MIMETYPE + " TEXT NOT NULL" +
   1082         ");");
   1083 
   1084         // Mimetype table requires an index on mime type
   1085         db.execSQL("CREATE UNIQUE INDEX mime_type ON " + Tables.MIMETYPES + " (" +
   1086                 MimetypesColumns.MIMETYPE +
   1087         ");");
   1088 
   1089         // Public generic data table
   1090         db.execSQL("CREATE TABLE " + Tables.DATA + " (" +
   1091                 Data._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   1092                 DataColumns.PACKAGE_ID + " INTEGER REFERENCES package(_id)," +
   1093                 DataColumns.MIMETYPE_ID + " INTEGER REFERENCES mimetype(_id) NOT NULL," +
   1094                 Data.RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," +
   1095                 Data.IS_READ_ONLY + " INTEGER NOT NULL DEFAULT 0," +
   1096                 Data.IS_PRIMARY + " INTEGER NOT NULL DEFAULT 0," +
   1097                 Data.IS_SUPER_PRIMARY + " INTEGER NOT NULL DEFAULT 0," +
   1098                 Data.DATA_VERSION + " INTEGER NOT NULL DEFAULT 0," +
   1099                 Data.DATA1 + " TEXT," +
   1100                 Data.DATA2 + " TEXT," +
   1101                 Data.DATA3 + " TEXT," +
   1102                 Data.DATA4 + " TEXT," +
   1103                 Data.DATA5 + " TEXT," +
   1104                 Data.DATA6 + " TEXT," +
   1105                 Data.DATA7 + " TEXT," +
   1106                 Data.DATA8 + " TEXT," +
   1107                 Data.DATA9 + " TEXT," +
   1108                 Data.DATA10 + " TEXT," +
   1109                 Data.DATA11 + " TEXT," +
   1110                 Data.DATA12 + " TEXT," +
   1111                 Data.DATA13 + " TEXT," +
   1112                 Data.DATA14 + " TEXT," +
   1113                 Data.DATA15 + " TEXT," +
   1114                 Data.SYNC1 + " TEXT, " +
   1115                 Data.SYNC2 + " TEXT, " +
   1116                 Data.SYNC3 + " TEXT, " +
   1117                 Data.SYNC4 + " TEXT " +
   1118         ");");
   1119 
   1120         db.execSQL("CREATE INDEX data_raw_contact_id ON " + Tables.DATA + " (" +
   1121                 Data.RAW_CONTACT_ID +
   1122         ");");
   1123 
   1124         /**
   1125          * For email lookup and similar queries.
   1126          */
   1127         db.execSQL("CREATE INDEX data_mimetype_data1_index ON " + Tables.DATA + " (" +
   1128                 DataColumns.MIMETYPE_ID + "," +
   1129                 Data.DATA1 +
   1130         ");");
   1131 
   1132         // Private phone numbers table used for lookup
   1133         db.execSQL("CREATE TABLE " + Tables.PHONE_LOOKUP + " (" +
   1134                 PhoneLookupColumns.DATA_ID
   1135                         + " INTEGER REFERENCES data(_id) NOT NULL," +
   1136                 PhoneLookupColumns.RAW_CONTACT_ID
   1137                         + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," +
   1138                 PhoneLookupColumns.NORMALIZED_NUMBER + " TEXT NOT NULL," +
   1139                 PhoneLookupColumns.MIN_MATCH + " TEXT NOT NULL" +
   1140         ");");
   1141 
   1142         db.execSQL("CREATE INDEX phone_lookup_index ON " + Tables.PHONE_LOOKUP + " (" +
   1143                 PhoneLookupColumns.NORMALIZED_NUMBER + "," +
   1144                 PhoneLookupColumns.RAW_CONTACT_ID + "," +
   1145                 PhoneLookupColumns.DATA_ID +
   1146         ");");
   1147 
   1148         db.execSQL("CREATE INDEX phone_lookup_min_match_index ON " + Tables.PHONE_LOOKUP + " (" +
   1149                 PhoneLookupColumns.MIN_MATCH + "," +
   1150                 PhoneLookupColumns.RAW_CONTACT_ID + "," +
   1151                 PhoneLookupColumns.DATA_ID +
   1152         ");");
   1153 
   1154         db.execSQL("CREATE INDEX phone_lookup_data_id_min_match_index ON " + Tables.PHONE_LOOKUP +
   1155                 " (" + PhoneLookupColumns.DATA_ID + ", " + PhoneLookupColumns.MIN_MATCH + ");");
   1156 
   1157         // Private name/nickname table used for lookup
   1158         db.execSQL("CREATE TABLE " + Tables.NAME_LOOKUP + " (" +
   1159                 NameLookupColumns.DATA_ID
   1160                         + " INTEGER REFERENCES data(_id) NOT NULL," +
   1161                 NameLookupColumns.RAW_CONTACT_ID
   1162                         + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," +
   1163                 NameLookupColumns.NORMALIZED_NAME + " TEXT NOT NULL," +
   1164                 NameLookupColumns.NAME_TYPE + " INTEGER NOT NULL," +
   1165                 "PRIMARY KEY ("
   1166                         + NameLookupColumns.DATA_ID + ", "
   1167                         + NameLookupColumns.NORMALIZED_NAME + ", "
   1168                         + NameLookupColumns.NAME_TYPE + ")" +
   1169         ");");
   1170 
   1171         db.execSQL("CREATE INDEX name_lookup_raw_contact_id_index ON " + Tables.NAME_LOOKUP + " (" +
   1172                 NameLookupColumns.RAW_CONTACT_ID +
   1173         ");");
   1174 
   1175         db.execSQL("CREATE TABLE " + Tables.NICKNAME_LOOKUP + " (" +
   1176                 NicknameLookupColumns.NAME + " TEXT," +
   1177                 NicknameLookupColumns.CLUSTER + " TEXT" +
   1178         ");");
   1179 
   1180         db.execSQL("CREATE UNIQUE INDEX nickname_lookup_index ON " + Tables.NICKNAME_LOOKUP + " (" +
   1181                 NicknameLookupColumns.NAME + ", " +
   1182                 NicknameLookupColumns.CLUSTER +
   1183         ");");
   1184 
   1185         // Groups table
   1186         db.execSQL("CREATE TABLE " + Tables.GROUPS + " (" +
   1187                 Groups._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   1188                 GroupsColumns.PACKAGE_ID + " INTEGER REFERENCES package(_id)," +
   1189                 GroupsColumns.ACCOUNT_ID + " INTEGER REFERENCES " +
   1190                     Tables.ACCOUNTS + "(" + AccountsColumns._ID + ")," +
   1191                 Groups.SOURCE_ID + " TEXT," +
   1192                 Groups.VERSION + " INTEGER NOT NULL DEFAULT 1," +
   1193                 Groups.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
   1194                 Groups.TITLE + " TEXT," +
   1195                 Groups.TITLE_RES + " INTEGER," +
   1196                 Groups.NOTES + " TEXT," +
   1197                 Groups.SYSTEM_ID + " TEXT," +
   1198                 Groups.DELETED + " INTEGER NOT NULL DEFAULT 0," +
   1199                 Groups.GROUP_VISIBLE + " INTEGER NOT NULL DEFAULT 0," +
   1200                 Groups.SHOULD_SYNC + " INTEGER NOT NULL DEFAULT 1," +
   1201                 Groups.AUTO_ADD + " INTEGER NOT NULL DEFAULT 0," +
   1202                 Groups.FAVORITES + " INTEGER NOT NULL DEFAULT 0," +
   1203                 Groups.GROUP_IS_READ_ONLY + " INTEGER NOT NULL DEFAULT 0," +
   1204                 Groups.SYNC1 + " TEXT, " +
   1205                 Groups.SYNC2 + " TEXT, " +
   1206                 Groups.SYNC3 + " TEXT, " +
   1207                 Groups.SYNC4 + " TEXT " +
   1208         ");");
   1209 
   1210         db.execSQL("CREATE INDEX groups_source_id_account_id_index ON " + Tables.GROUPS + " (" +
   1211                 Groups.SOURCE_ID + ", " +
   1212                 GroupsColumns.ACCOUNT_ID +
   1213         ");");
   1214 
   1215         db.execSQL("CREATE TABLE IF NOT EXISTS " + Tables.AGGREGATION_EXCEPTIONS + " (" +
   1216                 AggregationExceptionColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   1217                 AggregationExceptions.TYPE + " INTEGER NOT NULL, " +
   1218                 AggregationExceptions.RAW_CONTACT_ID1
   1219                         + " INTEGER REFERENCES raw_contacts(_id), " +
   1220                 AggregationExceptions.RAW_CONTACT_ID2
   1221                         + " INTEGER REFERENCES raw_contacts(_id)" +
   1222         ");");
   1223 
   1224         db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS aggregation_exception_index1 ON " +
   1225                 Tables.AGGREGATION_EXCEPTIONS + " (" +
   1226                 AggregationExceptions.RAW_CONTACT_ID1 + ", " +
   1227                 AggregationExceptions.RAW_CONTACT_ID2 +
   1228         ");");
   1229 
   1230         db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS aggregation_exception_index2 ON " +
   1231                 Tables.AGGREGATION_EXCEPTIONS + " (" +
   1232                 AggregationExceptions.RAW_CONTACT_ID2 + ", " +
   1233                 AggregationExceptions.RAW_CONTACT_ID1 +
   1234         ");");
   1235 
   1236         db.execSQL("CREATE TABLE IF NOT EXISTS " + Tables.SETTINGS + " (" +
   1237                 Settings.ACCOUNT_NAME + " STRING NOT NULL," +
   1238                 Settings.ACCOUNT_TYPE + " STRING NOT NULL," +
   1239                 Settings.DATA_SET + " STRING," +
   1240                 Settings.UNGROUPED_VISIBLE + " INTEGER NOT NULL DEFAULT 0," +
   1241                 Settings.SHOULD_SYNC + " INTEGER NOT NULL DEFAULT 1" +
   1242         ");");
   1243 
   1244         db.execSQL("CREATE TABLE " + Tables.VISIBLE_CONTACTS + " (" +
   1245                 Contacts._ID + " INTEGER PRIMARY KEY" +
   1246         ");");
   1247 
   1248         db.execSQL("CREATE TABLE " + Tables.DEFAULT_DIRECTORY + " (" +
   1249                 Contacts._ID + " INTEGER PRIMARY KEY" +
   1250         ");");
   1251 
   1252         // The table for recent calls is here so we can do table joins
   1253         // on people, phones, and calls all in one place.
   1254         db.execSQL("CREATE TABLE " + Tables.CALLS + " (" +
   1255                 Calls._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   1256                 Calls.NUMBER + " TEXT," +
   1257                 Calls.DATE + " INTEGER," +
   1258                 Calls.DURATION + " INTEGER," +
   1259                 Calls.TYPE + " INTEGER," +
   1260                 Calls.NEW + " INTEGER," +
   1261                 Calls.CACHED_NAME + " TEXT," +
   1262                 Calls.CACHED_NUMBER_TYPE + " INTEGER," +
   1263                 Calls.CACHED_NUMBER_LABEL + " TEXT," +
   1264                 Calls.COUNTRY_ISO + " TEXT," +
   1265                 Calls.VOICEMAIL_URI + " TEXT," +
   1266                 Calls.IS_READ + " INTEGER," +
   1267                 Calls.GEOCODED_LOCATION + " TEXT," +
   1268                 Calls.CACHED_LOOKUP_URI + " TEXT," +
   1269                 Calls.CACHED_MATCHED_NUMBER + " TEXT," +
   1270                 Calls.CACHED_NORMALIZED_NUMBER + " TEXT," +
   1271                 Calls.CACHED_PHOTO_ID + " INTEGER NOT NULL DEFAULT 0," +
   1272                 Calls.CACHED_FORMATTED_NUMBER + " TEXT," +
   1273                 Voicemails._DATA + " TEXT," +
   1274                 Voicemails.HAS_CONTENT + " INTEGER," +
   1275                 Voicemails.MIME_TYPE + " TEXT," +
   1276                 Voicemails.SOURCE_DATA + " TEXT," +
   1277                 Voicemails.SOURCE_PACKAGE + " TEXT," +
   1278                 Voicemails.STATE + " INTEGER" +
   1279         ");");
   1280 
   1281         // Voicemail source status table.
   1282         db.execSQL("CREATE TABLE " + Tables.VOICEMAIL_STATUS + " (" +
   1283                 VoicemailContract.Status._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   1284                 VoicemailContract.Status.SOURCE_PACKAGE + " TEXT UNIQUE NOT NULL," +
   1285                 VoicemailContract.Status.SETTINGS_URI + " TEXT," +
   1286                 VoicemailContract.Status.VOICEMAIL_ACCESS_URI + " TEXT," +
   1287                 VoicemailContract.Status.CONFIGURATION_STATE + " INTEGER," +
   1288                 VoicemailContract.Status.DATA_CHANNEL_STATE + " INTEGER," +
   1289                 VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE + " INTEGER" +
   1290         ");");
   1291 
   1292         db.execSQL("CREATE TABLE " + Tables.STATUS_UPDATES + " (" +
   1293                 StatusUpdatesColumns.DATA_ID + " INTEGER PRIMARY KEY REFERENCES data(_id)," +
   1294                 StatusUpdates.STATUS + " TEXT," +
   1295                 StatusUpdates.STATUS_TIMESTAMP + " INTEGER," +
   1296                 StatusUpdates.STATUS_RES_PACKAGE + " TEXT, " +
   1297                 StatusUpdates.STATUS_LABEL + " INTEGER, " +
   1298                 StatusUpdates.STATUS_ICON + " INTEGER" +
   1299         ");");
   1300 
   1301         db.execSQL("CREATE TABLE " + Tables.PROPERTIES + " (" +
   1302                 PropertiesColumns.PROPERTY_KEY + " TEXT PRIMARY KEY, " +
   1303                 PropertiesColumns.PROPERTY_VALUE + " TEXT " +
   1304         ");");
   1305 
   1306         createDirectoriesTable(db);
   1307         createSearchIndexTable(db, false /* we build stats table later */);
   1308 
   1309         db.execSQL("CREATE TABLE " + Tables.DATA_USAGE_STAT + "(" +
   1310                 DataUsageStatColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
   1311                 DataUsageStatColumns.DATA_ID + " INTEGER NOT NULL, " +
   1312                 DataUsageStatColumns.USAGE_TYPE_INT + " INTEGER NOT NULL DEFAULT 0, " +
   1313                 DataUsageStatColumns.TIMES_USED + " INTEGER NOT NULL DEFAULT 0, " +
   1314                 DataUsageStatColumns.LAST_TIME_USED + " INTERGER NOT NULL DEFAULT 0, " +
   1315                 "FOREIGN KEY(" + DataUsageStatColumns.DATA_ID + ") REFERENCES "
   1316                         + Tables.DATA + "(" + Data._ID + ")" +
   1317         ");");
   1318         db.execSQL("CREATE UNIQUE INDEX data_usage_stat_index ON " +
   1319                 Tables.DATA_USAGE_STAT + " (" +
   1320                 DataUsageStatColumns.DATA_ID + ", " +
   1321                 DataUsageStatColumns.USAGE_TYPE_INT +
   1322         ");");
   1323 
   1324         // When adding new tables, be sure to also add size-estimates in updateSqliteStats
   1325         createContactsViews(db);
   1326         createGroupsView(db);
   1327         createContactsTriggers(db);
   1328         createContactsIndexes(db, false /* we build stats table later */);
   1329 
   1330         loadNicknameLookupTable(db);
   1331 
   1332         // Set sequence starts.
   1333         initializeAutoIncrementSequences(db);
   1334 
   1335         // Add the legacy API support views, etc
   1336         LegacyApiSupport.createDatabase(db);
   1337 
   1338         if (mDatabaseOptimizationEnabled) {
   1339             // This will create a sqlite_stat1 table that is used for query optimization
   1340             db.execSQL("ANALYZE;");
   1341 
   1342             updateSqliteStats(db);
   1343         }
   1344 
   1345         ContentResolver.requestSync(null /* all accounts */,
   1346                 ContactsContract.AUTHORITY, new Bundle());
   1347 
   1348         // Only send broadcasts for regular contacts db.
   1349         if (dbForProfile() == 0) {
   1350             mContext.sendBroadcast(new Intent(ContactsContract.Intents.CONTACTS_DATABASE_CREATED),
   1351                     android.Manifest.permission.READ_CONTACTS);
   1352         }
   1353     }
   1354 
   1355     protected void initializeAutoIncrementSequences(SQLiteDatabase db) {
   1356         // Default implementation does nothing.
   1357     }
   1358 
   1359     private void createDirectoriesTable(SQLiteDatabase db) {
   1360         db.execSQL("CREATE TABLE " + Tables.DIRECTORIES + "(" +
   1361                 Directory._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   1362                 Directory.PACKAGE_NAME + " TEXT NOT NULL," +
   1363                 Directory.DIRECTORY_AUTHORITY + " TEXT NOT NULL," +
   1364                 Directory.TYPE_RESOURCE_ID + " INTEGER," +
   1365                 DirectoryColumns.TYPE_RESOURCE_NAME + " TEXT," +
   1366                 Directory.ACCOUNT_TYPE + " TEXT," +
   1367                 Directory.ACCOUNT_NAME + " TEXT," +
   1368                 Directory.DISPLAY_NAME + " TEXT, " +
   1369                 Directory.EXPORT_SUPPORT + " INTEGER NOT NULL" +
   1370                         " DEFAULT " + Directory.EXPORT_SUPPORT_NONE + "," +
   1371                 Directory.SHORTCUT_SUPPORT + " INTEGER NOT NULL" +
   1372                         " DEFAULT " + Directory.SHORTCUT_SUPPORT_NONE + "," +
   1373                 Directory.PHOTO_SUPPORT + " INTEGER NOT NULL" +
   1374                         " DEFAULT " + Directory.PHOTO_SUPPORT_NONE +
   1375         ");");
   1376 
   1377         // Trigger a full scan of directories in the system
   1378         setProperty(db, DbProperties.DIRECTORY_SCAN_COMPLETE, "0");
   1379     }
   1380 
   1381     public void createSearchIndexTable(SQLiteDatabase db, boolean rebuildSqliteStats) {
   1382         db.execSQL("DROP TABLE IF EXISTS " + Tables.SEARCH_INDEX);
   1383         db.execSQL("CREATE VIRTUAL TABLE " + Tables.SEARCH_INDEX
   1384                 + " USING FTS4 ("
   1385                     + SearchIndexColumns.CONTACT_ID + " INTEGER REFERENCES contacts(_id) NOT NULL,"
   1386                     + SearchIndexColumns.CONTENT + " TEXT, "
   1387                     + SearchIndexColumns.NAME + " TEXT, "
   1388                     + SearchIndexColumns.TOKENS + " TEXT"
   1389                 + ")");
   1390         if (rebuildSqliteStats) {
   1391             updateSqliteStats(db);
   1392         }
   1393     }
   1394 
   1395     private void createContactsTriggers(SQLiteDatabase db) {
   1396 
   1397         /*
   1398          * Automatically delete Data rows when a raw contact is deleted.
   1399          */
   1400         db.execSQL("DROP TRIGGER IF EXISTS " + Tables.RAW_CONTACTS + "_deleted;");
   1401         db.execSQL("CREATE TRIGGER " + Tables.RAW_CONTACTS + "_deleted "
   1402                 + "   BEFORE DELETE ON " + Tables.RAW_CONTACTS
   1403                 + " BEGIN "
   1404                 + "   DELETE FROM " + Tables.DATA
   1405                 + "     WHERE " + Data.RAW_CONTACT_ID
   1406                                 + "=OLD." + RawContacts._ID + ";"
   1407                 + "   DELETE FROM " + Tables.AGGREGATION_EXCEPTIONS
   1408                 + "     WHERE " + AggregationExceptions.RAW_CONTACT_ID1
   1409                                 + "=OLD." + RawContacts._ID
   1410                 + "        OR " + AggregationExceptions.RAW_CONTACT_ID2
   1411                                 + "=OLD." + RawContacts._ID + ";"
   1412                 + "   DELETE FROM " + Tables.VISIBLE_CONTACTS
   1413                 + "     WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID
   1414                 + "       AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS
   1415                 + "            WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID
   1416                 + "           )=1;"
   1417                 + "   DELETE FROM " + Tables.DEFAULT_DIRECTORY
   1418                 + "     WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID
   1419                 + "       AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS
   1420                 + "            WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID
   1421                 + "           )=1;"
   1422                 + "   DELETE FROM " + Tables.CONTACTS
   1423                 + "     WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID
   1424                 + "       AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS
   1425                 + "            WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID
   1426                 + "           )=1;"
   1427                 + " END");
   1428 
   1429 
   1430         db.execSQL("DROP TRIGGER IF EXISTS contacts_times_contacted;");
   1431         db.execSQL("DROP TRIGGER IF EXISTS raw_contacts_times_contacted;");
   1432 
   1433         /*
   1434          * Triggers that update {@link RawContacts#VERSION} when the contact is
   1435          * marked for deletion or any time a data row is inserted, updated or
   1436          * deleted.
   1437          */
   1438         db.execSQL("DROP TRIGGER IF EXISTS " + Tables.RAW_CONTACTS + "_marked_deleted;");
   1439         db.execSQL("CREATE TRIGGER " + Tables.RAW_CONTACTS + "_marked_deleted "
   1440                 + "   AFTER UPDATE ON " + Tables.RAW_CONTACTS
   1441                 + " BEGIN "
   1442                 + "   UPDATE " + Tables.RAW_CONTACTS
   1443                 + "     SET "
   1444                 +         RawContacts.VERSION + "=OLD." + RawContacts.VERSION + "+1 "
   1445                 + "     WHERE " + RawContacts._ID + "=OLD." + RawContacts._ID
   1446                 + "       AND NEW." + RawContacts.DELETED + "!= OLD." + RawContacts.DELETED + ";"
   1447                 + " END");
   1448 
   1449         db.execSQL("DROP TRIGGER IF EXISTS " + Tables.DATA + "_updated;");
   1450         db.execSQL("CREATE TRIGGER " + Tables.DATA + "_updated AFTER UPDATE ON " + Tables.DATA
   1451                 + " BEGIN "
   1452                 + "   UPDATE " + Tables.DATA
   1453                 + "     SET " + Data.DATA_VERSION + "=OLD." + Data.DATA_VERSION + "+1 "
   1454                 + "     WHERE " + Data._ID + "=OLD." + Data._ID + ";"
   1455                 + "   UPDATE " + Tables.RAW_CONTACTS
   1456                 + "     SET " + RawContacts.VERSION + "=" + RawContacts.VERSION + "+1 "
   1457                 + "     WHERE " + RawContacts._ID + "=OLD." + Data.RAW_CONTACT_ID + ";"
   1458                 + " END");
   1459 
   1460         db.execSQL("DROP TRIGGER IF EXISTS " + Tables.DATA + "_deleted;");
   1461         db.execSQL("CREATE TRIGGER " + Tables.DATA + "_deleted BEFORE DELETE ON " + Tables.DATA
   1462                 + " BEGIN "
   1463                 + "   UPDATE " + Tables.RAW_CONTACTS
   1464                 + "     SET " + RawContacts.VERSION + "=" + RawContacts.VERSION + "+1 "
   1465                 + "     WHERE " + RawContacts._ID + "=OLD." + Data.RAW_CONTACT_ID + ";"
   1466                 + "   DELETE FROM " + Tables.PHONE_LOOKUP
   1467                 + "     WHERE " + PhoneLookupColumns.DATA_ID + "=OLD." + Data._ID + ";"
   1468                 + "   DELETE FROM " + Tables.STATUS_UPDATES
   1469                 + "     WHERE " + StatusUpdatesColumns.DATA_ID + "=OLD." + Data._ID + ";"
   1470                 + "   DELETE FROM " + Tables.NAME_LOOKUP
   1471                 + "     WHERE " + NameLookupColumns.DATA_ID + "=OLD." + Data._ID + ";"
   1472                 + " END");
   1473 
   1474 
   1475         db.execSQL("DROP TRIGGER IF EXISTS " + Tables.GROUPS + "_updated1;");
   1476         db.execSQL("CREATE TRIGGER " + Tables.GROUPS + "_updated1 "
   1477                 + "   AFTER UPDATE ON " + Tables.GROUPS
   1478                 + " BEGIN "
   1479                 + "   UPDATE " + Tables.GROUPS
   1480                 + "     SET "
   1481                 +         Groups.VERSION + "=OLD." + Groups.VERSION + "+1"
   1482                 + "     WHERE " + Groups._ID + "=OLD." + Groups._ID + ";"
   1483                 + " END");
   1484 
   1485         // Update DEFAULT_FILTER table per AUTO_ADD column update.
   1486         // See also upgradeToVersion411().
   1487         final String insertContactsWithoutAccount = (
   1488                 " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
   1489                 "     SELECT " + RawContacts.CONTACT_ID +
   1490                 "     FROM " + Tables.RAW_CONTACTS +
   1491                 "     WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID +
   1492                             "=" + Clauses.LOCAL_ACCOUNT_ID + ";");
   1493         final String insertContactsWithAccountNoDefaultGroup = (
   1494                 " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
   1495                 "     SELECT " + RawContacts.CONTACT_ID +
   1496                 "         FROM " + Tables.RAW_CONTACTS +
   1497                 "     WHERE NOT EXISTS" +
   1498                 "         (SELECT " + Groups._ID +
   1499                 "             FROM " + Tables.GROUPS +
   1500                 "             WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID + " = " +
   1501                                     GroupsColumns.CONCRETE_ACCOUNT_ID +
   1502                 "             AND " + Groups.AUTO_ADD + " != 0" + ");");
   1503         final String insertContactsWithAccountDefaultGroup = (
   1504                 " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
   1505                 "     SELECT " + RawContacts.CONTACT_ID +
   1506                 "         FROM " + Tables.RAW_CONTACTS +
   1507                 "     JOIN " + Tables.DATA +
   1508                 "           ON (" + RawContactsColumns.CONCRETE_ID + "=" +
   1509                         Data.RAW_CONTACT_ID + ")" +
   1510                 "     WHERE " + DataColumns.MIMETYPE_ID + "=" +
   1511                     "(SELECT " + MimetypesColumns._ID + " FROM " + Tables.MIMETYPES +
   1512                         " WHERE " + MimetypesColumns.MIMETYPE +
   1513                             "='" + GroupMembership.CONTENT_ITEM_TYPE + "')" +
   1514                 "     AND EXISTS" +
   1515                 "         (SELECT " + Groups._ID +
   1516                 "             FROM " + Tables.GROUPS +
   1517                 "                 WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID + " = " +
   1518                                         GroupsColumns.CONCRETE_ACCOUNT_ID +
   1519                 "                 AND " + Groups.AUTO_ADD + " != 0" + ");");
   1520 
   1521         db.execSQL("DROP TRIGGER IF EXISTS " + Tables.GROUPS + "_auto_add_updated1;");
   1522         db.execSQL("CREATE TRIGGER " + Tables.GROUPS + "_auto_add_updated1 "
   1523                 + "   AFTER UPDATE OF " + Groups.AUTO_ADD + " ON " + Tables.GROUPS
   1524                 + " BEGIN "
   1525                 + "   DELETE FROM " + Tables.DEFAULT_DIRECTORY + ";"
   1526                     + insertContactsWithoutAccount
   1527                     + insertContactsWithAccountNoDefaultGroup
   1528                     + insertContactsWithAccountDefaultGroup
   1529                 + " END");
   1530     }
   1531 
   1532     private void createContactsIndexes(SQLiteDatabase db, boolean rebuildSqliteStats) {
   1533         db.execSQL("DROP INDEX IF EXISTS name_lookup_index");
   1534         db.execSQL("CREATE INDEX name_lookup_index ON " + Tables.NAME_LOOKUP + " (" +
   1535                 NameLookupColumns.NORMALIZED_NAME + "," +
   1536                 NameLookupColumns.NAME_TYPE + ", " +
   1537                 NameLookupColumns.RAW_CONTACT_ID + ", " +
   1538                 NameLookupColumns.DATA_ID +
   1539         ");");
   1540 
   1541         db.execSQL("DROP INDEX IF EXISTS raw_contact_sort_key1_index");
   1542         db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" +
   1543                 RawContacts.SORT_KEY_PRIMARY +
   1544         ");");
   1545 
   1546         db.execSQL("DROP INDEX IF EXISTS raw_contact_sort_key2_index");
   1547         db.execSQL("CREATE INDEX raw_contact_sort_key2_index ON " + Tables.RAW_CONTACTS + " (" +
   1548                 RawContacts.SORT_KEY_ALTERNATIVE +
   1549         ");");
   1550 
   1551         if (rebuildSqliteStats) {
   1552             updateSqliteStats(db);
   1553         }
   1554     }
   1555 
   1556     private void createContactsViews(SQLiteDatabase db) {
   1557         db.execSQL("DROP VIEW IF EXISTS " + Views.CONTACTS + ";");
   1558         db.execSQL("DROP VIEW IF EXISTS " + Views.DATA + ";");
   1559         db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_CONTACTS + ";");
   1560         db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_ENTITIES + ";");
   1561         db.execSQL("DROP VIEW IF EXISTS " + Views.ENTITIES + ";");
   1562         db.execSQL("DROP VIEW IF EXISTS " + Views.DATA_USAGE_STAT + ";");
   1563         db.execSQL("DROP VIEW IF EXISTS " + Views.STREAM_ITEMS + ";");
   1564 
   1565         String dataColumns =
   1566                 Data.IS_PRIMARY + ", "
   1567                 + Data.IS_SUPER_PRIMARY + ", "
   1568                 + Data.DATA_VERSION + ", "
   1569                 + DataColumns.CONCRETE_PACKAGE_ID + ","
   1570                 + PackagesColumns.PACKAGE + " AS " + Data.RES_PACKAGE + ","
   1571                 + DataColumns.CONCRETE_MIMETYPE_ID + ","
   1572                 + MimetypesColumns.MIMETYPE + " AS " + Data.MIMETYPE + ", "
   1573                 + Data.IS_READ_ONLY + ", "
   1574                 + Data.DATA1 + ", "
   1575                 + Data.DATA2 + ", "
   1576                 + Data.DATA3 + ", "
   1577                 + Data.DATA4 + ", "
   1578                 + Data.DATA5 + ", "
   1579                 + Data.DATA6 + ", "
   1580                 + Data.DATA7 + ", "
   1581                 + Data.DATA8 + ", "
   1582                 + Data.DATA9 + ", "
   1583                 + Data.DATA10 + ", "
   1584                 + Data.DATA11 + ", "
   1585                 + Data.DATA12 + ", "
   1586                 + Data.DATA13 + ", "
   1587                 + Data.DATA14 + ", "
   1588                 + Data.DATA15 + ", "
   1589                 + Data.SYNC1 + ", "
   1590                 + Data.SYNC2 + ", "
   1591                 + Data.SYNC3 + ", "
   1592                 + Data.SYNC4;
   1593 
   1594         String syncColumns =
   1595                 RawContactsColumns.CONCRETE_ACCOUNT_ID + ","
   1596                 + AccountsColumns.CONCRETE_ACCOUNT_NAME + " AS " + RawContacts.ACCOUNT_NAME + ","
   1597                 + AccountsColumns.CONCRETE_ACCOUNT_TYPE + " AS " + RawContacts.ACCOUNT_TYPE + ","
   1598                 + AccountsColumns.CONCRETE_DATA_SET + " AS " + RawContacts.DATA_SET + ","
   1599                 + "(CASE WHEN " + AccountsColumns.CONCRETE_DATA_SET + " IS NULL THEN "
   1600                             + AccountsColumns.CONCRETE_ACCOUNT_TYPE
   1601                         + " ELSE " + AccountsColumns.CONCRETE_ACCOUNT_TYPE + "||'/'||"
   1602                             + AccountsColumns.CONCRETE_DATA_SET + " END) AS "
   1603                                 + RawContacts.ACCOUNT_TYPE_AND_DATA_SET + ","
   1604                 + RawContactsColumns.CONCRETE_SOURCE_ID + " AS " + RawContacts.SOURCE_ID + ","
   1605                 + RawContactsColumns.CONCRETE_NAME_VERIFIED + " AS "
   1606                         + RawContacts.NAME_VERIFIED + ","
   1607                 + RawContactsColumns.CONCRETE_VERSION + " AS " + RawContacts.VERSION + ","
   1608                 + RawContactsColumns.CONCRETE_DIRTY + " AS " + RawContacts.DIRTY + ","
   1609                 + RawContactsColumns.CONCRETE_SYNC1 + " AS " + RawContacts.SYNC1 + ","
   1610                 + RawContactsColumns.CONCRETE_SYNC2 + " AS " + RawContacts.SYNC2 + ","
   1611                 + RawContactsColumns.CONCRETE_SYNC3 + " AS " + RawContacts.SYNC3 + ","
   1612                 + RawContactsColumns.CONCRETE_SYNC4 + " AS " + RawContacts.SYNC4;
   1613 
   1614         String baseContactColumns =
   1615                 Contacts.HAS_PHONE_NUMBER + ", "
   1616                 + Contacts.NAME_RAW_CONTACT_ID + ", "
   1617                 + Contacts.LOOKUP_KEY + ", "
   1618                 + Contacts.PHOTO_ID + ", "
   1619                 + Contacts.PHOTO_FILE_ID + ", "
   1620                 + "CAST(" + Clauses.CONTACT_VISIBLE + " AS INTEGER) AS "
   1621                         + Contacts.IN_VISIBLE_GROUP + ", "
   1622                 + ContactsColumns.LAST_STATUS_UPDATE_ID + ", "
   1623                 + ContactsColumns.CONCRETE_CONTACT_LAST_UPDATED_TIMESTAMP;
   1624 
   1625         String contactOptionColumns =
   1626                 ContactsColumns.CONCRETE_CUSTOM_RINGTONE
   1627                         + " AS " + RawContacts.CUSTOM_RINGTONE + ","
   1628                 + ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL
   1629                         + " AS " + RawContacts.SEND_TO_VOICEMAIL + ","
   1630                 + ContactsColumns.CONCRETE_LAST_TIME_CONTACTED
   1631                         + " AS " + RawContacts.LAST_TIME_CONTACTED + ","
   1632                 + ContactsColumns.CONCRETE_TIMES_CONTACTED
   1633                         + " AS " + RawContacts.TIMES_CONTACTED + ","
   1634                 + ContactsColumns.CONCRETE_STARRED
   1635                         + " AS " + RawContacts.STARRED;
   1636 
   1637         String contactNameColumns =
   1638                 "name_raw_contact." + RawContacts.DISPLAY_NAME_SOURCE
   1639                         + " AS " + Contacts.DISPLAY_NAME_SOURCE + ", "
   1640                 + "name_raw_contact." + RawContacts.DISPLAY_NAME_PRIMARY
   1641                         + " AS " + Contacts.DISPLAY_NAME_PRIMARY + ", "
   1642                 + "name_raw_contact." + RawContacts.DISPLAY_NAME_ALTERNATIVE
   1643                         + " AS " + Contacts.DISPLAY_NAME_ALTERNATIVE + ", "
   1644                 + "name_raw_contact." + RawContacts.PHONETIC_NAME
   1645                         + " AS " + Contacts.PHONETIC_NAME + ", "
   1646                 + "name_raw_contact." + RawContacts.PHONETIC_NAME_STYLE
   1647                         + " AS " + Contacts.PHONETIC_NAME_STYLE + ", "
   1648                 + "name_raw_contact." + RawContacts.SORT_KEY_PRIMARY
   1649                         + " AS " + Contacts.SORT_KEY_PRIMARY + ", "
   1650                 + "name_raw_contact." + RawContactsColumns.PHONEBOOK_LABEL_PRIMARY
   1651                         + " AS " + ContactsColumns.PHONEBOOK_LABEL_PRIMARY + ", "
   1652                 + "name_raw_contact." + RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY
   1653                         + " AS " + ContactsColumns.PHONEBOOK_BUCKET_PRIMARY + ", "
   1654                 + "name_raw_contact." + RawContacts.SORT_KEY_ALTERNATIVE
   1655                         + " AS " + Contacts.SORT_KEY_ALTERNATIVE + ", "
   1656                 + "name_raw_contact." + RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE
   1657                         + " AS " + ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + ", "
   1658                 + "name_raw_contact." + RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE
   1659                         + " AS " + ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE;
   1660 
   1661         String dataSelect = "SELECT "
   1662                 + DataColumns.CONCRETE_ID + " AS " + Data._ID + ","
   1663                 + Data.RAW_CONTACT_ID + ", "
   1664                 + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", "
   1665                 + syncColumns + ", "
   1666                 + dataColumns + ", "
   1667                 + contactOptionColumns + ", "
   1668                 + contactNameColumns + ", "
   1669                 + baseContactColumns + ", "
   1670                 + buildDisplayPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
   1671                         Contacts.PHOTO_URI) + ", "
   1672                 + buildThumbnailPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
   1673                         Contacts.PHOTO_THUMBNAIL_URI) + ", "
   1674                 + dbForProfile() + " AS " + RawContacts.RAW_CONTACT_IS_USER_PROFILE + ", "
   1675                 + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID
   1676                 + " FROM " + Tables.DATA
   1677                 + " JOIN " + Tables.MIMETYPES + " ON ("
   1678                 +   DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")"
   1679                 + " JOIN " + Tables.RAW_CONTACTS + " ON ("
   1680                 +   DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")"
   1681                 + " JOIN " + Tables.ACCOUNTS + " ON ("
   1682                 +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
   1683                     + ")"
   1684                 + " JOIN " + Tables.CONTACTS + " ON ("
   1685                 +   RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")"
   1686                 + " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON("
   1687                 +   Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")"
   1688                 + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON ("
   1689                 +   DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")"
   1690                 + " LEFT OUTER JOIN " + Tables.GROUPS + " ON ("
   1691                 +   MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
   1692                 +   "' AND " + GroupsColumns.CONCRETE_ID + "="
   1693                         + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")";
   1694 
   1695         db.execSQL("CREATE VIEW " + Views.DATA + " AS " + dataSelect);
   1696 
   1697         String rawContactOptionColumns =
   1698                 RawContacts.CUSTOM_RINGTONE + ","
   1699                 + RawContacts.SEND_TO_VOICEMAIL + ","
   1700                 + RawContacts.LAST_TIME_CONTACTED + ","
   1701                 + RawContacts.TIMES_CONTACTED + ","
   1702                 + RawContacts.STARRED;
   1703 
   1704         String rawContactsSelect = "SELECT "
   1705                 + RawContactsColumns.CONCRETE_ID + " AS " + RawContacts._ID + ","
   1706                 + RawContacts.CONTACT_ID + ", "
   1707                 + RawContacts.AGGREGATION_MODE + ", "
   1708                 + RawContacts.RAW_CONTACT_IS_READ_ONLY + ", "
   1709                 + RawContacts.DELETED + ", "
   1710                 + RawContacts.DISPLAY_NAME_SOURCE  + ", "
   1711                 + RawContacts.DISPLAY_NAME_PRIMARY  + ", "
   1712                 + RawContacts.DISPLAY_NAME_ALTERNATIVE  + ", "
   1713                 + RawContacts.PHONETIC_NAME  + ", "
   1714                 + RawContacts.PHONETIC_NAME_STYLE  + ", "
   1715                 + RawContacts.SORT_KEY_PRIMARY  + ", "
   1716                 + RawContactsColumns.PHONEBOOK_LABEL_PRIMARY  + ", "
   1717                 + RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY  + ", "
   1718                 + RawContacts.SORT_KEY_ALTERNATIVE + ", "
   1719                 + RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE  + ", "
   1720                 + RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE  + ", "
   1721                 + dbForProfile() + " AS " + RawContacts.RAW_CONTACT_IS_USER_PROFILE + ", "
   1722                 + rawContactOptionColumns + ", "
   1723                 + syncColumns
   1724                 + " FROM " + Tables.RAW_CONTACTS
   1725                 + " JOIN " + Tables.ACCOUNTS + " ON ("
   1726                 +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
   1727                     + ")";
   1728 
   1729         db.execSQL("CREATE VIEW " + Views.RAW_CONTACTS + " AS " + rawContactsSelect);
   1730 
   1731         String contactsColumns =
   1732                 ContactsColumns.CONCRETE_CUSTOM_RINGTONE
   1733                         + " AS " + Contacts.CUSTOM_RINGTONE + ", "
   1734                 + contactNameColumns + ", "
   1735                 + baseContactColumns + ", "
   1736                 + ContactsColumns.CONCRETE_LAST_TIME_CONTACTED
   1737                         + " AS " + Contacts.LAST_TIME_CONTACTED + ", "
   1738                 + ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL
   1739                         + " AS " + Contacts.SEND_TO_VOICEMAIL + ", "
   1740                 + ContactsColumns.CONCRETE_STARRED
   1741                         + " AS " + Contacts.STARRED + ", "
   1742                 + ContactsColumns.CONCRETE_TIMES_CONTACTED
   1743                         + " AS " + Contacts.TIMES_CONTACTED;
   1744 
   1745         String contactsSelect = "SELECT "
   1746                 + ContactsColumns.CONCRETE_ID + " AS " + Contacts._ID + ","
   1747                 + contactsColumns + ", "
   1748                 + buildDisplayPhotoUriAlias(ContactsColumns.CONCRETE_ID, Contacts.PHOTO_URI) + ", "
   1749                 + buildThumbnailPhotoUriAlias(ContactsColumns.CONCRETE_ID,
   1750                         Contacts.PHOTO_THUMBNAIL_URI) + ", "
   1751                 + dbForProfile() + " AS " + Contacts.IS_USER_PROFILE
   1752                 + " FROM " + Tables.CONTACTS
   1753                 + " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON("
   1754                 +   Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")";
   1755 
   1756         db.execSQL("CREATE VIEW " + Views.CONTACTS + " AS " + contactsSelect);
   1757 
   1758         String rawEntitiesSelect = "SELECT "
   1759                 + RawContacts.CONTACT_ID + ", "
   1760                 + RawContactsColumns.CONCRETE_DELETED + " AS " + RawContacts.DELETED + ","
   1761                 + dataColumns + ", "
   1762                 + syncColumns + ", "
   1763                 + Data.SYNC1 + ", "
   1764                 + Data.SYNC2 + ", "
   1765                 + Data.SYNC3 + ", "
   1766                 + Data.SYNC4 + ", "
   1767                 + RawContactsColumns.CONCRETE_ID + " AS " + RawContacts._ID + ", "
   1768                 + DataColumns.CONCRETE_ID + " AS " + RawContacts.Entity.DATA_ID + ","
   1769                 + RawContactsColumns.CONCRETE_STARRED + " AS " + RawContacts.STARRED + ","
   1770                 + dbForProfile() + " AS " + RawContacts.RAW_CONTACT_IS_USER_PROFILE + ","
   1771                 + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID
   1772                 + " FROM " + Tables.RAW_CONTACTS
   1773                 + " JOIN " + Tables.ACCOUNTS + " ON ("
   1774                 +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
   1775                     + ")"
   1776                 + " LEFT OUTER JOIN " + Tables.DATA + " ON ("
   1777                 +   DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")"
   1778                 + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON ("
   1779                 +   DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")"
   1780                 + " LEFT OUTER JOIN " + Tables.MIMETYPES + " ON ("
   1781                 +   DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")"
   1782                 + " LEFT OUTER JOIN " + Tables.GROUPS + " ON ("
   1783                 +   MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
   1784                 +   "' AND " + GroupsColumns.CONCRETE_ID + "="
   1785                 + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")";
   1786 
   1787         db.execSQL("CREATE VIEW " + Views.RAW_ENTITIES + " AS "
   1788                 + rawEntitiesSelect);
   1789 
   1790         String entitiesSelect = "SELECT "
   1791                 + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + Contacts._ID + ", "
   1792                 + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", "
   1793                 + RawContactsColumns.CONCRETE_DELETED + " AS " + RawContacts.DELETED + ","
   1794                 + dataColumns + ", "
   1795                 + syncColumns + ", "
   1796                 + contactsColumns + ", "
   1797                 + buildDisplayPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
   1798                         Contacts.PHOTO_URI) + ", "
   1799                 + buildThumbnailPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID,
   1800                         Contacts.PHOTO_THUMBNAIL_URI) + ", "
   1801                 + dbForProfile() + " AS " + Contacts.IS_USER_PROFILE + ", "
   1802                 + Data.SYNC1 + ", "
   1803                 + Data.SYNC2 + ", "
   1804                 + Data.SYNC3 + ", "
   1805                 + Data.SYNC4 + ", "
   1806                 + RawContactsColumns.CONCRETE_ID + " AS " + Contacts.Entity.RAW_CONTACT_ID + ", "
   1807                 + DataColumns.CONCRETE_ID + " AS " + Contacts.Entity.DATA_ID + ","
   1808                 + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID
   1809                 + " FROM " + Tables.RAW_CONTACTS
   1810                 + " JOIN " + Tables.ACCOUNTS + " ON ("
   1811                 +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
   1812                     + ")"
   1813                 + " JOIN " + Tables.CONTACTS + " ON ("
   1814                 +   RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")"
   1815                 + " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON("
   1816                 +   Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")"
   1817                 + " LEFT OUTER JOIN " + Tables.DATA + " ON ("
   1818                 +   DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")"
   1819                 + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON ("
   1820                 +   DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")"
   1821                 + " LEFT OUTER JOIN " + Tables.MIMETYPES + " ON ("
   1822                 +   DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")"
   1823                 + " LEFT OUTER JOIN " + Tables.GROUPS + " ON ("
   1824                 +   MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
   1825                 +   "' AND " + GroupsColumns.CONCRETE_ID + "="
   1826                 + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")";
   1827 
   1828         db.execSQL("CREATE VIEW " + Views.ENTITIES + " AS "
   1829                 + entitiesSelect);
   1830 
   1831         String dataUsageStatSelect = "SELECT "
   1832                 + DataUsageStatColumns.CONCRETE_ID + " AS " + DataUsageStatColumns._ID + ", "
   1833                 + DataUsageStatColumns.DATA_ID + ", "
   1834                 + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", "
   1835                 + MimetypesColumns.CONCRETE_MIMETYPE + " AS " + Data.MIMETYPE + ", "
   1836                 + DataUsageStatColumns.USAGE_TYPE_INT + ", "
   1837                 + DataUsageStatColumns.TIMES_USED + ", "
   1838                 + DataUsageStatColumns.LAST_TIME_USED
   1839                 + " FROM " + Tables.DATA_USAGE_STAT
   1840                 + " JOIN " + Tables.DATA + " ON ("
   1841                 +   DataColumns.CONCRETE_ID + "=" + DataUsageStatColumns.CONCRETE_DATA_ID + ")"
   1842                 + " JOIN " + Tables.RAW_CONTACTS + " ON ("
   1843                 +   RawContactsColumns.CONCRETE_ID + "=" + DataColumns.CONCRETE_RAW_CONTACT_ID
   1844                     + " )"
   1845                 + " JOIN " + Tables.MIMETYPES + " ON ("
   1846                 +   MimetypesColumns.CONCRETE_ID + "=" + DataColumns.CONCRETE_MIMETYPE_ID + ")";
   1847 
   1848         db.execSQL("CREATE VIEW " + Views.DATA_USAGE_STAT + " AS " + dataUsageStatSelect);
   1849 
   1850         String streamItemSelect = "SELECT " +
   1851                 StreamItemsColumns.CONCRETE_ID + ", " +
   1852                 ContactsColumns.CONCRETE_ID + " AS " + StreamItems.CONTACT_ID + ", " +
   1853                 ContactsColumns.CONCRETE_LOOKUP_KEY +
   1854                         " AS " + StreamItems.CONTACT_LOOKUP_KEY + ", " +
   1855                 AccountsColumns.CONCRETE_ACCOUNT_NAME + ", " +
   1856                 AccountsColumns.CONCRETE_ACCOUNT_TYPE + ", " +
   1857                 AccountsColumns.CONCRETE_DATA_SET + ", " +
   1858                 StreamItemsColumns.CONCRETE_RAW_CONTACT_ID +
   1859                         " as " + StreamItems.RAW_CONTACT_ID + ", " +
   1860                 RawContactsColumns.CONCRETE_SOURCE_ID +
   1861                         " as " + StreamItems.RAW_CONTACT_SOURCE_ID + ", " +
   1862                 StreamItemsColumns.CONCRETE_PACKAGE + ", " +
   1863                 StreamItemsColumns.CONCRETE_ICON + ", " +
   1864                 StreamItemsColumns.CONCRETE_LABEL + ", " +
   1865                 StreamItemsColumns.CONCRETE_TEXT + ", " +
   1866                 StreamItemsColumns.CONCRETE_TIMESTAMP + ", " +
   1867                 StreamItemsColumns.CONCRETE_COMMENTS + ", " +
   1868                 StreamItemsColumns.CONCRETE_SYNC1 + ", " +
   1869                 StreamItemsColumns.CONCRETE_SYNC2 + ", " +
   1870                 StreamItemsColumns.CONCRETE_SYNC3 + ", " +
   1871                 StreamItemsColumns.CONCRETE_SYNC4 +
   1872                 " FROM " + Tables.STREAM_ITEMS
   1873                 + " JOIN " + Tables.RAW_CONTACTS + " ON ("
   1874                 + StreamItemsColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID
   1875                     + ")"
   1876                 + " JOIN " + Tables.ACCOUNTS + " ON ("
   1877                 +   RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
   1878                     + ")"
   1879                 + " JOIN " + Tables.CONTACTS + " ON ("
   1880                 + RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")";
   1881 
   1882         db.execSQL("CREATE VIEW " + Views.STREAM_ITEMS + " AS " + streamItemSelect);
   1883     }
   1884 
   1885     private static String buildDisplayPhotoUriAlias(String contactIdColumn, String alias) {
   1886         return "(CASE WHEN " + Contacts.PHOTO_FILE_ID + " IS NULL THEN (CASE WHEN "
   1887                 + Contacts.PHOTO_ID + " IS NULL"
   1888                 + " OR " + Contacts.PHOTO_ID + "=0"
   1889                 + " THEN NULL"
   1890                 + " ELSE '" + Contacts.CONTENT_URI + "/'||"
   1891                         + contactIdColumn + "|| '/" + Photo.CONTENT_DIRECTORY + "'"
   1892                 + " END) ELSE '" + DisplayPhoto.CONTENT_URI + "/'||"
   1893                         + Contacts.PHOTO_FILE_ID + " END)"
   1894                 + " AS " + alias;
   1895     }
   1896 
   1897     private static String buildThumbnailPhotoUriAlias(String contactIdColumn, String alias) {
   1898         return "(CASE WHEN "
   1899                 + Contacts.PHOTO_ID + " IS NULL"
   1900                 + " OR " + Contacts.PHOTO_ID + "=0"
   1901                 + " THEN NULL"
   1902                 + " ELSE '" + Contacts.CONTENT_URI + "/'||"
   1903                         + contactIdColumn + "|| '/" + Photo.CONTENT_DIRECTORY + "'"
   1904                 + " END)"
   1905                 + " AS " + alias;
   1906     }
   1907 
   1908     /**
   1909      * Returns the value to be returned when querying the column indicating that the contact
   1910      * or raw contact belongs to the user's personal profile.  Overridden in the profile
   1911      * DB helper subclass.
   1912      */
   1913     protected int dbForProfile() {
   1914         return 0;
   1915     }
   1916 
   1917     private void createGroupsView(SQLiteDatabase db) {
   1918         db.execSQL("DROP VIEW IF EXISTS " + Views.GROUPS + ";");
   1919 
   1920         String groupsColumns =
   1921                 GroupsColumns.CONCRETE_ACCOUNT_ID + " AS " + GroupsColumns.ACCOUNT_ID + ","
   1922                 + AccountsColumns.CONCRETE_ACCOUNT_NAME + " AS " + Groups.ACCOUNT_NAME + ","
   1923                 + AccountsColumns.CONCRETE_ACCOUNT_TYPE + " AS " + Groups.ACCOUNT_TYPE + ","
   1924                 + AccountsColumns.CONCRETE_DATA_SET + " AS " + Groups.DATA_SET + ","
   1925                 + "(CASE WHEN " + AccountsColumns.CONCRETE_DATA_SET
   1926                     + " IS NULL THEN " + AccountsColumns.CONCRETE_ACCOUNT_TYPE
   1927                     + " ELSE " + AccountsColumns.CONCRETE_ACCOUNT_TYPE
   1928                         + "||'/'||" + AccountsColumns.CONCRETE_DATA_SET + " END) AS "
   1929                             + Groups.ACCOUNT_TYPE_AND_DATA_SET + ","
   1930                 + Groups.SOURCE_ID + ","
   1931                 + Groups.VERSION + ","
   1932                 + Groups.DIRTY + ","
   1933                 + Groups.TITLE + ","
   1934                 + Groups.TITLE_RES + ","
   1935                 + Groups.NOTES + ","
   1936                 + Groups.SYSTEM_ID + ","
   1937                 + Groups.DELETED + ","
   1938                 + Groups.GROUP_VISIBLE + ","
   1939                 + Groups.SHOULD_SYNC + ","
   1940                 + Groups.AUTO_ADD + ","
   1941                 + Groups.FAVORITES + ","
   1942                 + Groups.GROUP_IS_READ_ONLY + ","
   1943                 + Groups.SYNC1 + ","
   1944                 + Groups.SYNC2 + ","
   1945                 + Groups.SYNC3 + ","
   1946                 + Groups.SYNC4 + ","
   1947                 + PackagesColumns.PACKAGE + " AS " + Groups.RES_PACKAGE;
   1948 
   1949         String groupsSelect = "SELECT "
   1950                 + GroupsColumns.CONCRETE_ID + " AS " + Groups._ID + ","
   1951                 + groupsColumns
   1952                 + " FROM " + Tables.GROUPS
   1953                 + " JOIN " + Tables.ACCOUNTS + " ON ("
   1954                     + GroupsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID + ")"
   1955                 + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON ("
   1956                     + GroupsColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")";
   1957 
   1958         db.execSQL("CREATE VIEW " + Views.GROUPS + " AS " + groupsSelect);
   1959     }
   1960 
   1961     @Override
   1962     public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   1963         Log.i(TAG, "ContactsProvider cannot proceed because downgrading your database is not " +
   1964                 "supported. To continue, please either re-upgrade to your previous Android " +
   1965                 "version, or clear all application data in Contacts Storage (this will result " +
   1966                 "in the loss of all local contacts that are not synced). To avoid data loss, " +
   1967                 "your contacts database will not be wiped automatically.");
   1968         super.onDowngrade(db, oldVersion, newVersion);
   1969     }
   1970 
   1971     @Override
   1972     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   1973         if (oldVersion < 99) {
   1974             Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion
   1975                     + ", data will be lost!");
   1976 
   1977             db.execSQL("DROP TABLE IF EXISTS " + Tables.CONTACTS + ";");
   1978             db.execSQL("DROP TABLE IF EXISTS " + Tables.RAW_CONTACTS + ";");
   1979             db.execSQL("DROP TABLE IF EXISTS " + Tables.PACKAGES + ";");
   1980             db.execSQL("DROP TABLE IF EXISTS " + Tables.MIMETYPES + ";");
   1981             db.execSQL("DROP TABLE IF EXISTS " + Tables.DATA + ";");
   1982             db.execSQL("DROP TABLE IF EXISTS " + Tables.PHONE_LOOKUP + ";");
   1983             db.execSQL("DROP TABLE IF EXISTS " + Tables.NAME_LOOKUP + ";");
   1984             db.execSQL("DROP TABLE IF EXISTS " + Tables.NICKNAME_LOOKUP + ";");
   1985             db.execSQL("DROP TABLE IF EXISTS " + Tables.GROUPS + ";");
   1986             db.execSQL("DROP TABLE IF EXISTS activities;");
   1987             db.execSQL("DROP TABLE IF EXISTS " + Tables.CALLS + ";");
   1988             db.execSQL("DROP TABLE IF EXISTS " + Tables.SETTINGS + ";");
   1989             db.execSQL("DROP TABLE IF EXISTS " + Tables.STATUS_UPDATES + ";");
   1990 
   1991             // TODO: we should not be dropping agg_exceptions and contact_options. In case that
   1992             // table's schema changes, we should try to preserve the data, because it was entered
   1993             // by the user and has never been synched to the server.
   1994             db.execSQL("DROP TABLE IF EXISTS " + Tables.AGGREGATION_EXCEPTIONS + ";");
   1995 
   1996             onCreate(db);
   1997             return;
   1998         }
   1999 
   2000         Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion);
   2001 
   2002         boolean upgradeViewsAndTriggers = false;
   2003         boolean upgradeNameLookup = false;
   2004         boolean upgradeLegacyApiSupport = false;
   2005         boolean upgradeSearchIndex = false;
   2006         boolean rescanDirectories = false;
   2007         boolean rebuildSqliteStats = false;
   2008         boolean upgradeLocaleSpecificData = false;
   2009 
   2010         if (oldVersion == 99) {
   2011             upgradeViewsAndTriggers = true;
   2012             oldVersion++;
   2013         }
   2014 
   2015         if (oldVersion == 100) {
   2016             db.execSQL("CREATE INDEX IF NOT EXISTS mimetypes_mimetype_index ON "
   2017                     + Tables.MIMETYPES + " ("
   2018                             + MimetypesColumns.MIMETYPE + ","
   2019                             + MimetypesColumns._ID + ");");
   2020             updateIndexStats(db, Tables.MIMETYPES,
   2021                     "mimetypes_mimetype_index", "50 1 1");
   2022 
   2023             upgradeViewsAndTriggers = true;
   2024             oldVersion++;
   2025         }
   2026 
   2027         if (oldVersion == 101) {
   2028             upgradeViewsAndTriggers = true;
   2029             oldVersion++;
   2030         }
   2031 
   2032         if (oldVersion == 102) {
   2033             upgradeViewsAndTriggers = true;
   2034             oldVersion++;
   2035         }
   2036 
   2037         if (oldVersion == 103) {
   2038             upgradeViewsAndTriggers = true;
   2039             oldVersion++;
   2040         }
   2041 
   2042         if (oldVersion == 104 || oldVersion == 201) {
   2043             LegacyApiSupport.createSettingsTable(db);
   2044             upgradeViewsAndTriggers = true;
   2045             oldVersion++;
   2046         }
   2047 
   2048         if (oldVersion == 105) {
   2049             upgradeToVersion202(db);
   2050             upgradeNameLookup = true;
   2051             oldVersion = 202;
   2052         }
   2053 
   2054         if (oldVersion == 202) {
   2055             upgradeToVersion203(db);
   2056             upgradeViewsAndTriggers = true;
   2057             oldVersion++;
   2058         }
   2059 
   2060         if (oldVersion == 203) {
   2061             upgradeViewsAndTriggers = true;
   2062             oldVersion++;
   2063         }
   2064 
   2065         if (oldVersion == 204) {
   2066             upgradeToVersion205(db);
   2067             upgradeViewsAndTriggers = true;
   2068             oldVersion++;
   2069         }
   2070 
   2071         if (oldVersion == 205) {
   2072             upgrateToVersion206(db);
   2073             upgradeViewsAndTriggers = true;
   2074             oldVersion++;
   2075         }
   2076 
   2077         if (oldVersion == 206) {
   2078             upgradeToVersion300(db);
   2079             oldVersion = 300;
   2080         }
   2081 
   2082         if (oldVersion == 300) {
   2083             upgradeViewsAndTriggers = true;
   2084             oldVersion = 301;
   2085         }
   2086 
   2087         if (oldVersion == 301) {
   2088             upgradeViewsAndTriggers = true;
   2089             oldVersion = 302;
   2090         }
   2091 
   2092         if (oldVersion == 302) {
   2093             upgradeEmailToVersion303(db);
   2094             upgradeNicknameToVersion303(db);
   2095             oldVersion = 303;
   2096         }
   2097 
   2098         if (oldVersion == 303) {
   2099             upgradeToVersion304(db);
   2100             oldVersion = 304;
   2101         }
   2102 
   2103         if (oldVersion == 304) {
   2104             upgradeNameLookup = true;
   2105             oldVersion = 305;
   2106         }
   2107 
   2108         if (oldVersion == 305) {
   2109             upgradeToVersion306(db);
   2110             oldVersion = 306;
   2111         }
   2112 
   2113         if (oldVersion == 306) {
   2114             upgradeToVersion307(db);
   2115             oldVersion = 307;
   2116         }
   2117 
   2118         if (oldVersion == 307) {
   2119             upgradeToVersion308(db);
   2120             oldVersion = 308;
   2121         }
   2122 
   2123         // Gingerbread upgrades
   2124         if (oldVersion < 350) {
   2125             upgradeViewsAndTriggers = true;
   2126             oldVersion = 351;
   2127         }
   2128 
   2129         if (oldVersion == 351) {
   2130             upgradeNameLookup = true;
   2131             oldVersion = 352;
   2132         }
   2133 
   2134         if (oldVersion == 352) {
   2135             upgradeToVersion353(db);
   2136             oldVersion = 353;
   2137         }
   2138 
   2139         // Honeycomb upgrades
   2140         if (oldVersion < 400) {
   2141             upgradeViewsAndTriggers = true;
   2142             upgradeToVersion400(db);
   2143             oldVersion = 400;
   2144         }
   2145 
   2146         if (oldVersion == 400) {
   2147             upgradeViewsAndTriggers = true;
   2148             upgradeToVersion401(db);
   2149             oldVersion = 401;
   2150         }
   2151 
   2152         if (oldVersion == 401) {
   2153             upgradeToVersion402(db);
   2154             oldVersion = 402;
   2155         }
   2156 
   2157         if (oldVersion == 402) {
   2158             upgradeViewsAndTriggers = true;
   2159             upgradeToVersion403(db);
   2160             oldVersion = 403;
   2161         }
   2162 
   2163         if (oldVersion == 403) {
   2164             upgradeViewsAndTriggers = true;
   2165             oldVersion = 404;
   2166         }
   2167 
   2168         if (oldVersion == 404) {
   2169             upgradeViewsAndTriggers = true;
   2170             upgradeToVersion405(db);
   2171             oldVersion = 405;
   2172         }
   2173 
   2174         if (oldVersion == 405) {
   2175             upgradeViewsAndTriggers = true;
   2176             upgradeToVersion406(db);
   2177             oldVersion = 406;
   2178         }
   2179 
   2180         if (oldVersion == 406) {
   2181             upgradeViewsAndTriggers = true;
   2182             oldVersion = 407;
   2183         }
   2184 
   2185         if (oldVersion == 407) {
   2186             // Obsolete
   2187             oldVersion = 408;
   2188         }
   2189 
   2190         if (oldVersion == 408) {
   2191             upgradeViewsAndTriggers = true;
   2192             upgradeToVersion409(db);
   2193             oldVersion = 409;
   2194         }
   2195 
   2196         if (oldVersion == 409) {
   2197             upgradeViewsAndTriggers = true;
   2198             oldVersion = 410;
   2199         }
   2200 
   2201         if (oldVersion == 410) {
   2202             upgradeToVersion411(db);
   2203             oldVersion = 411;
   2204         }
   2205 
   2206         if (oldVersion == 411) {
   2207             // Same upgrade as 353, only on Honeycomb devices
   2208             upgradeToVersion353(db);
   2209             oldVersion = 412;
   2210         }
   2211 
   2212         if (oldVersion == 412) {
   2213             upgradeToVersion413(db);
   2214             oldVersion = 413;
   2215         }
   2216 
   2217         if (oldVersion == 413) {
   2218             upgradeNameLookup = true;
   2219             oldVersion = 414;
   2220         }
   2221 
   2222         if (oldVersion == 414) {
   2223             upgradeToVersion415(db);
   2224             upgradeViewsAndTriggers = true;
   2225             oldVersion = 415;
   2226         }
   2227 
   2228         if (oldVersion == 415) {
   2229             upgradeToVersion416(db);
   2230             oldVersion = 416;
   2231         }
   2232 
   2233         if (oldVersion == 416) {
   2234             upgradeLegacyApiSupport = true;
   2235             oldVersion = 417;
   2236         }
   2237 
   2238         // Honeycomb-MR1 upgrades
   2239         if (oldVersion < 500) {
   2240             upgradeSearchIndex = true;
   2241         }
   2242 
   2243         if (oldVersion < 501) {
   2244             upgradeSearchIndex = true;
   2245             upgradeToVersion501(db);
   2246             oldVersion = 501;
   2247         }
   2248 
   2249         if (oldVersion < 502) {
   2250             upgradeSearchIndex = true;
   2251             upgradeToVersion502(db);
   2252             oldVersion = 502;
   2253         }
   2254 
   2255         if (oldVersion < 503) {
   2256             upgradeSearchIndex = true;
   2257             oldVersion = 503;
   2258         }
   2259 
   2260         if (oldVersion < 504) {
   2261             upgradeToVersion504(db);
   2262             oldVersion = 504;
   2263         }
   2264 
   2265         if (oldVersion < 600) {
   2266             upgradeToVersion600(db);
   2267             upgradeViewsAndTriggers = true;
   2268             oldVersion = 600;
   2269         }
   2270 
   2271         if (oldVersion < 601) {
   2272             upgradeToVersion601(db);
   2273             oldVersion = 601;
   2274         }
   2275 
   2276         if (oldVersion < 602) {
   2277             upgradeToVersion602(db);
   2278             oldVersion = 602;
   2279         }
   2280 
   2281         if (oldVersion < 603) {
   2282             upgradeViewsAndTriggers = true;
   2283             oldVersion = 603;
   2284         }
   2285 
   2286         if (oldVersion < 604) {
   2287             upgradeToVersion604(db);
   2288             oldVersion = 604;
   2289         }
   2290 
   2291         if (oldVersion < 605) {
   2292             upgradeViewsAndTriggers = true;
   2293             upgradeToVersion605(db);
   2294             oldVersion = 605;
   2295         }
   2296 
   2297         if (oldVersion < 606) {
   2298             upgradeViewsAndTriggers = true;
   2299             upgradeLegacyApiSupport = true;
   2300             upgradeToVersion606(db);
   2301             oldVersion = 606;
   2302         }
   2303 
   2304         if (oldVersion < 607) {
   2305             upgradeViewsAndTriggers = true;
   2306             upgradeToVersion607(db);
   2307             oldVersion = 607;
   2308         }
   2309 
   2310         if (oldVersion < 608) {
   2311             upgradeViewsAndTriggers = true;
   2312             upgradeToVersion608(db);
   2313             oldVersion = 608;
   2314         }
   2315 
   2316         if (oldVersion < 609) {
   2317             upgradeToVersion609(db);
   2318             oldVersion = 609;
   2319         }
   2320 
   2321         if (oldVersion < 610) {
   2322             upgradeToVersion610(db);
   2323             oldVersion = 610;
   2324         }
   2325 
   2326         if (oldVersion < 611) {
   2327             upgradeViewsAndTriggers = true;
   2328             upgradeToVersion611(db);
   2329             oldVersion = 611;
   2330         }
   2331 
   2332         if (oldVersion < 612) {
   2333             upgradeViewsAndTriggers = true;
   2334             upgradeToVersion612(db);
   2335             oldVersion = 612;
   2336         }
   2337 
   2338         if (oldVersion < 613) {
   2339             upgradeToVersion613(db);
   2340             oldVersion = 613;
   2341         }
   2342 
   2343         if (oldVersion < 614) {
   2344             // this creates the view "view_stream_items"
   2345             upgradeViewsAndTriggers = true;
   2346             oldVersion = 614;
   2347         }
   2348 
   2349         if (oldVersion < 615) {
   2350             upgradeToVersion615(db);
   2351             oldVersion = 615;
   2352         }
   2353 
   2354         if (oldVersion < 616) {
   2355             // this updates the "view_stream_items" view
   2356             upgradeViewsAndTriggers = true;
   2357             oldVersion = 616;
   2358         }
   2359 
   2360         if (oldVersion < 617) {
   2361             // This version upgrade obsoleted the profile_raw_contact_id field of the Accounts
   2362             // table, but we aren't removing the column because it is very little data (and not
   2363             // referenced anymore).  We do need to upgrade the views to handle the simplified
   2364             // per-database "is profile" columns.
   2365             upgradeViewsAndTriggers = true;
   2366             oldVersion = 617;
   2367         }
   2368 
   2369         if (oldVersion < 618) {
   2370             upgradeToVersion618(db);
   2371             oldVersion = 618;
   2372         }
   2373 
   2374         if (oldVersion < 619) {
   2375             upgradeViewsAndTriggers = true;
   2376             oldVersion = 619;
   2377         }
   2378 
   2379         if (oldVersion < 620) {
   2380             upgradeViewsAndTriggers = true;
   2381             oldVersion = 620;
   2382         }
   2383 
   2384         if (oldVersion < 621) {
   2385             upgradeSearchIndex = true;
   2386             oldVersion = 621;
   2387         }
   2388 
   2389         if (oldVersion < 622) {
   2390             upgradeToVersion622(db);
   2391             oldVersion = 622;
   2392         }
   2393 
   2394         if (oldVersion < 623) {
   2395             // change FTS to normalize names using collation key
   2396             upgradeSearchIndex = true;
   2397             oldVersion = 623;
   2398         }
   2399 
   2400         if (oldVersion < 624) {
   2401             // Upgraded the sqlite index stats
   2402             upgradeViewsAndTriggers = true;
   2403             oldVersion = 624;
   2404         }
   2405 
   2406         if (oldVersion < 625) {
   2407             // Fix for search for hyphenated names
   2408             upgradeSearchIndex = true;
   2409             oldVersion = 625;
   2410         }
   2411 
   2412         if (oldVersion < 626) {
   2413             upgradeToVersion626(db);
   2414             upgradeViewsAndTriggers = true;
   2415             oldVersion = 626;
   2416         }
   2417 
   2418         if (oldVersion < 700) {
   2419             rescanDirectories = true;
   2420             oldVersion = 700;
   2421         }
   2422 
   2423         if (oldVersion < 701) {
   2424             upgradeToVersion701(db);
   2425             oldVersion = 701;
   2426         }
   2427 
   2428         if (oldVersion < 702) {
   2429             upgradeToVersion702(db);
   2430             oldVersion = 702;
   2431         }
   2432 
   2433         if (oldVersion < 703) {
   2434             // Now names like "L'Image" will be searchable.
   2435             upgradeSearchIndex = true;
   2436             oldVersion = 703;
   2437         }
   2438 
   2439         if (oldVersion < 704) {
   2440             db.execSQL("DROP TABLE IF EXISTS activities;");
   2441             oldVersion = 704;
   2442         }
   2443 
   2444         if (oldVersion < 705) {
   2445             // Before this version, we didn't rebuild the search index on locale changes, so
   2446             // if the locale has changed after sync, the index contains gets stale.
   2447             // To correct the issue we have to rebuild the index here.
   2448             upgradeSearchIndex = true;
   2449             oldVersion = 705;
   2450         }
   2451 
   2452         if (oldVersion < 706) {
   2453             // Prior to this version, we didn't rebuild the stats table after drop operations,
   2454             // which resulted in losing some of the rows from the stats table.
   2455             rebuildSqliteStats = true;
   2456             oldVersion = 706;
   2457         }
   2458 
   2459         if (oldVersion < 707) {
   2460             upgradeToVersion707(db);
   2461             upgradeViewsAndTriggers = true;
   2462             oldVersion = 707;
   2463         }
   2464 
   2465         if (oldVersion < 708) {
   2466             // Sort keys, phonebook labels and buckets, and search keys have
   2467             // changed so force a rebuild.
   2468             upgradeLocaleSpecificData = true;
   2469             oldVersion = 708;
   2470         }
   2471         if (oldVersion < 709) {
   2472             // Added secondary locale phonebook labels; changed Japanese
   2473             // and Chinese sort keys.
   2474             upgradeLocaleSpecificData = true;
   2475             oldVersion = 709;
   2476         }
   2477 
   2478         if (oldVersion < 710) {
   2479             upgradeToVersion710(db);
   2480             upgradeViewsAndTriggers = true;
   2481             oldVersion = 710;
   2482         }
   2483 
   2484         if (upgradeViewsAndTriggers) {
   2485             createContactsViews(db);
   2486             createGroupsView(db);
   2487             createContactsTriggers(db);
   2488             createContactsIndexes(db, false /* we build stats table later */);
   2489             upgradeLegacyApiSupport = true;
   2490             rebuildSqliteStats = true;
   2491         }
   2492 
   2493         if (upgradeLegacyApiSupport) {
   2494             LegacyApiSupport.createViews(db);
   2495         }
   2496 
   2497         if (upgradeLocaleSpecificData) {
   2498             upgradeLocaleData(db, false /* we build stats table later */);
   2499             // Name lookups are rebuilt as part of the full locale rebuild
   2500             upgradeNameLookup = false;
   2501             upgradeSearchIndex = true;
   2502             rebuildSqliteStats = true;
   2503         }
   2504 
   2505         if (upgradeNameLookup) {
   2506             rebuildNameLookup(db, false /* we build stats table later */);
   2507             rebuildSqliteStats = true;
   2508         }
   2509 
   2510         if (upgradeSearchIndex) {
   2511             rebuildSearchIndex(db, false /* we build stats table later */);
   2512             rebuildSqliteStats = true;
   2513         }
   2514 
   2515         if (rescanDirectories) {
   2516             // Force the next ContactDirectoryManager.scanAllPackages() to rescan all packages.
   2517             // (It's called from the BACKGROUND_TASK_UPDATE_ACCOUNTS background task.)
   2518             setProperty(db, DbProperties.DIRECTORY_SCAN_COMPLETE, "0");
   2519         }
   2520 
   2521         if (rebuildSqliteStats) {
   2522             updateSqliteStats(db);
   2523         }
   2524 
   2525         if (oldVersion != newVersion) {
   2526             throw new IllegalStateException(
   2527                     "error upgrading the database to version " + newVersion);
   2528         }
   2529     }
   2530 
   2531     private void upgradeToVersion202(SQLiteDatabase db) {
   2532         db.execSQL(
   2533                 "ALTER TABLE " + Tables.PHONE_LOOKUP +
   2534                 " ADD " + PhoneLookupColumns.MIN_MATCH + " TEXT;");
   2535 
   2536         db.execSQL("CREATE INDEX phone_lookup_min_match_index ON " + Tables.PHONE_LOOKUP + " (" +
   2537                 PhoneLookupColumns.MIN_MATCH + "," +
   2538                 PhoneLookupColumns.RAW_CONTACT_ID + "," +
   2539                 PhoneLookupColumns.DATA_ID +
   2540         ");");
   2541 
   2542         updateIndexStats(db, Tables.PHONE_LOOKUP,
   2543                 "phone_lookup_min_match_index", "10000 2 2 1");
   2544 
   2545         SQLiteStatement update = db.compileStatement(
   2546                 "UPDATE " + Tables.PHONE_LOOKUP +
   2547                 " SET " + PhoneLookupColumns.MIN_MATCH + "=?" +
   2548                 " WHERE " + PhoneLookupColumns.DATA_ID + "=?");
   2549 
   2550         // Populate the new column
   2551         Cursor c = db.query(Tables.PHONE_LOOKUP + " JOIN " + Tables.DATA +
   2552                 " ON (" + PhoneLookupColumns.DATA_ID + "=" + DataColumns.CONCRETE_ID + ")",
   2553                 new String[]{Data._ID, Phone.NUMBER}, null, null, null, null, null);
   2554         try {
   2555             while (c.moveToNext()) {
   2556                 long dataId = c.getLong(0);
   2557                 String number = c.getString(1);
   2558                 if (!TextUtils.isEmpty(number)) {
   2559                     update.bindString(1, PhoneNumberUtils.toCallerIDMinMatch(number));
   2560                     update.bindLong(2, dataId);
   2561                     update.execute();
   2562                 }
   2563             }
   2564         } finally {
   2565             c.close();
   2566         }
   2567     }
   2568 
   2569     private void upgradeToVersion203(SQLiteDatabase db) {
   2570         // Garbage-collect first. A bug in Eclair was sometimes leaving
   2571         // raw_contacts in the database that no longer had contacts associated
   2572         // with them.  To avoid failures during this database upgrade, drop
   2573         // the orphaned raw_contacts.
   2574         db.execSQL(
   2575                 "DELETE FROM raw_contacts" +
   2576                 " WHERE contact_id NOT NULL" +
   2577                 " AND contact_id NOT IN (SELECT _id FROM contacts)");
   2578 
   2579         db.execSQL(
   2580                 "ALTER TABLE " + Tables.CONTACTS +
   2581                 " ADD " + Contacts.NAME_RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)");
   2582         db.execSQL(
   2583                 "ALTER TABLE " + Tables.RAW_CONTACTS +
   2584                 " ADD contact_in_visible_group INTEGER NOT NULL DEFAULT 0");
   2585 
   2586         // For each Contact, find the RawContact that contributed the display name
   2587         db.execSQL(
   2588                 "UPDATE " + Tables.CONTACTS +
   2589                 " SET " + Contacts.NAME_RAW_CONTACT_ID + "=(" +
   2590                         " SELECT " + RawContacts._ID +
   2591                         " FROM " + Tables.RAW_CONTACTS +
   2592                         " WHERE " + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID +
   2593                         " AND " + RawContactsColumns.CONCRETE_DISPLAY_NAME + "=" +
   2594                                 Tables.CONTACTS + "." + Contacts.DISPLAY_NAME +
   2595                         " ORDER BY " + RawContacts._ID +
   2596                         " LIMIT 1)"
   2597         );
   2598 
   2599         db.execSQL("CREATE INDEX contacts_name_raw_contact_id_index ON " + Tables.CONTACTS + " (" +
   2600                 Contacts.NAME_RAW_CONTACT_ID +
   2601         ");");
   2602 
   2603         // If for some unknown reason we missed some names, let's make sure there are
   2604         // no contacts without a name, picking a raw contact "at random".
   2605         db.execSQL(
   2606                 "UPDATE " + Tables.CONTACTS +
   2607                 " SET " + Contacts.NAME_RAW_CONTACT_ID + "=(" +
   2608                         " SELECT " + RawContacts._ID +
   2609                         " FROM " + Tables.RAW_CONTACTS +
   2610                         " WHERE " + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID +
   2611                         " ORDER BY " + RawContacts._ID +
   2612                         " LIMIT 1)" +
   2613                 " WHERE " + Contacts.NAME_RAW_CONTACT_ID + " IS NULL"
   2614         );
   2615 
   2616         // Wipe out DISPLAY_NAME on the Contacts table as it is no longer in use.
   2617         db.execSQL(
   2618                 "UPDATE " + Tables.CONTACTS +
   2619                 " SET " + Contacts.DISPLAY_NAME + "=NULL"
   2620         );
   2621 
   2622         // Copy the IN_VISIBLE_GROUP flag down to all raw contacts to allow
   2623         // indexing on (display_name, in_visible_group)
   2624         db.execSQL(
   2625                 "UPDATE " + Tables.RAW_CONTACTS +
   2626                 " SET contact_in_visible_group=(" +
   2627                         "SELECT " + Contacts.IN_VISIBLE_GROUP +
   2628                         " FROM " + Tables.CONTACTS +
   2629                         " WHERE " + Contacts._ID + "=" + RawContacts.CONTACT_ID + ")" +
   2630                 " WHERE " + RawContacts.CONTACT_ID + " NOT NULL"
   2631         );
   2632 
   2633         db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" +
   2634                 "contact_in_visible_group" + "," +
   2635                 RawContactsColumns.DISPLAY_NAME + " COLLATE LOCALIZED ASC" +
   2636         ");");
   2637 
   2638         db.execSQL("DROP INDEX contacts_visible_index");
   2639         db.execSQL("CREATE INDEX contacts_visible_index ON " + Tables.CONTACTS + " (" +
   2640                 Contacts.IN_VISIBLE_GROUP +
   2641         ");");
   2642     }
   2643 
   2644     private void upgradeToVersion205(SQLiteDatabase db) {
   2645         db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
   2646                 + " ADD " + RawContacts.DISPLAY_NAME_ALTERNATIVE + " TEXT;");
   2647         db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
   2648                 + " ADD " + RawContacts.PHONETIC_NAME + " TEXT;");
   2649         db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
   2650                 + " ADD " + RawContacts.PHONETIC_NAME_STYLE + " INTEGER;");
   2651         db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
   2652                 + " ADD " + RawContacts.SORT_KEY_PRIMARY
   2653                 + " TEXT COLLATE " + ContactsProvider2.PHONEBOOK_COLLATOR_NAME + ";");
   2654         db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
   2655                 + " ADD " + RawContacts.SORT_KEY_ALTERNATIVE
   2656                 + " TEXT COLLATE " + ContactsProvider2.PHONEBOOK_COLLATOR_NAME + ";");
   2657 
   2658         NameSplitter splitter = createNameSplitter();
   2659 
   2660         SQLiteStatement rawContactUpdate = db.compileStatement(
   2661                 "UPDATE " + Tables.RAW_CONTACTS +
   2662                 " SET " +
   2663                         RawContacts.DISPLAY_NAME_PRIMARY + "=?," +
   2664                         RawContacts.DISPLAY_NAME_ALTERNATIVE + "=?," +
   2665                         RawContacts.PHONETIC_NAME + "=?," +
   2666                         RawContacts.PHONETIC_NAME_STYLE + "=?," +
   2667                         RawContacts.SORT_KEY_PRIMARY + "=?," +
   2668                         RawContacts.SORT_KEY_ALTERNATIVE + "=?" +
   2669                 " WHERE " + RawContacts._ID + "=?");
   2670 
   2671         upgradeStructuredNamesToVersion205(db, rawContactUpdate, splitter);
   2672         upgradeOrganizationsToVersion205(db, rawContactUpdate, splitter);
   2673 
   2674         db.execSQL("DROP INDEX raw_contact_sort_key1_index");
   2675         db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" +
   2676                 "contact_in_visible_group" + "," +
   2677                 RawContacts.SORT_KEY_PRIMARY +
   2678         ");");
   2679 
   2680         db.execSQL("CREATE INDEX raw_contact_sort_key2_index ON " + Tables.RAW_CONTACTS + " (" +
   2681                 "contact_in_visible_group" + "," +
   2682                 RawContacts.SORT_KEY_ALTERNATIVE +
   2683         ");");
   2684     }
   2685 
   2686     private interface StructName205Query {
   2687         String TABLE = Tables.DATA_JOIN_RAW_CONTACTS;
   2688 
   2689         String COLUMNS[] = {
   2690                 DataColumns.CONCRETE_ID,
   2691                 Data.RAW_CONTACT_ID,
   2692                 RawContacts.DISPLAY_NAME_SOURCE,
   2693                 RawContacts.DISPLAY_NAME_PRIMARY,
   2694                 StructuredName.PREFIX,
   2695                 StructuredName.GIVEN_NAME,
   2696                 StructuredName.MIDDLE_NAME,
   2697                 StructuredName.FAMILY_NAME,
   2698                 StructuredName.SUFFIX,
   2699                 StructuredName.PHONETIC_FAMILY_NAME,
   2700                 StructuredName.PHONETIC_MIDDLE_NAME,
   2701                 StructuredName.PHONETIC_GIVEN_NAME,
   2702         };
   2703 
   2704         int ID = 0;
   2705         int RAW_CONTACT_ID = 1;
   2706         int DISPLAY_NAME_SOURCE = 2;
   2707         int DISPLAY_NAME = 3;
   2708         int PREFIX = 4;
   2709         int GIVEN_NAME = 5;
   2710         int MIDDLE_NAME = 6;
   2711         int FAMILY_NAME = 7;
   2712         int SUFFIX = 8;
   2713         int PHONETIC_FAMILY_NAME = 9;
   2714         int PHONETIC_MIDDLE_NAME = 10;
   2715         int PHONETIC_GIVEN_NAME = 11;
   2716     }
   2717 
   2718     private void upgradeStructuredNamesToVersion205(SQLiteDatabase db,
   2719             SQLiteStatement rawContactUpdate, NameSplitter splitter) {
   2720 
   2721         // Process structured names to detect the style of the full name and phonetic name
   2722 
   2723         long mMimeType;
   2724         try {
   2725             mMimeType = DatabaseUtils.longForQuery(db,
   2726                     "SELECT " + MimetypesColumns._ID +
   2727                     " FROM " + Tables.MIMETYPES +
   2728                     " WHERE " + MimetypesColumns.MIMETYPE
   2729                             + "='" + StructuredName.CONTENT_ITEM_TYPE + "'", null);
   2730         } catch (SQLiteDoneException e) {
   2731             // No structured names in the database
   2732             return;
   2733         }
   2734 
   2735         SQLiteStatement structuredNameUpdate = db.compileStatement(
   2736                 "UPDATE " + Tables.DATA +
   2737                 " SET " +
   2738                         StructuredName.FULL_NAME_STYLE + "=?," +
   2739                         StructuredName.DISPLAY_NAME + "=?," +
   2740                         StructuredName.PHONETIC_NAME_STYLE + "=?" +
   2741                 " WHERE " + Data._ID + "=?");
   2742 
   2743         NameSplitter.Name name = new NameSplitter.Name();
   2744         StringBuilder sb = new StringBuilder();
   2745         Cursor cursor = db.query(StructName205Query.TABLE,
   2746                 StructName205Query.COLUMNS,
   2747                 DataColumns.MIMETYPE_ID + "=" + mMimeType, null, null, null, null);
   2748         try {
   2749             while (cursor.moveToNext()) {
   2750                 long dataId = cursor.getLong(StructName205Query.ID);
   2751                 long rawContactId = cursor.getLong(StructName205Query.RAW_CONTACT_ID);
   2752                 int displayNameSource = cursor.getInt(StructName205Query.DISPLAY_NAME_SOURCE);
   2753                 String displayName = cursor.getString(StructName205Query.DISPLAY_NAME);
   2754 
   2755                 name.clear();
   2756                 name.prefix = cursor.getString(StructName205Query.PREFIX);
   2757                 name.givenNames = cursor.getString(StructName205Query.GIVEN_NAME);
   2758                 name.middleName = cursor.getString(StructName205Query.MIDDLE_NAME);
   2759                 name.familyName = cursor.getString(StructName205Query.FAMILY_NAME);
   2760                 name.suffix = cursor.getString(StructName205Query.SUFFIX);
   2761                 name.phoneticFamilyName = cursor.getString(StructName205Query.PHONETIC_FAMILY_NAME);
   2762                 name.phoneticMiddleName = cursor.getString(StructName205Query.PHONETIC_MIDDLE_NAME);
   2763                 name.phoneticGivenName = cursor.getString(StructName205Query.PHONETIC_GIVEN_NAME);
   2764 
   2765                 upgradeNameToVersion205(dataId, rawContactId, displayNameSource, displayName, name,
   2766                         structuredNameUpdate, rawContactUpdate, splitter, sb);
   2767             }
   2768         } finally {
   2769             cursor.close();
   2770         }
   2771     }
   2772 
   2773     private void upgradeNameToVersion205(long dataId, long rawContactId, int displayNameSource,
   2774             String currentDisplayName, NameSplitter.Name name,
   2775             SQLiteStatement structuredNameUpdate, SQLiteStatement rawContactUpdate,
   2776             NameSplitter splitter, StringBuilder sb) {
   2777 
   2778         splitter.guessNameStyle(name);
   2779         int unadjustedFullNameStyle = name.fullNameStyle;
   2780         name.fullNameStyle = splitter.getAdjustedFullNameStyle(name.fullNameStyle);
   2781         String displayName = splitter.join(name, true, true);
   2782 
   2783         // Don't update database with the adjusted fullNameStyle as it is locale
   2784         // related
   2785         structuredNameUpdate.bindLong(1, unadjustedFullNameStyle);
   2786         DatabaseUtils.bindObjectToProgram(structuredNameUpdate, 2, displayName);
   2787         structuredNameUpdate.bindLong(3, name.phoneticNameStyle);
   2788         structuredNameUpdate.bindLong(4, dataId);
   2789         structuredNameUpdate.execute();
   2790 
   2791         if (displayNameSource == DisplayNameSources.STRUCTURED_NAME) {
   2792             String displayNameAlternative = splitter.join(name, false, false);
   2793             String phoneticName = splitter.joinPhoneticName(name);
   2794             String sortKey = null;
   2795             String sortKeyAlternative = null;
   2796 
   2797             if (phoneticName != null) {
   2798                 sortKey = sortKeyAlternative = phoneticName;
   2799             } else if (name.fullNameStyle == FullNameStyle.CHINESE ||
   2800                     name.fullNameStyle == FullNameStyle.CJK) {
   2801                 sortKey = sortKeyAlternative = displayName;
   2802             }
   2803 
   2804             if (sortKey == null) {
   2805                 sortKey = displayName;
   2806                 sortKeyAlternative = displayNameAlternative;
   2807             }
   2808 
   2809             updateRawContact205(rawContactUpdate, rawContactId, displayName,
   2810                     displayNameAlternative, name.phoneticNameStyle, phoneticName, sortKey,
   2811                     sortKeyAlternative);
   2812         }
   2813     }
   2814 
   2815     private interface Organization205Query {
   2816         String TABLE = Tables.DATA_JOIN_RAW_CONTACTS;
   2817 
   2818         String COLUMNS[] = {
   2819                 DataColumns.CONCRETE_ID,
   2820                 Data.RAW_CONTACT_ID,
   2821                 Organization.COMPANY,
   2822                 Organization.PHONETIC_NAME,
   2823         };
   2824 
   2825         int ID = 0;
   2826         int RAW_CONTACT_ID = 1;
   2827         int COMPANY = 2;
   2828         int PHONETIC_NAME = 3;
   2829     }
   2830 
   2831     private void upgradeOrganizationsToVersion205(SQLiteDatabase db,
   2832             SQLiteStatement rawContactUpdate, NameSplitter splitter) {
   2833         final long mimeType = lookupMimeTypeId(db, Organization.CONTENT_ITEM_TYPE);
   2834 
   2835         SQLiteStatement organizationUpdate = db.compileStatement(
   2836                 "UPDATE " + Tables.DATA +
   2837                 " SET " +
   2838                         Organization.PHONETIC_NAME_STYLE + "=?" +
   2839                 " WHERE " + Data._ID + "=?");
   2840 
   2841         Cursor cursor = db.query(Organization205Query.TABLE, Organization205Query.COLUMNS,
   2842                 DataColumns.MIMETYPE_ID + "=" + mimeType + " AND "
   2843                         + RawContacts.DISPLAY_NAME_SOURCE + "=" + DisplayNameSources.ORGANIZATION,
   2844                 null, null, null, null);
   2845         try {
   2846             while (cursor.moveToNext()) {
   2847                 long dataId = cursor.getLong(Organization205Query.ID);
   2848                 long rawContactId = cursor.getLong(Organization205Query.RAW_CONTACT_ID);
   2849                 String company = cursor.getString(Organization205Query.COMPANY);
   2850                 String phoneticName = cursor.getString(Organization205Query.PHONETIC_NAME);
   2851 
   2852                 int phoneticNameStyle = splitter.guessPhoneticNameStyle(phoneticName);
   2853 
   2854                 organizationUpdate.bindLong(1, phoneticNameStyle);
   2855                 organizationUpdate.bindLong(2, dataId);
   2856                 organizationUpdate.execute();
   2857 
   2858                 String sortKey = company;
   2859 
   2860                 updateRawContact205(rawContactUpdate, rawContactId, company,
   2861                         company, phoneticNameStyle, phoneticName, sortKey, sortKey);
   2862             }
   2863         } finally {
   2864             cursor.close();
   2865         }
   2866     }
   2867 
   2868     private void updateRawContact205(SQLiteStatement rawContactUpdate, long rawContactId,
   2869             String displayName, String displayNameAlternative, int phoneticNameStyle,
   2870             String phoneticName, String sortKeyPrimary, String sortKeyAlternative) {
   2871         bindString(rawContactUpdate, 1, displayName);
   2872         bindString(rawContactUpdate, 2, displayNameAlternative);
   2873         bindString(rawContactUpdate, 3, phoneticName);
   2874         rawContactUpdate.bindLong(4, phoneticNameStyle);
   2875         bindString(rawContactUpdate, 5, sortKeyPrimary);
   2876         bindString(rawContactUpdate, 6, sortKeyAlternative);
   2877         rawContactUpdate.bindLong(7, rawContactId);
   2878         rawContactUpdate.execute();
   2879     }
   2880 
   2881     private void upgrateToVersion206(SQLiteDatabase db) {
   2882         db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
   2883                 + " ADD " + RawContacts.NAME_VERIFIED + " INTEGER NOT NULL DEFAULT 0;");
   2884     }
   2885 
   2886     /**
   2887      * Fix for the bug where name lookup records for organizations would get removed by
   2888      * unrelated updates of the data rows.
   2889      */
   2890     private void upgradeToVersion300(SQLiteDatabase db) {
   2891         // No longer needed
   2892     }
   2893 
   2894     private static final class Upgrade303Query {
   2895         public static final String TABLE = Tables.DATA;
   2896 
   2897         public static final String SELECTION =
   2898                 DataColumns.MIMETYPE_ID + "=?" +
   2899                     " AND " + Data._ID + " NOT IN " +
   2900                     "(SELECT " + NameLookupColumns.DATA_ID + " FROM " + Tables.NAME_LOOKUP + ")" +
   2901                     " AND " + Data.DATA1 + " NOT NULL";
   2902 
   2903         public static final String COLUMNS[] = {
   2904                 Data._ID,
   2905                 Data.RAW_CONTACT_ID,
   2906                 Data.DATA1,
   2907         };
   2908 
   2909         public static final int ID = 0;
   2910         public static final int RAW_CONTACT_ID = 1;
   2911         public static final int DATA1 = 2;
   2912     }
   2913 
   2914     /**
   2915      * The {@link ContactsProvider2#update} method was deleting name lookup for new
   2916      * emails during the sync.  We need to restore the lost name lookup rows.
   2917      */
   2918     private void upgradeEmailToVersion303(SQLiteDatabase db) {
   2919         final long mimeTypeId = lookupMimeTypeId(db, Email.CONTENT_ITEM_TYPE);
   2920         if (mimeTypeId == -1) {
   2921             return;
   2922         }
   2923 
   2924         ContentValues values = new ContentValues();
   2925 
   2926         // Find all data rows with the mime type "email" that are missing name lookup
   2927         Cursor cursor = db.query(Upgrade303Query.TABLE, Upgrade303Query.COLUMNS,
   2928                 Upgrade303Query.SELECTION, new String[] {String.valueOf(mimeTypeId)},
   2929                 null, null, null);
   2930         try {
   2931             while (cursor.moveToNext()) {
   2932                 long dataId = cursor.getLong(Upgrade303Query.ID);
   2933                 long rawContactId = cursor.getLong(Upgrade303Query.RAW_CONTACT_ID);
   2934                 String value = cursor.getString(Upgrade303Query.DATA1);
   2935                 value = extractHandleFromEmailAddress(value);
   2936 
   2937                 if (value != null) {
   2938                     values.put(NameLookupColumns.DATA_ID, dataId);
   2939                     values.put(NameLookupColumns.RAW_CONTACT_ID, rawContactId);
   2940                     values.put(NameLookupColumns.NAME_TYPE, NameLookupType.EMAIL_BASED_NICKNAME);
   2941                     values.put(NameLookupColumns.NORMALIZED_NAME, NameNormalizer.normalize(value));
   2942                     db.insert(Tables.NAME_LOOKUP, null, values);
   2943                 }
   2944             }
   2945         } finally {
   2946             cursor.close();
   2947         }
   2948     }
   2949 
   2950     /**
   2951      * The {@link ContactsProvider2#update} method was deleting name lookup for new
   2952      * nicknames during the sync.  We need to restore the lost name lookup rows.
   2953      */
   2954     private void upgradeNicknameToVersion303(SQLiteDatabase db) {
   2955         final long mimeTypeId = lookupMimeTypeId(db, Nickname.CONTENT_ITEM_TYPE);
   2956         if (mimeTypeId == -1) {
   2957             return;
   2958         }
   2959 
   2960         ContentValues values = new ContentValues();
   2961 
   2962         // Find all data rows with the mime type "nickname" that are missing name lookup
   2963         Cursor cursor = db.query(Upgrade303Query.TABLE, Upgrade303Query.COLUMNS,
   2964                 Upgrade303Query.SELECTION, new String[] {String.valueOf(mimeTypeId)},
   2965                 null, null, null);
   2966         try {
   2967             while (cursor.moveToNext()) {
   2968                 long dataId = cursor.getLong(Upgrade303Query.ID);
   2969                 long rawContactId = cursor.getLong(Upgrade303Query.RAW_CONTACT_ID);
   2970                 String value = cursor.getString(Upgrade303Query.DATA1);
   2971 
   2972                 values.put(NameLookupColumns.DATA_ID, dataId);
   2973                 values.put(NameLookupColumns.RAW_CONTACT_ID, rawContactId);
   2974                 values.put(NameLookupColumns.NAME_TYPE, NameLookupType.NICKNAME);
   2975                 values.put(NameLookupColumns.NORMALIZED_NAME, NameNormalizer.normalize(value));
   2976                 db.insert(Tables.NAME_LOOKUP, null, values);
   2977             }
   2978         } finally {
   2979             cursor.close();
   2980         }
   2981     }
   2982 
   2983     private void upgradeToVersion304(SQLiteDatabase db) {
   2984         // Mimetype table requires an index on mime type
   2985         db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS mime_type ON " + Tables.MIMETYPES + " (" +
   2986                 MimetypesColumns.MIMETYPE +
   2987         ");");
   2988     }
   2989 
   2990     private void upgradeToVersion306(SQLiteDatabase db) {
   2991         // Fix invalid lookup that was used for Exchange contacts (it was not escaped)
   2992         // It happened when a new contact was created AND synchronized
   2993         final StringBuilder lookupKeyBuilder = new StringBuilder();
   2994         final SQLiteStatement updateStatement = db.compileStatement(
   2995                 "UPDATE contacts " +
   2996                 "SET lookup=? " +
   2997                 "WHERE _id=?");
   2998         final Cursor contactIdCursor = db.rawQuery(
   2999                 "SELECT DISTINCT contact_id " +
   3000                 "FROM raw_contacts " +
   3001                 "WHERE deleted=0 AND account_type='com.android.exchange'",
   3002                 null);
   3003         try {
   3004             while (contactIdCursor.moveToNext()) {
   3005                 final long contactId = contactIdCursor.getLong(0);
   3006                 lookupKeyBuilder.setLength(0);
   3007                 final Cursor c = db.rawQuery(
   3008                         "SELECT account_type, account_name, _id, sourceid, display_name " +
   3009                         "FROM raw_contacts " +
   3010                         "WHERE contact_id=? " +
   3011                         "ORDER BY _id",
   3012                         new String[] { String.valueOf(contactId) });
   3013                 try {
   3014                     while (c.moveToNext()) {
   3015                         ContactLookupKey.appendToLookupKey(lookupKeyBuilder,
   3016                                 c.getString(0),
   3017                                 c.getString(1),
   3018                                 c.getLong(2),
   3019                                 c.getString(3),
   3020                                 c.getString(4));
   3021                     }
   3022                 } finally {
   3023                     c.close();
   3024                 }
   3025 
   3026                 if (lookupKeyBuilder.length() == 0) {
   3027                     updateStatement.bindNull(1);
   3028                 } else {
   3029                     updateStatement.bindString(1, Uri.encode(lookupKeyBuilder.toString()));
   3030                 }
   3031                 updateStatement.bindLong(2, contactId);
   3032 
   3033                 updateStatement.execute();
   3034             }
   3035         } finally {
   3036             updateStatement.close();
   3037             contactIdCursor.close();
   3038         }
   3039     }
   3040 
   3041     private void upgradeToVersion307(SQLiteDatabase db) {
   3042         db.execSQL("CREATE TABLE properties (" +
   3043                 "property_key TEXT PRIMARY_KEY, " +
   3044                 "property_value TEXT" +
   3045         ");");
   3046     }
   3047 
   3048     private void upgradeToVersion308(SQLiteDatabase db) {
   3049         db.execSQL("CREATE TABLE accounts (" +
   3050                 "account_name TEXT, " +
   3051                 "account_type TEXT " +
   3052         ");");
   3053 
   3054         db.execSQL("INSERT INTO accounts " +
   3055                 "SELECT DISTINCT account_name, account_type FROM raw_contacts");
   3056     }
   3057 
   3058     private void upgradeToVersion400(SQLiteDatabase db) {
   3059         db.execSQL("ALTER TABLE " + Tables.GROUPS
   3060                 + " ADD " + Groups.FAVORITES + " INTEGER NOT NULL DEFAULT 0;");
   3061         db.execSQL("ALTER TABLE " + Tables.GROUPS
   3062                 + " ADD " + Groups.AUTO_ADD + " INTEGER NOT NULL DEFAULT 0;");
   3063     }
   3064 
   3065     private void upgradeToVersion353(SQLiteDatabase db) {
   3066         db.execSQL("DELETE FROM contacts " +
   3067                 "WHERE NOT EXISTS (SELECT 1 FROM raw_contacts WHERE contact_id=contacts._id)");
   3068     }
   3069 
   3070     private void rebuildNameLookup(SQLiteDatabase db, boolean rebuildSqliteStats) {
   3071         db.execSQL("DROP INDEX IF EXISTS name_lookup_index");
   3072         insertNameLookup(db);
   3073         createContactsIndexes(db, rebuildSqliteStats);
   3074     }
   3075 
   3076     protected void rebuildSearchIndex() {
   3077         rebuildSearchIndex(getWritableDatabase(), true);
   3078     }
   3079 
   3080     private void rebuildSearchIndex(SQLiteDatabase db, boolean rebuildSqliteStats) {
   3081         createSearchIndexTable(db, rebuildSqliteStats);
   3082         setProperty(db, SearchIndexManager.PROPERTY_SEARCH_INDEX_VERSION, "0");
   3083     }
   3084 
   3085     /**
   3086      * Checks whether the current ICU code version matches that used to build
   3087      * the locale specific data in the ContactsDB.
   3088      */
   3089     public boolean needsToUpdateLocaleData(Locale locale) {
   3090         final String dbLocale = getProperty(DbProperties.LOCALE, "");
   3091         if (!dbLocale.equals(locale.toString())) {
   3092             return true;
   3093         }
   3094         final String curICUVersion = ICU.getIcuVersion();
   3095         final String dbICUVersion = getProperty(DbProperties.ICU_VERSION,
   3096                 "(unknown)");
   3097         if (!curICUVersion.equals(dbICUVersion)) {
   3098             Log.i(TAG, "ICU version has changed. Current version is "
   3099                     + curICUVersion + "; DB was built with " + dbICUVersion);
   3100             return true;
   3101         }
   3102         return false;
   3103     }
   3104 
   3105     private void upgradeLocaleData(SQLiteDatabase db, boolean rebuildSqliteStats) {
   3106         final Locale locale = Locale.getDefault();
   3107         Log.i(TAG, "Upgrading locale data for " + locale
   3108                 + " (ICU v" + ICU.getIcuVersion() + ")");
   3109         final long start = SystemClock.elapsedRealtime();
   3110         initializeCache(db);
   3111         rebuildLocaleData(db, locale, rebuildSqliteStats);
   3112         Log.i(TAG, "Locale update completed in " + (SystemClock.elapsedRealtime() - start) + "ms");
   3113     }
   3114 
   3115     private void rebuildLocaleData(SQLiteDatabase db, Locale locale,
   3116             boolean rebuildSqliteStats) {
   3117         db.execSQL("DROP INDEX raw_contact_sort_key1_index");
   3118         db.execSQL("DROP INDEX raw_contact_sort_key2_index");
   3119         db.execSQL("DROP INDEX IF EXISTS name_lookup_index");
   3120 
   3121         loadNicknameLookupTable(db);
   3122         insertNameLookup(db);
   3123         rebuildSortKeys(db);
   3124         createContactsIndexes(db, rebuildSqliteStats);
   3125 
   3126         FastScrollingIndexCache.getInstance(mContext).invalidate();
   3127         // Update the ICU version used to generate the locale derived data
   3128         // so we can tell when we need to rebuild with new ICU versions.
   3129         setProperty(db, DbProperties.ICU_VERSION, ICU.getIcuVersion());
   3130         setProperty(db, DbProperties.LOCALE, locale.toString());
   3131     }
   3132 
   3133     /**
   3134      * Regenerates all locale-sensitive data if needed:
   3135      * nickname_lookup, name_lookup and sort keys. Invalidates the fast
   3136      * scrolling index cache.
   3137      */
   3138     public void setLocale(Locale locale) {
   3139         if (!needsToUpdateLocaleData(locale)) {
   3140             return;
   3141         }
   3142         Log.i(TAG, "Switching to locale " + locale
   3143                 + " (ICU v" + ICU.getIcuVersion() + ")");
   3144 
   3145         final long start = SystemClock.elapsedRealtime();
   3146         SQLiteDatabase db = getWritableDatabase();
   3147         db.setLocale(locale);
   3148         db.beginTransaction();
   3149         try {
   3150             rebuildLocaleData(db, locale, true);
   3151             db.setTransactionSuccessful();
   3152         } finally {
   3153             db.endTransaction();
   3154         }
   3155 
   3156         Log.i(TAG, "Locale change completed in " + (SystemClock.elapsedRealtime() - start) + "ms");
   3157     }
   3158 
   3159     /**
   3160      * Regenerates sort keys for all contacts.
   3161      */
   3162     private void rebuildSortKeys(SQLiteDatabase db) {
   3163         Cursor cursor = db.query(Tables.RAW_CONTACTS, new String[]{RawContacts._ID},
   3164                 null, null, null, null, null);
   3165         try {
   3166             while (cursor.moveToNext()) {
   3167                 long rawContactId = cursor.getLong(0);
   3168                 updateRawContactDisplayName(db, rawContactId);
   3169             }
   3170         } finally {
   3171             cursor.close();
   3172         }
   3173     }
   3174 
   3175     private void insertNameLookup(SQLiteDatabase db) {
   3176         db.execSQL("DELETE FROM " + Tables.NAME_LOOKUP);
   3177 
   3178         SQLiteStatement nameLookupInsert = db.compileStatement(
   3179                 "INSERT OR IGNORE INTO " + Tables.NAME_LOOKUP + "("
   3180                         + NameLookupColumns.RAW_CONTACT_ID + ","
   3181                         + NameLookupColumns.DATA_ID + ","
   3182                         + NameLookupColumns.NAME_TYPE + ","
   3183                         + NameLookupColumns.NORMALIZED_NAME +
   3184                 ") VALUES (?,?,?,?)");
   3185 
   3186         try {
   3187             insertStructuredNameLookup(db, nameLookupInsert);
   3188             insertEmailLookup(db, nameLookupInsert);
   3189             insertNicknameLookup(db, nameLookupInsert);
   3190         } finally {
   3191             nameLookupInsert.close();
   3192         }
   3193     }
   3194 
   3195     private static final class StructuredNameQuery {
   3196         public static final String TABLE = Tables.DATA;
   3197 
   3198         public static final String SELECTION =
   3199                 DataColumns.MIMETYPE_ID + "=? AND " + Data.DATA1 + " NOT NULL";
   3200 
   3201         public static final String COLUMNS[] = {
   3202                 StructuredName._ID,
   3203                 StructuredName.RAW_CONTACT_ID,
   3204                 StructuredName.DISPLAY_NAME,
   3205         };
   3206 
   3207         public static final int ID = 0;
   3208         public static final int RAW_CONTACT_ID = 1;
   3209         public static final int DISPLAY_NAME = 2;
   3210     }
   3211 
   3212     private class StructuredNameLookupBuilder extends NameLookupBuilder {
   3213 
   3214         private final SQLiteStatement mNameLookupInsert;
   3215         private final CommonNicknameCache mCommonNicknameCache;
   3216 
   3217         public StructuredNameLookupBuilder(NameSplitter splitter,
   3218                 CommonNicknameCache commonNicknameCache, SQLiteStatement nameLookupInsert) {
   3219             super(splitter);
   3220             this.mCommonNicknameCache = commonNicknameCache;
   3221             this.mNameLookupInsert = nameLookupInsert;
   3222         }
   3223 
   3224         @Override
   3225         protected void insertNameLookup(long rawContactId, long dataId, int lookupType,
   3226                 String name) {
   3227             if (!TextUtils.isEmpty(name)) {
   3228                 ContactsDatabaseHelper.this.insertNormalizedNameLookup(mNameLookupInsert,
   3229                         rawContactId, dataId, lookupType, name);
   3230             }
   3231         }
   3232 
   3233         @Override
   3234         protected String[] getCommonNicknameClusters(String normalizedName) {
   3235             return mCommonNicknameCache.getCommonNicknameClusters(normalizedName);
   3236         }
   3237     }
   3238 
   3239     /**
   3240      * Inserts name lookup rows for all structured names in the database.
   3241      */
   3242     private void insertStructuredNameLookup(SQLiteDatabase db, SQLiteStatement nameLookupInsert) {
   3243         NameSplitter nameSplitter = createNameSplitter();
   3244         NameLookupBuilder nameLookupBuilder = new StructuredNameLookupBuilder(nameSplitter,
   3245                 new CommonNicknameCache(db), nameLookupInsert);
   3246         final long mimeTypeId = lookupMimeTypeId(db, StructuredName.CONTENT_ITEM_TYPE);
   3247         Cursor cursor = db.query(StructuredNameQuery.TABLE, StructuredNameQuery.COLUMNS,
   3248                 StructuredNameQuery.SELECTION, new String[] {String.valueOf(mimeTypeId)},
   3249                 null, null, null);
   3250         try {
   3251             while (cursor.moveToNext()) {
   3252                 long dataId = cursor.getLong(StructuredNameQuery.ID);
   3253                 long rawContactId = cursor.getLong(StructuredNameQuery.RAW_CONTACT_ID);
   3254                 String name = cursor.getString(StructuredNameQuery.DISPLAY_NAME);
   3255                 int fullNameStyle = nameSplitter.guessFullNameStyle(name);
   3256                 fullNameStyle = nameSplitter.getAdjustedFullNameStyle(fullNameStyle);
   3257                 nameLookupBuilder.insertNameLookup(rawContactId, dataId, name, fullNameStyle);
   3258             }
   3259         } finally {
   3260             cursor.close();
   3261         }
   3262     }
   3263 
   3264     private static final class OrganizationQuery {
   3265         public static final String TABLE = Tables.DATA;
   3266 
   3267         public static final String SELECTION =
   3268                 DataColumns.MIMETYPE_ID + "=? AND " + Data.DATA1 + " NOT NULL";
   3269 
   3270         public static final String COLUMNS[] = {
   3271                 Organization._ID,
   3272                 Organization.RAW_CONTACT_ID,
   3273                 Organization.COMPANY,
   3274                 Organization.TITLE,
   3275         };
   3276 
   3277         public static final int ID = 0;
   3278         public static final int RAW_CONTACT_ID = 1;
   3279         public static final int COMPANY = 2;
   3280         public static final int TITLE = 3;
   3281     }
   3282 
   3283     private static final class EmailQuery {
   3284         public static final String TABLE = Tables.DATA;
   3285 
   3286         public static final String SELECTION =
   3287                 DataColumns.MIMETYPE_ID + "=? AND " + Data.DATA1 + " NOT NULL";
   3288 
   3289         public static final String COLUMNS[] = {
   3290                 Email._ID,
   3291                 Email.RAW_CONTACT_ID,
   3292                 Email.ADDRESS,
   3293         };
   3294 
   3295         public static final int ID = 0;
   3296         public static final int RAW_CONTACT_ID = 1;
   3297         public static final int ADDRESS = 2;
   3298     }
   3299 
   3300     /**
   3301      * Inserts name lookup rows for all email addresses in the database.
   3302      */
   3303     private void insertEmailLookup(SQLiteDatabase db, SQLiteStatement nameLookupInsert) {
   3304         final long mimeTypeId = lookupMimeTypeId(db, Email.CONTENT_ITEM_TYPE);
   3305         Cursor cursor = db.query(EmailQuery.TABLE, EmailQuery.COLUMNS,
   3306                 EmailQuery.SELECTION, new String[] {String.valueOf(mimeTypeId)},
   3307                 null, null, null);
   3308         try {
   3309             while (cursor.moveToNext()) {
   3310                 long dataId = cursor.getLong(EmailQuery.ID);
   3311                 long rawContactId = cursor.getLong(EmailQuery.RAW_CONTACT_ID);
   3312                 String address = cursor.getString(EmailQuery.ADDRESS);
   3313                 address = extractHandleFromEmailAddress(address);
   3314                 insertNameLookup(nameLookupInsert, rawContactId, dataId,
   3315                         NameLookupType.EMAIL_BASED_NICKNAME, address);
   3316             }
   3317         } finally {
   3318             cursor.close();
   3319         }
   3320     }
   3321 
   3322     private static final class NicknameQuery {
   3323         public static final String TABLE = Tables.DATA;
   3324 
   3325         public static final String SELECTION =
   3326                 DataColumns.MIMETYPE_ID + "=? AND " + Data.DATA1 + " NOT NULL";
   3327 
   3328         public static final String COLUMNS[] = {
   3329                 Nickname._ID,
   3330                 Nickname.RAW_CONTACT_ID,
   3331                 Nickname.NAME,
   3332         };
   3333 
   3334         public static final int ID = 0;
   3335         public static final int RAW_CONTACT_ID = 1;
   3336         public static final int NAME = 2;
   3337     }
   3338 
   3339     /**
   3340      * Inserts name lookup rows for all nicknames in the database.
   3341      */
   3342     private void insertNicknameLookup(SQLiteDatabase db, SQLiteStatement nameLookupInsert) {
   3343         final long mimeTypeId = lookupMimeTypeId(db, Nickname.CONTENT_ITEM_TYPE);
   3344         Cursor cursor = db.query(NicknameQuery.TABLE, NicknameQuery.COLUMNS,
   3345                 NicknameQuery.SELECTION, new String[] {String.valueOf(mimeTypeId)},
   3346                 null, null, null);
   3347         try {
   3348             while (cursor.moveToNext()) {
   3349                 long dataId = cursor.getLong(NicknameQuery.ID);
   3350                 long rawContactId = cursor.getLong(NicknameQuery.RAW_CONTACT_ID);
   3351                 String nickname = cursor.getString(NicknameQuery.NAME);
   3352                 insertNameLookup(nameLookupInsert, rawContactId, dataId,
   3353                         NameLookupType.NICKNAME, nickname);
   3354             }
   3355         } finally {
   3356             cursor.close();
   3357         }
   3358     }
   3359 
   3360     /**
   3361      * Inserts a record in the {@link Tables#NAME_LOOKUP} table.
   3362      */
   3363     public void insertNameLookup(SQLiteStatement stmt, long rawContactId, long dataId,
   3364             int lookupType, String name) {
   3365         if (TextUtils.isEmpty(name)) {
   3366             return;
   3367         }
   3368 
   3369         String normalized = NameNormalizer.normalize(name);
   3370         if (TextUtils.isEmpty(normalized)) {
   3371             return;
   3372         }
   3373 
   3374         insertNormalizedNameLookup(stmt, rawContactId, dataId, lookupType, normalized);
   3375     }
   3376 
   3377     private void insertNormalizedNameLookup(SQLiteStatement stmt, long rawContactId, long dataId,
   3378             int lookupType, String normalizedName) {
   3379         stmt.bindLong(1, rawContactId);
   3380         stmt.bindLong(2, dataId);
   3381         stmt.bindLong(3, lookupType);
   3382         stmt.bindString(4, normalizedName);
   3383         stmt.executeInsert();
   3384     }
   3385 
   3386     /**
   3387      * Changing the VISIBLE bit from a field on both RawContacts and Contacts to a separate table.
   3388      */
   3389     private void upgradeToVersion401(SQLiteDatabase db) {
   3390         db.execSQL("CREATE TABLE " + Tables.VISIBLE_CONTACTS + " (" +
   3391                 Contacts._ID + " INTEGER PRIMARY KEY" +
   3392         ");");
   3393         db.execSQL("INSERT INTO " + Tables.VISIBLE_CONTACTS +
   3394                 " SELECT " + Contacts._ID +
   3395                 " FROM " + Tables.CONTACTS +
   3396                 " WHERE " + Contacts.IN_VISIBLE_GROUP + "!=0");
   3397         db.execSQL("DROP INDEX contacts_visible_index");
   3398     }
   3399 
   3400     /**
   3401      * Introducing a new table: directories.
   3402      */
   3403     private void upgradeToVersion402(SQLiteDatabase db) {
   3404         createDirectoriesTable(db);
   3405     }
   3406 
   3407     private void upgradeToVersion403(SQLiteDatabase db) {
   3408         db.execSQL("DROP TABLE IF EXISTS directories;");
   3409         createDirectoriesTable(db);
   3410 
   3411         db.execSQL("ALTER TABLE raw_contacts"
   3412                 + " ADD raw_contact_is_read_only INTEGER NOT NULL DEFAULT 0;");
   3413 
   3414         db.execSQL("ALTER TABLE data"
   3