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