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