ContactsDatabaseHelper.java revision ea7be853d5b68b05f44c7d937ce9498f3a0e6e34
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 = 1107; 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.POST_DIAL_DIGITS + " TEXT NOT NULL DEFAULT ''," + 1560 Calls.DATE + " INTEGER," + 1561 Calls.DURATION + " INTEGER," + 1562 Calls.DATA_USAGE + " INTEGER," + 1563 Calls.TYPE + " INTEGER," + 1564 Calls.FEATURES + " INTEGER NOT NULL DEFAULT 0," + 1565 Calls.PHONE_ACCOUNT_COMPONENT_NAME + " TEXT," + 1566 Calls.PHONE_ACCOUNT_ID + " TEXT," + 1567 Calls.PHONE_ACCOUNT_ADDRESS + " TEXT," + 1568 Calls.PHONE_ACCOUNT_HIDDEN + " INTEGER NOT NULL DEFAULT 0," + 1569 Calls.SUB_ID + " INTEGER DEFAULT -1," + 1570 Calls.NEW + " INTEGER," + 1571 Calls.CACHED_NAME + " TEXT," + 1572 Calls.CACHED_NUMBER_TYPE + " INTEGER," + 1573 Calls.CACHED_NUMBER_LABEL + " TEXT," + 1574 Calls.COUNTRY_ISO + " TEXT," + 1575 Calls.VOICEMAIL_URI + " TEXT," + 1576 Calls.IS_READ + " INTEGER," + 1577 Calls.GEOCODED_LOCATION + " TEXT," + 1578 Calls.CACHED_LOOKUP_URI + " TEXT," + 1579 Calls.CACHED_MATCHED_NUMBER + " TEXT," + 1580 Calls.CACHED_NORMALIZED_NUMBER + " TEXT," + 1581 Calls.CACHED_PHOTO_ID + " INTEGER NOT NULL DEFAULT 0," + 1582 Calls.CACHED_PHOTO_URI + " TEXT," + 1583 Calls.CACHED_FORMATTED_NUMBER + " TEXT," + 1584 Voicemails._DATA + " TEXT," + 1585 Voicemails.HAS_CONTENT + " INTEGER," + 1586 Voicemails.MIME_TYPE + " TEXT," + 1587 Voicemails.SOURCE_DATA + " TEXT," + 1588 Voicemails.SOURCE_PACKAGE + " TEXT," + 1589 Voicemails.TRANSCRIPTION + " TEXT," + 1590 Voicemails.STATE + " INTEGER," + 1591 Voicemails.DIRTY + " INTEGER NOT NULL DEFAULT 0," + 1592 Voicemails.DELETED + " INTEGER NOT NULL DEFAULT 0" + 1593 ");"); 1594 1595 // Voicemail source status table. 1596 db.execSQL("CREATE TABLE " + Tables.VOICEMAIL_STATUS + " (" + 1597 VoicemailContract.Status._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 1598 VoicemailContract.Status.SOURCE_PACKAGE + " TEXT UNIQUE NOT NULL," + 1599 VoicemailContract.Status.PHONE_ACCOUNT_COMPONENT_NAME + " TEXT," + 1600 VoicemailContract.Status.PHONE_ACCOUNT_ID + " TEXT," + 1601 VoicemailContract.Status.SETTINGS_URI + " TEXT," + 1602 VoicemailContract.Status.VOICEMAIL_ACCESS_URI + " TEXT," + 1603 VoicemailContract.Status.CONFIGURATION_STATE + " INTEGER," + 1604 VoicemailContract.Status.DATA_CHANNEL_STATE + " INTEGER," + 1605 VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE + " INTEGER" + 1606 ");"); 1607 1608 db.execSQL("CREATE TABLE " + Tables.STATUS_UPDATES + " (" + 1609 StatusUpdatesColumns.DATA_ID + " INTEGER PRIMARY KEY REFERENCES data(_id)," + 1610 StatusUpdates.STATUS + " TEXT," + 1611 StatusUpdates.STATUS_TIMESTAMP + " INTEGER," + 1612 StatusUpdates.STATUS_RES_PACKAGE + " TEXT, " + 1613 StatusUpdates.STATUS_LABEL + " INTEGER, " + 1614 StatusUpdates.STATUS_ICON + " INTEGER" + 1615 ");"); 1616 1617 createDirectoriesTable(db); 1618 createSearchIndexTable(db, false /* we build stats table later */); 1619 1620 db.execSQL("CREATE TABLE " + Tables.DATA_USAGE_STAT + "(" + 1621 DataUsageStatColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + 1622 DataUsageStatColumns.DATA_ID + " INTEGER NOT NULL, " + 1623 DataUsageStatColumns.USAGE_TYPE_INT + " INTEGER NOT NULL DEFAULT 0, " + 1624 DataUsageStatColumns.TIMES_USED + " INTEGER NOT NULL DEFAULT 0, " + 1625 DataUsageStatColumns.LAST_TIME_USED + " INTERGER NOT NULL DEFAULT 0, " + 1626 "FOREIGN KEY(" + DataUsageStatColumns.DATA_ID + ") REFERENCES " 1627 + Tables.DATA + "(" + Data._ID + ")" + 1628 ");"); 1629 db.execSQL("CREATE UNIQUE INDEX data_usage_stat_index ON " + 1630 Tables.DATA_USAGE_STAT + " (" + 1631 DataUsageStatColumns.DATA_ID + ", " + 1632 DataUsageStatColumns.USAGE_TYPE_INT + 1633 ");"); 1634 1635 db.execSQL("CREATE TABLE IF NOT EXISTS " 1636 + Tables.METADATA_SYNC + " (" + 1637 MetadataSync._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 1638 MetadataSync.RAW_CONTACT_BACKUP_ID + " TEXT NOT NULL," + 1639 MetadataSyncColumns.ACCOUNT_ID + " INTEGER NOT NULL," + 1640 MetadataSync.DATA + " TEXT," + 1641 MetadataSync.DELETED + " INTEGER NOT NULL DEFAULT 0);"); 1642 1643 db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS metadata_sync_index ON " + 1644 Tables.METADATA_SYNC + " (" + 1645 MetadataSync.RAW_CONTACT_BACKUP_ID + ", " + 1646 MetadataSyncColumns.ACCOUNT_ID +");"); 1647 1648 db.execSQL("CREATE TABLE " + Tables.PRE_AUTHORIZED_URIS + " ("+ 1649 PreAuthorizedUris._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + 1650 PreAuthorizedUris.URI + " STRING NOT NULL, " + 1651 PreAuthorizedUris.EXPIRATION + " INTEGER NOT NULL DEFAULT 0);"); 1652 1653 db.execSQL("CREATE TABLE IF NOT EXISTS " 1654 + Tables.METADATA_SYNC_STATE + " (" + 1655 MetadataSyncState._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 1656 MetadataSyncStateColumns.ACCOUNT_ID + " INTEGER NOT NULL," + 1657 MetadataSyncState.STATE + " BLOB);"); 1658 1659 db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS metadata_sync_state_index ON " + 1660 Tables.METADATA_SYNC_STATE + " (" + 1661 MetadataSyncColumns.ACCOUNT_ID +");"); 1662 1663 // When adding new tables, be sure to also add size-estimates in updateSqliteStats 1664 createContactsViews(db); 1665 createGroupsView(db); 1666 createContactsTriggers(db); 1667 createContactsIndexes(db, false /* we build stats table later */); 1668 1669 loadNicknameLookupTable(db); 1670 1671 // Set sequence starts. 1672 initializeAutoIncrementSequences(db); 1673 1674 // Add the legacy API support views, etc. 1675 LegacyApiSupport.createDatabase(db); 1676 1677 if (mDatabaseOptimizationEnabled) { 1678 // This will create a sqlite_stat1 table that is used for query optimization 1679 db.execSQL("ANALYZE;"); 1680 1681 updateSqliteStats(db); 1682 } 1683 1684 ContentResolver.requestSync(null /* all accounts */, 1685 ContactsContract.AUTHORITY, new Bundle()); 1686 1687 // Only send broadcasts for regular contacts db. 1688 if (dbForProfile() == 0) { 1689 final Intent dbCreatedIntent = new Intent( 1690 ContactsContract.Intents.CONTACTS_DATABASE_CREATED); 1691 dbCreatedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 1692 mContext.sendBroadcast(dbCreatedIntent, android.Manifest.permission.READ_CONTACTS); 1693 } 1694 } 1695 1696 protected void initializeAutoIncrementSequences(SQLiteDatabase db) { 1697 // Default implementation does nothing. 1698 } 1699 1700 private void createDirectoriesTable(SQLiteDatabase db) { 1701 db.execSQL("CREATE TABLE " + Tables.DIRECTORIES + "(" + 1702 Directory._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 1703 Directory.PACKAGE_NAME + " TEXT NOT NULL," + 1704 Directory.DIRECTORY_AUTHORITY + " TEXT NOT NULL," + 1705 Directory.TYPE_RESOURCE_ID + " INTEGER," + 1706 DirectoryColumns.TYPE_RESOURCE_NAME + " TEXT," + 1707 Directory.ACCOUNT_TYPE + " TEXT," + 1708 Directory.ACCOUNT_NAME + " TEXT," + 1709 Directory.DISPLAY_NAME + " TEXT, " + 1710 Directory.EXPORT_SUPPORT + " INTEGER NOT NULL" + 1711 " DEFAULT " + Directory.EXPORT_SUPPORT_NONE + "," + 1712 Directory.SHORTCUT_SUPPORT + " INTEGER NOT NULL" + 1713 " DEFAULT " + Directory.SHORTCUT_SUPPORT_NONE + "," + 1714 Directory.PHOTO_SUPPORT + " INTEGER NOT NULL" + 1715 " DEFAULT " + Directory.PHOTO_SUPPORT_NONE + 1716 ");"); 1717 1718 // Trigger a full scan of directories in the system 1719 setProperty(db, DbProperties.DIRECTORY_SCAN_COMPLETE, "0"); 1720 } 1721 1722 public void createSearchIndexTable(SQLiteDatabase db, boolean rebuildSqliteStats) { 1723 db.beginTransaction(); 1724 try { 1725 db.execSQL("DROP TABLE IF EXISTS " + Tables.SEARCH_INDEX); 1726 db.execSQL("CREATE VIRTUAL TABLE " + Tables.SEARCH_INDEX 1727 + " USING FTS4 (" 1728 + SearchIndexColumns.CONTACT_ID + " INTEGER REFERENCES contacts(_id) NOT NULL," 1729 + SearchIndexColumns.CONTENT + " TEXT, " 1730 + SearchIndexColumns.NAME + " TEXT, " 1731 + SearchIndexColumns.TOKENS + " TEXT" 1732 + ")"); 1733 if (rebuildSqliteStats) { 1734 updateSqliteStats(db); 1735 } 1736 db.setTransactionSuccessful(); 1737 } finally { 1738 db.endTransaction(); 1739 } 1740 } 1741 1742 private void createContactsTriggers(SQLiteDatabase db) { 1743 1744 // Automatically delete Data rows when a raw contact is deleted. 1745 db.execSQL("DROP TRIGGER IF EXISTS " + Tables.RAW_CONTACTS + "_deleted;"); 1746 db.execSQL("CREATE TRIGGER " + Tables.RAW_CONTACTS + "_deleted " 1747 + " BEFORE DELETE ON " + Tables.RAW_CONTACTS 1748 + " BEGIN " 1749 + " DELETE FROM " + Tables.DATA 1750 + " WHERE " + Data.RAW_CONTACT_ID 1751 + "=OLD." + RawContacts._ID + ";" 1752 + " DELETE FROM " + Tables.AGGREGATION_EXCEPTIONS 1753 + " WHERE " + AggregationExceptions.RAW_CONTACT_ID1 1754 + "=OLD." + RawContacts._ID 1755 + " OR " + AggregationExceptions.RAW_CONTACT_ID2 1756 + "=OLD." + RawContacts._ID + ";" 1757 + " DELETE FROM " + Tables.VISIBLE_CONTACTS 1758 + " WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID 1759 + " AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS 1760 + " WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID 1761 + " )=1;" 1762 + " DELETE FROM " + Tables.DEFAULT_DIRECTORY 1763 + " WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID 1764 + " AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS 1765 + " WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID 1766 + " )=1;" 1767 + " DELETE FROM " + Tables.CONTACTS 1768 + " WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID 1769 + " AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS 1770 + " WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID 1771 + " )=1;" 1772 + " END"); 1773 1774 1775 db.execSQL("DROP TRIGGER IF EXISTS contacts_times_contacted;"); 1776 db.execSQL("DROP TRIGGER IF EXISTS raw_contacts_times_contacted;"); 1777 1778 // Triggers that update {@link RawContacts#VERSION} when the contact is marked for deletion 1779 // or any time a data row is inserted, updated or deleted. 1780 db.execSQL("DROP TRIGGER IF EXISTS " + Tables.RAW_CONTACTS + "_marked_deleted;"); 1781 db.execSQL("CREATE TRIGGER " + Tables.RAW_CONTACTS + "_marked_deleted " 1782 + " AFTER UPDATE ON " + Tables.RAW_CONTACTS 1783 + " BEGIN " 1784 + " UPDATE " + Tables.RAW_CONTACTS 1785 + " SET " 1786 + RawContacts.VERSION + "=OLD." + RawContacts.VERSION + "+1 " 1787 + " WHERE " + RawContacts._ID + "=OLD." + RawContacts._ID 1788 + " AND NEW." + RawContacts.DELETED + "!= OLD." + RawContacts.DELETED + ";" 1789 + " END"); 1790 1791 db.execSQL("DROP TRIGGER IF EXISTS " + Tables.DATA + "_updated;"); 1792 db.execSQL("CREATE TRIGGER " + Tables.DATA + "_updated AFTER UPDATE ON " + Tables.DATA 1793 + " BEGIN " 1794 + " UPDATE " + Tables.DATA 1795 + " SET " + Data.DATA_VERSION + "=OLD." + Data.DATA_VERSION + "+1 " 1796 + " WHERE " + Data._ID + "=OLD." + Data._ID + ";" 1797 + " UPDATE " + Tables.RAW_CONTACTS 1798 + " SET " + RawContacts.VERSION + "=" + RawContacts.VERSION + "+1 " 1799 + " WHERE " + RawContacts._ID + "=OLD." + Data.RAW_CONTACT_ID + ";" 1800 + " END"); 1801 1802 db.execSQL("DROP TRIGGER IF EXISTS " + Tables.DATA + "_deleted;"); 1803 db.execSQL("CREATE TRIGGER " + Tables.DATA + "_deleted BEFORE DELETE ON " + Tables.DATA 1804 + " BEGIN " 1805 + " UPDATE " + Tables.RAW_CONTACTS 1806 + " SET " + RawContacts.VERSION + "=" + RawContacts.VERSION + "+1 " 1807 + " WHERE " + RawContacts._ID + "=OLD." + Data.RAW_CONTACT_ID + ";" 1808 + " DELETE FROM " + Tables.PHONE_LOOKUP 1809 + " WHERE " + PhoneLookupColumns.DATA_ID + "=OLD." + Data._ID + ";" 1810 + " DELETE FROM " + Tables.STATUS_UPDATES 1811 + " WHERE " + StatusUpdatesColumns.DATA_ID + "=OLD." + Data._ID + ";" 1812 + " DELETE FROM " + Tables.NAME_LOOKUP 1813 + " WHERE " + NameLookupColumns.DATA_ID + "=OLD." + Data._ID + ";" 1814 + " END"); 1815 1816 1817 db.execSQL("DROP TRIGGER IF EXISTS " + Tables.GROUPS + "_updated1;"); 1818 db.execSQL("CREATE TRIGGER " + Tables.GROUPS + "_updated1 " 1819 + " AFTER UPDATE ON " + Tables.GROUPS 1820 + " BEGIN " 1821 + " UPDATE " + Tables.GROUPS 1822 + " SET " 1823 + Groups.VERSION + "=OLD." + Groups.VERSION + "+1" 1824 + " WHERE " + Groups._ID + "=OLD." + Groups._ID + ";" 1825 + " END"); 1826 1827 // Update DEFAULT_FILTER table per AUTO_ADD column update, see upgradeToVersion411. 1828 final String insertContactsWithoutAccount = ( 1829 " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY + 1830 " SELECT " + RawContacts.CONTACT_ID + 1831 " FROM " + Tables.RAW_CONTACTS + 1832 " WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID + 1833 "=" + Clauses.LOCAL_ACCOUNT_ID + ";"); 1834 1835 final String insertContactsWithAccountNoDefaultGroup = ( 1836 " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY + 1837 " SELECT " + RawContacts.CONTACT_ID + 1838 " FROM " + Tables.RAW_CONTACTS + 1839 " WHERE NOT EXISTS" + 1840 " (SELECT " + Groups._ID + 1841 " FROM " + Tables.GROUPS + 1842 " WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID + " = " + 1843 GroupsColumns.CONCRETE_ACCOUNT_ID + 1844 " AND " + Groups.AUTO_ADD + " != 0" + ");"); 1845 1846 final String insertContactsWithAccountDefaultGroup = ( 1847 " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY + 1848 " SELECT " + RawContacts.CONTACT_ID + 1849 " FROM " + Tables.RAW_CONTACTS + 1850 " JOIN " + Tables.DATA + 1851 " ON (" + RawContactsColumns.CONCRETE_ID + "=" + 1852 Data.RAW_CONTACT_ID + ")" + 1853 " WHERE " + DataColumns.MIMETYPE_ID + "=" + 1854 "(SELECT " + MimetypesColumns._ID + " FROM " + Tables.MIMETYPES + 1855 " WHERE " + MimetypesColumns.MIMETYPE + 1856 "='" + GroupMembership.CONTENT_ITEM_TYPE + "')" + 1857 " AND EXISTS" + 1858 " (SELECT " + Groups._ID + 1859 " FROM " + Tables.GROUPS + 1860 " WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID + " = " + 1861 GroupsColumns.CONCRETE_ACCOUNT_ID + 1862 " AND " + Groups.AUTO_ADD + " != 0" + ");"); 1863 1864 db.execSQL("DROP TRIGGER IF EXISTS " + Tables.GROUPS + "_auto_add_updated1;"); 1865 db.execSQL("CREATE TRIGGER " + Tables.GROUPS + "_auto_add_updated1 " 1866 + " AFTER UPDATE OF " + Groups.AUTO_ADD + " ON " + Tables.GROUPS 1867 + " BEGIN " 1868 + " DELETE FROM " + Tables.DEFAULT_DIRECTORY + ";" 1869 + insertContactsWithoutAccount 1870 + insertContactsWithAccountNoDefaultGroup 1871 + insertContactsWithAccountDefaultGroup 1872 + " END"); 1873 } 1874 1875 private void createContactsIndexes(SQLiteDatabase db, boolean rebuildSqliteStats) { 1876 db.execSQL("DROP INDEX IF EXISTS name_lookup_index"); 1877 db.execSQL("CREATE INDEX name_lookup_index ON " + Tables.NAME_LOOKUP + " (" + 1878 NameLookupColumns.NORMALIZED_NAME + "," + 1879 NameLookupColumns.NAME_TYPE + ", " + 1880 NameLookupColumns.RAW_CONTACT_ID + ", " + 1881 NameLookupColumns.DATA_ID + 1882 ");"); 1883 1884 db.execSQL("DROP INDEX IF EXISTS raw_contact_sort_key1_index"); 1885 db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" + 1886 RawContacts.SORT_KEY_PRIMARY + 1887 ");"); 1888 1889 db.execSQL("DROP INDEX IF EXISTS raw_contact_sort_key2_index"); 1890 db.execSQL("CREATE INDEX raw_contact_sort_key2_index ON " + Tables.RAW_CONTACTS + " (" + 1891 RawContacts.SORT_KEY_ALTERNATIVE + 1892 ");"); 1893 1894 if (rebuildSqliteStats) { 1895 updateSqliteStats(db); 1896 } 1897 } 1898 1899 private void createContactsViews(SQLiteDatabase db) { 1900 db.execSQL("DROP VIEW IF EXISTS " + Views.CONTACTS + ";"); 1901 db.execSQL("DROP VIEW IF EXISTS " + Views.DATA + ";"); 1902 db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_CONTACTS + ";"); 1903 db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_ENTITIES + ";"); 1904 db.execSQL("DROP VIEW IF EXISTS " + Views.ENTITIES + ";"); 1905 db.execSQL("DROP VIEW IF EXISTS " + Views.DATA_USAGE_STAT + ";"); 1906 db.execSQL("DROP VIEW IF EXISTS " + Views.STREAM_ITEMS + ";"); 1907 db.execSQL("DROP VIEW IF EXISTS " + Views.METADATA_SYNC + ";"); 1908 1909 String dataColumns = 1910 Data.IS_PRIMARY + ", " 1911 + Data.IS_SUPER_PRIMARY + ", " 1912 + Data.DATA_VERSION + ", " 1913 + DataColumns.CONCRETE_PACKAGE_ID + "," 1914 + PackagesColumns.PACKAGE + " AS " + Data.RES_PACKAGE + "," 1915 + DataColumns.CONCRETE_MIMETYPE_ID + "," 1916 + MimetypesColumns.MIMETYPE + " AS " + Data.MIMETYPE + ", " 1917 + Data.IS_READ_ONLY + ", " 1918 + Data.DATA1 + ", " 1919 + Data.DATA2 + ", " 1920 + Data.DATA3 + ", " 1921 + Data.DATA4 + ", " 1922 + Data.DATA5 + ", " 1923 + Data.DATA6 + ", " 1924 + Data.DATA7 + ", " 1925 + Data.DATA8 + ", " 1926 + Data.DATA9 + ", " 1927 + Data.DATA10 + ", " 1928 + Data.DATA11 + ", " 1929 + Data.DATA12 + ", " 1930 + Data.DATA13 + ", " 1931 + Data.DATA14 + ", " 1932 + Data.DATA15 + ", " 1933 + Data.CARRIER_PRESENCE + ", " 1934 + Data.SYNC1 + ", " 1935 + Data.SYNC2 + ", " 1936 + Data.SYNC3 + ", " 1937 + Data.SYNC4; 1938 1939 String syncColumns = 1940 RawContactsColumns.CONCRETE_ACCOUNT_ID + "," 1941 + AccountsColumns.CONCRETE_ACCOUNT_NAME + " AS " + RawContacts.ACCOUNT_NAME + "," 1942 + AccountsColumns.CONCRETE_ACCOUNT_TYPE + " AS " + RawContacts.ACCOUNT_TYPE + "," 1943 + AccountsColumns.CONCRETE_DATA_SET + " AS " + RawContacts.DATA_SET + "," 1944 + "(CASE WHEN " + AccountsColumns.CONCRETE_DATA_SET + " IS NULL THEN " 1945 + AccountsColumns.CONCRETE_ACCOUNT_TYPE 1946 + " ELSE " + AccountsColumns.CONCRETE_ACCOUNT_TYPE + "||'/'||" 1947 + AccountsColumns.CONCRETE_DATA_SET + " END) AS " 1948 + RawContacts.ACCOUNT_TYPE_AND_DATA_SET + "," 1949 + RawContactsColumns.CONCRETE_SOURCE_ID + " AS " + RawContacts.SOURCE_ID + "," 1950 + RawContactsColumns.CONCRETE_BACKUP_ID + " AS " + RawContacts.BACKUP_ID + "," 1951 + RawContactsColumns.CONCRETE_VERSION + " AS " + RawContacts.VERSION + "," 1952 + RawContactsColumns.CONCRETE_DIRTY + " AS " + RawContacts.DIRTY + "," 1953 + RawContactsColumns.CONCRETE_SYNC1 + " AS " + RawContacts.SYNC1 + "," 1954 + RawContactsColumns.CONCRETE_SYNC2 + " AS " + RawContacts.SYNC2 + "," 1955 + RawContactsColumns.CONCRETE_SYNC3 + " AS " + RawContacts.SYNC3 + "," 1956 + RawContactsColumns.CONCRETE_SYNC4 + " AS " + RawContacts.SYNC4; 1957 1958 String baseContactColumns = 1959 Contacts.HAS_PHONE_NUMBER + ", " 1960 + Contacts.NAME_RAW_CONTACT_ID + ", " 1961 + Contacts.LOOKUP_KEY + ", " 1962 + Contacts.PHOTO_ID + ", " 1963 + Contacts.PHOTO_FILE_ID + ", " 1964 + "CAST(" + Clauses.CONTACT_VISIBLE + " AS INTEGER) AS " 1965 + Contacts.IN_VISIBLE_GROUP + ", " 1966 + "CAST(" + Clauses.CONTACT_IN_DEFAULT_DIRECTORY + " AS INTEGER) AS " 1967 + Contacts.IN_DEFAULT_DIRECTORY + ", " 1968 + ContactsColumns.LAST_STATUS_UPDATE_ID + ", " 1969 + ContactsColumns.CONCRETE_CONTACT_LAST_UPDATED_TIMESTAMP; 1970 1971 String contactOptionColumns = 1972 ContactsColumns.CONCRETE_CUSTOM_RINGTONE 1973 + " AS " + RawContacts.CUSTOM_RINGTONE + "," 1974 + ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL 1975 + " AS " + RawContacts.SEND_TO_VOICEMAIL + "," 1976 + ContactsColumns.CONCRETE_LAST_TIME_CONTACTED 1977 + " AS " + RawContacts.LAST_TIME_CONTACTED + "," 1978 + ContactsColumns.CONCRETE_TIMES_CONTACTED 1979 + " AS " + RawContacts.TIMES_CONTACTED + "," 1980 + ContactsColumns.CONCRETE_STARRED 1981 + " AS " + RawContacts.STARRED + "," 1982 + ContactsColumns.CONCRETE_PINNED 1983 + " AS " + RawContacts.PINNED; 1984 1985 String contactNameColumns = 1986 "name_raw_contact." + RawContacts.DISPLAY_NAME_SOURCE 1987 + " AS " + Contacts.DISPLAY_NAME_SOURCE + ", " 1988 + "name_raw_contact." + RawContacts.DISPLAY_NAME_PRIMARY 1989 + " AS " + Contacts.DISPLAY_NAME_PRIMARY + ", " 1990 + "name_raw_contact." + RawContacts.DISPLAY_NAME_ALTERNATIVE 1991 + " AS " + Contacts.DISPLAY_NAME_ALTERNATIVE + ", " 1992 + "name_raw_contact." + RawContacts.PHONETIC_NAME 1993 + " AS " + Contacts.PHONETIC_NAME + ", " 1994 + "name_raw_contact." + RawContacts.PHONETIC_NAME_STYLE 1995 + " AS " + Contacts.PHONETIC_NAME_STYLE + ", " 1996 + "name_raw_contact." + RawContacts.SORT_KEY_PRIMARY 1997 + " AS " + Contacts.SORT_KEY_PRIMARY + ", " 1998 + "name_raw_contact." + RawContactsColumns.PHONEBOOK_LABEL_PRIMARY 1999 + " AS " + ContactsColumns.PHONEBOOK_LABEL_PRIMARY + ", " 2000 + "name_raw_contact." + RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY 2001 + " AS " + ContactsColumns.PHONEBOOK_BUCKET_PRIMARY + ", " 2002 + "name_raw_contact." + RawContacts.SORT_KEY_ALTERNATIVE 2003 + " AS " + Contacts.SORT_KEY_ALTERNATIVE + ", " 2004 + "name_raw_contact." + RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE 2005 + " AS " + ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + ", " 2006 + "name_raw_contact." + RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE 2007 + " AS " + ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE; 2008 2009 String dataSelect = "SELECT " 2010 + DataColumns.CONCRETE_ID + " AS " + Data._ID + "," 2011 + Data.HASH_ID + ", " 2012 + Data.RAW_CONTACT_ID + ", " 2013 + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", " 2014 + syncColumns + ", " 2015 + dataColumns + ", " 2016 + contactOptionColumns + ", " 2017 + contactNameColumns + ", " 2018 + baseContactColumns + ", " 2019 + buildDisplayPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID, 2020 Contacts.PHOTO_URI) + ", " 2021 + buildThumbnailPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID, 2022 Contacts.PHOTO_THUMBNAIL_URI) + ", " 2023 + dbForProfile() + " AS " + RawContacts.RAW_CONTACT_IS_USER_PROFILE + ", " 2024 + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID 2025 + " FROM " + Tables.DATA 2026 + " JOIN " + Tables.MIMETYPES + " ON (" 2027 + DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")" 2028 + " JOIN " + Tables.RAW_CONTACTS + " ON (" 2029 + DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")" 2030 + " JOIN " + Tables.ACCOUNTS + " ON (" 2031 + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID 2032 + ")" 2033 + " JOIN " + Tables.CONTACTS + " ON (" 2034 + RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")" 2035 + " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON(" 2036 + Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")" 2037 + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON (" 2038 + DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")" 2039 + " LEFT OUTER JOIN " + Tables.GROUPS + " ON (" 2040 + MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE 2041 + "' AND " + GroupsColumns.CONCRETE_ID + "=" 2042 + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")"; 2043 2044 db.execSQL("CREATE VIEW " + Views.DATA + " AS " + dataSelect); 2045 2046 String rawContactOptionColumns = 2047 RawContacts.CUSTOM_RINGTONE + "," 2048 + RawContacts.SEND_TO_VOICEMAIL + "," 2049 + RawContacts.LAST_TIME_CONTACTED + "," 2050 + RawContacts.TIMES_CONTACTED + "," 2051 + RawContacts.STARRED + "," 2052 + RawContacts.PINNED; 2053 2054 String rawContactsSelect = "SELECT " 2055 + RawContactsColumns.CONCRETE_ID + " AS " + RawContacts._ID + "," 2056 + RawContacts.CONTACT_ID + ", " 2057 + RawContacts.AGGREGATION_MODE + ", " 2058 + RawContacts.RAW_CONTACT_IS_READ_ONLY + ", " 2059 + RawContacts.DELETED + ", " 2060 + RawContactsColumns.CONCRETE_METADATA_DIRTY + ", " 2061 + RawContacts.DISPLAY_NAME_SOURCE + ", " 2062 + RawContacts.DISPLAY_NAME_PRIMARY + ", " 2063 + RawContacts.DISPLAY_NAME_ALTERNATIVE + ", " 2064 + RawContacts.PHONETIC_NAME + ", " 2065 + RawContacts.PHONETIC_NAME_STYLE + ", " 2066 + RawContacts.SORT_KEY_PRIMARY + ", " 2067 + RawContactsColumns.PHONEBOOK_LABEL_PRIMARY + ", " 2068 + RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY + ", " 2069 + RawContacts.SORT_KEY_ALTERNATIVE + ", " 2070 + RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + ", " 2071 + RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE + ", " 2072 + dbForProfile() + " AS " + RawContacts.RAW_CONTACT_IS_USER_PROFILE + ", " 2073 + rawContactOptionColumns + ", " 2074 + syncColumns 2075 + " FROM " + Tables.RAW_CONTACTS 2076 + " JOIN " + Tables.ACCOUNTS + " ON (" 2077 + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID 2078 + ")"; 2079 2080 db.execSQL("CREATE VIEW " + Views.RAW_CONTACTS + " AS " + rawContactsSelect); 2081 2082 String contactsColumns = 2083 ContactsColumns.CONCRETE_CUSTOM_RINGTONE 2084 + " AS " + Contacts.CUSTOM_RINGTONE + ", " 2085 + contactNameColumns + ", " 2086 + baseContactColumns + ", " 2087 + ContactsColumns.CONCRETE_LAST_TIME_CONTACTED 2088 + " AS " + Contacts.LAST_TIME_CONTACTED + ", " 2089 + ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL 2090 + " AS " + Contacts.SEND_TO_VOICEMAIL + ", " 2091 + ContactsColumns.CONCRETE_STARRED 2092 + " AS " + Contacts.STARRED + ", " 2093 + ContactsColumns.CONCRETE_PINNED 2094 + " AS " + Contacts.PINNED + ", " 2095 + ContactsColumns.CONCRETE_TIMES_CONTACTED 2096 + " AS " + Contacts.TIMES_CONTACTED; 2097 2098 String contactsSelect = "SELECT " 2099 + ContactsColumns.CONCRETE_ID + " AS " + Contacts._ID + "," 2100 + contactsColumns + ", " 2101 + buildDisplayPhotoUriAlias(ContactsColumns.CONCRETE_ID, Contacts.PHOTO_URI) + ", " 2102 + buildThumbnailPhotoUriAlias(ContactsColumns.CONCRETE_ID, 2103 Contacts.PHOTO_THUMBNAIL_URI) + ", " 2104 + dbForProfile() + " AS " + Contacts.IS_USER_PROFILE 2105 + " FROM " + Tables.CONTACTS 2106 + " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON(" 2107 + Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")"; 2108 2109 db.execSQL("CREATE VIEW " + Views.CONTACTS + " AS " + contactsSelect); 2110 2111 String rawEntitiesSelect = "SELECT " 2112 + RawContacts.CONTACT_ID + ", " 2113 + RawContactsColumns.CONCRETE_DELETED + " AS " + RawContacts.DELETED + "," 2114 + RawContactsColumns.CONCRETE_METADATA_DIRTY + ", " 2115 + dataColumns + ", " 2116 + syncColumns + ", " 2117 + Data.SYNC1 + ", " 2118 + Data.SYNC2 + ", " 2119 + Data.SYNC3 + ", " 2120 + Data.SYNC4 + ", " 2121 + RawContactsColumns.CONCRETE_ID + " AS " + RawContacts._ID + ", " 2122 + DataColumns.CONCRETE_ID + " AS " + RawContacts.Entity.DATA_ID + "," 2123 + RawContactsColumns.CONCRETE_STARRED + " AS " + RawContacts.STARRED + "," 2124 + dbForProfile() + " AS " + RawContacts.RAW_CONTACT_IS_USER_PROFILE + "," 2125 + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID 2126 + " FROM " + Tables.RAW_CONTACTS 2127 + " JOIN " + Tables.ACCOUNTS + " ON (" 2128 + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID 2129 + ")" 2130 + " LEFT OUTER JOIN " + Tables.DATA + " ON (" 2131 + DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")" 2132 + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON (" 2133 + DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")" 2134 + " LEFT OUTER JOIN " + Tables.MIMETYPES + " ON (" 2135 + DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")" 2136 + " LEFT OUTER JOIN " + Tables.GROUPS + " ON (" 2137 + MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE 2138 + "' AND " + GroupsColumns.CONCRETE_ID + "=" 2139 + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")"; 2140 2141 db.execSQL("CREATE VIEW " + Views.RAW_ENTITIES + " AS " 2142 + rawEntitiesSelect); 2143 2144 String entitiesSelect = "SELECT " 2145 + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + Contacts._ID + ", " 2146 + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", " 2147 + RawContactsColumns.CONCRETE_DELETED + " AS " + RawContacts.DELETED + "," 2148 + RawContactsColumns.CONCRETE_METADATA_DIRTY + ", " 2149 + dataColumns + ", " 2150 + syncColumns + ", " 2151 + contactsColumns + ", " 2152 + buildDisplayPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID, 2153 Contacts.PHOTO_URI) + ", " 2154 + buildThumbnailPhotoUriAlias(RawContactsColumns.CONCRETE_CONTACT_ID, 2155 Contacts.PHOTO_THUMBNAIL_URI) + ", " 2156 + dbForProfile() + " AS " + Contacts.IS_USER_PROFILE + ", " 2157 + Data.SYNC1 + ", " 2158 + Data.SYNC2 + ", " 2159 + Data.SYNC3 + ", " 2160 + Data.SYNC4 + ", " 2161 + RawContactsColumns.CONCRETE_ID + " AS " + Contacts.Entity.RAW_CONTACT_ID + ", " 2162 + DataColumns.CONCRETE_ID + " AS " + Contacts.Entity.DATA_ID + "," 2163 + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID 2164 + " FROM " + Tables.RAW_CONTACTS 2165 + " JOIN " + Tables.ACCOUNTS + " ON (" 2166 + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID 2167 + ")" 2168 + " JOIN " + Tables.CONTACTS + " ON (" 2169 + RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")" 2170 + " JOIN " + Tables.RAW_CONTACTS + " AS name_raw_contact ON(" 2171 + Contacts.NAME_RAW_CONTACT_ID + "=name_raw_contact." + RawContacts._ID + ")" 2172 + " LEFT OUTER JOIN " + Tables.DATA + " ON (" 2173 + DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")" 2174 + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON (" 2175 + DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")" 2176 + " LEFT OUTER JOIN " + Tables.MIMETYPES + " ON (" 2177 + DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")" 2178 + " LEFT OUTER JOIN " + Tables.GROUPS + " ON (" 2179 + MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE 2180 + "' AND " + GroupsColumns.CONCRETE_ID + "=" 2181 + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")"; 2182 2183 db.execSQL("CREATE VIEW " + Views.ENTITIES + " AS " 2184 + entitiesSelect); 2185 2186 String dataUsageStatSelect = "SELECT " 2187 + DataUsageStatColumns.CONCRETE_ID + " AS " + DataUsageStatColumns._ID + ", " 2188 + DataUsageStatColumns.DATA_ID + ", " 2189 + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", " 2190 + MimetypesColumns.CONCRETE_MIMETYPE + " AS " + Data.MIMETYPE + ", " 2191 + DataUsageStatColumns.USAGE_TYPE_INT + ", " 2192 + DataUsageStatColumns.TIMES_USED + ", " 2193 + DataUsageStatColumns.LAST_TIME_USED 2194 + " FROM " + Tables.DATA_USAGE_STAT 2195 + " JOIN " + Tables.DATA + " ON (" 2196 + DataColumns.CONCRETE_ID + "=" + DataUsageStatColumns.CONCRETE_DATA_ID + ")" 2197 + " JOIN " + Tables.RAW_CONTACTS + " ON (" 2198 + RawContactsColumns.CONCRETE_ID + "=" + DataColumns.CONCRETE_RAW_CONTACT_ID 2199 + " )" 2200 + " JOIN " + Tables.MIMETYPES + " ON (" 2201 + MimetypesColumns.CONCRETE_ID + "=" + DataColumns.CONCRETE_MIMETYPE_ID + ")"; 2202 2203 db.execSQL("CREATE VIEW " + Views.DATA_USAGE_STAT + " AS " + dataUsageStatSelect); 2204 2205 String streamItemSelect = "SELECT " + 2206 StreamItemsColumns.CONCRETE_ID + ", " + 2207 ContactsColumns.CONCRETE_ID + " AS " + StreamItems.CONTACT_ID + ", " + 2208 ContactsColumns.CONCRETE_LOOKUP_KEY + 2209 " AS " + StreamItems.CONTACT_LOOKUP_KEY + ", " + 2210 AccountsColumns.CONCRETE_ACCOUNT_NAME + ", " + 2211 AccountsColumns.CONCRETE_ACCOUNT_TYPE + ", " + 2212 AccountsColumns.CONCRETE_DATA_SET + ", " + 2213 StreamItemsColumns.CONCRETE_RAW_CONTACT_ID + 2214 " as " + StreamItems.RAW_CONTACT_ID + ", " + 2215 RawContactsColumns.CONCRETE_SOURCE_ID + 2216 " as " + StreamItems.RAW_CONTACT_SOURCE_ID + ", " + 2217 StreamItemsColumns.CONCRETE_PACKAGE + ", " + 2218 StreamItemsColumns.CONCRETE_ICON + ", " + 2219 StreamItemsColumns.CONCRETE_LABEL + ", " + 2220 StreamItemsColumns.CONCRETE_TEXT + ", " + 2221 StreamItemsColumns.CONCRETE_TIMESTAMP + ", " + 2222 StreamItemsColumns.CONCRETE_COMMENTS + ", " + 2223 StreamItemsColumns.CONCRETE_SYNC1 + ", " + 2224 StreamItemsColumns.CONCRETE_SYNC2 + ", " + 2225 StreamItemsColumns.CONCRETE_SYNC3 + ", " + 2226 StreamItemsColumns.CONCRETE_SYNC4 + 2227 " FROM " + Tables.STREAM_ITEMS 2228 + " JOIN " + Tables.RAW_CONTACTS + " ON (" 2229 + StreamItemsColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID 2230 + ")" 2231 + " JOIN " + Tables.ACCOUNTS + " ON (" 2232 + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID 2233 + ")" 2234 + " JOIN " + Tables.CONTACTS + " ON (" 2235 + RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")"; 2236 2237 db.execSQL("CREATE VIEW " + Views.STREAM_ITEMS + " AS " + streamItemSelect); 2238 2239 String metadataSyncSelect = "SELECT " + 2240 MetadataSyncColumns.CONCRETE_ID + ", " + 2241 MetadataSync.RAW_CONTACT_BACKUP_ID + ", " + 2242 AccountsColumns.ACCOUNT_NAME + ", " + 2243 AccountsColumns.ACCOUNT_TYPE + ", " + 2244 AccountsColumns.DATA_SET + ", " + 2245 MetadataSync.DATA + ", " + 2246 MetadataSync.DELETED + 2247 " FROM " + Tables.METADATA_SYNC 2248 + " JOIN " + Tables.ACCOUNTS + " ON (" 2249 + MetadataSyncColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID 2250 + ")"; 2251 2252 db.execSQL("CREATE VIEW " + Views.METADATA_SYNC + " AS " + metadataSyncSelect); 2253 2254 String metadataSyncStateSelect = "SELECT " + 2255 MetadataSyncStateColumns.CONCRETE_ID + ", " + 2256 AccountsColumns.ACCOUNT_NAME + ", " + 2257 AccountsColumns.ACCOUNT_TYPE + ", " + 2258 AccountsColumns.DATA_SET + ", " + 2259 MetadataSyncState.STATE + 2260 " FROM " + Tables.METADATA_SYNC_STATE 2261 + " JOIN " + Tables.ACCOUNTS + " ON (" 2262 + MetadataSyncStateColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID 2263 + ")"; 2264 2265 db.execSQL("CREATE VIEW " + Views.METADATA_SYNC_STATE + " AS " + metadataSyncStateSelect); 2266 } 2267 2268 private static String buildDisplayPhotoUriAlias(String contactIdColumn, String alias) { 2269 return "(CASE WHEN " + Contacts.PHOTO_FILE_ID + " IS NULL THEN (CASE WHEN " 2270 + Contacts.PHOTO_ID + " IS NULL" 2271 + " OR " + Contacts.PHOTO_ID + "=0" 2272 + " THEN NULL" 2273 + " ELSE '" + Contacts.CONTENT_URI + "/'||" 2274 + contactIdColumn + "|| '/" + Photo.CONTENT_DIRECTORY + "'" 2275 + " END) ELSE '" + DisplayPhoto.CONTENT_URI + "/'||" 2276 + Contacts.PHOTO_FILE_ID + " END)" 2277 + " AS " + alias; 2278 } 2279 2280 private static String buildThumbnailPhotoUriAlias(String contactIdColumn, String alias) { 2281 return "(CASE WHEN " 2282 + Contacts.PHOTO_ID + " IS NULL" 2283 + " OR " + Contacts.PHOTO_ID + "=0" 2284 + " THEN NULL" 2285 + " ELSE '" + Contacts.CONTENT_URI + "/'||" 2286 + contactIdColumn + "|| '/" + Photo.CONTENT_DIRECTORY + "'" 2287 + " END)" 2288 + " AS " + alias; 2289 } 2290 2291 /** 2292 * Returns the value to be returned when querying the column indicating that the contact 2293 * or raw contact belongs to the user's personal profile. Overridden in the profile 2294 * DB helper subclass. 2295 */ 2296 protected int dbForProfile() { 2297 return 0; 2298 } 2299 2300 private void createGroupsView(SQLiteDatabase db) { 2301 db.execSQL("DROP VIEW IF EXISTS " + Views.GROUPS + ";"); 2302 2303 String groupsColumns = 2304 GroupsColumns.CONCRETE_ACCOUNT_ID + " AS " + GroupsColumns.ACCOUNT_ID + "," 2305 + AccountsColumns.CONCRETE_ACCOUNT_NAME + " AS " + Groups.ACCOUNT_NAME + "," 2306 + AccountsColumns.CONCRETE_ACCOUNT_TYPE + " AS " + Groups.ACCOUNT_TYPE + "," 2307 + AccountsColumns.CONCRETE_DATA_SET + " AS " + Groups.DATA_SET + "," 2308 + "(CASE WHEN " + AccountsColumns.CONCRETE_DATA_SET 2309 + " IS NULL THEN " + AccountsColumns.CONCRETE_ACCOUNT_TYPE 2310 + " ELSE " + AccountsColumns.CONCRETE_ACCOUNT_TYPE 2311 + "||'/'||" + AccountsColumns.CONCRETE_DATA_SET + " END) AS " 2312 + Groups.ACCOUNT_TYPE_AND_DATA_SET + "," 2313 + Groups.SOURCE_ID + "," 2314 + Groups.VERSION + "," 2315 + Groups.DIRTY + "," 2316 + Groups.TITLE + "," 2317 + Groups.TITLE_RES + "," 2318 + Groups.NOTES + "," 2319 + Groups.SYSTEM_ID + "," 2320 + Groups.DELETED + "," 2321 + Groups.GROUP_VISIBLE + "," 2322 + Groups.SHOULD_SYNC + "," 2323 + Groups.AUTO_ADD + "," 2324 + Groups.FAVORITES + "," 2325 + Groups.GROUP_IS_READ_ONLY + "," 2326 + Groups.SYNC1 + "," 2327 + Groups.SYNC2 + "," 2328 + Groups.SYNC3 + "," 2329 + Groups.SYNC4 + "," 2330 + PackagesColumns.PACKAGE + " AS " + Groups.RES_PACKAGE; 2331 2332 String groupsSelect = "SELECT " 2333 + GroupsColumns.CONCRETE_ID + " AS " + Groups._ID + "," 2334 + groupsColumns 2335 + " FROM " + Tables.GROUPS 2336 + " JOIN " + Tables.ACCOUNTS + " ON (" 2337 + GroupsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID + ")" 2338 + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON (" 2339 + GroupsColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")"; 2340 2341 db.execSQL("CREATE VIEW " + Views.GROUPS + " AS " + groupsSelect); 2342 } 2343 2344 @Override 2345 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 2346 Log.i(TAG, "ContactsProvider cannot proceed because downgrading your database is not " + 2347 "supported. To continue, please either re-upgrade to your previous Android " + 2348 "version, or clear all application data in Contacts Storage (this will result " + 2349 "in the loss of all local contacts that are not synced). To avoid data loss, " + 2350 "your contacts database will not be wiped automatically."); 2351 super.onDowngrade(db, oldVersion, newVersion); 2352 } 2353 2354 @Override 2355 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 2356 if (oldVersion < 99) { 2357 Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion 2358 + ", data will be lost!"); 2359 2360 db.execSQL("DROP TABLE IF EXISTS " + Tables.CONTACTS + ";"); 2361 db.execSQL("DROP TABLE IF EXISTS " + Tables.RAW_CONTACTS + ";"); 2362 db.execSQL("DROP TABLE IF EXISTS " + Tables.PACKAGES + ";"); 2363 db.execSQL("DROP TABLE IF EXISTS " + Tables.MIMETYPES + ";"); 2364 db.execSQL("DROP TABLE IF EXISTS " + Tables.DATA + ";"); 2365 db.execSQL("DROP TABLE IF EXISTS " + Tables.PHONE_LOOKUP + ";"); 2366 db.execSQL("DROP TABLE IF EXISTS " + Tables.NAME_LOOKUP + ";"); 2367 db.execSQL("DROP TABLE IF EXISTS " + Tables.NICKNAME_LOOKUP + ";"); 2368 db.execSQL("DROP TABLE IF EXISTS " + Tables.GROUPS + ";"); 2369 db.execSQL("DROP TABLE IF EXISTS activities;"); 2370 db.execSQL("DROP TABLE IF EXISTS " + Tables.CALLS + ";"); 2371 db.execSQL("DROP TABLE IF EXISTS " + Tables.SETTINGS + ";"); 2372 db.execSQL("DROP TABLE IF EXISTS " + Tables.STATUS_UPDATES + ";"); 2373 2374 // TODO: we should not be dropping agg_exceptions and contact_options. In case that 2375 // table's schema changes, we should try to preserve the data, because it was entered 2376 // by the user and has never been synched to the server. 2377 db.execSQL("DROP TABLE IF EXISTS " + Tables.AGGREGATION_EXCEPTIONS + ";"); 2378 2379 onCreate(db); 2380 return; 2381 } 2382 2383 Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion); 2384 2385 boolean upgradeViewsAndTriggers = false; 2386 boolean upgradeNameLookup = false; 2387 boolean upgradeLegacyApiSupport = false; 2388 boolean upgradeSearchIndex = false; 2389 boolean rescanDirectories = false; 2390 boolean rebuildSqliteStats = false; 2391 boolean upgradeLocaleSpecificData = false; 2392 2393 if (oldVersion == 99) { 2394 upgradeViewsAndTriggers = true; 2395 oldVersion++; 2396 } 2397 2398 if (oldVersion == 100) { 2399 db.execSQL("CREATE INDEX IF NOT EXISTS mimetypes_mimetype_index ON " 2400 + Tables.MIMETYPES + " (" 2401 + MimetypesColumns.MIMETYPE + "," 2402 + MimetypesColumns._ID + ");"); 2403 updateIndexStats(db, Tables.MIMETYPES, 2404 "mimetypes_mimetype_index", "50 1 1"); 2405 2406 upgradeViewsAndTriggers = true; 2407 oldVersion++; 2408 } 2409 2410 if (oldVersion == 101) { 2411 upgradeViewsAndTriggers = true; 2412 oldVersion++; 2413 } 2414 2415 if (oldVersion == 102) { 2416 upgradeViewsAndTriggers = true; 2417 oldVersion++; 2418 } 2419 2420 if (oldVersion == 103) { 2421 upgradeViewsAndTriggers = true; 2422 oldVersion++; 2423 } 2424 2425 if (oldVersion == 104 || oldVersion == 201) { 2426 LegacyApiSupport.createSettingsTable(db); 2427 upgradeViewsAndTriggers = true; 2428 oldVersion++; 2429 } 2430 2431 if (oldVersion == 105) { 2432 upgradeToVersion202(db); 2433 upgradeNameLookup = true; 2434 oldVersion = 202; 2435 } 2436 2437 if (oldVersion == 202) { 2438 upgradeToVersion203(db); 2439 upgradeViewsAndTriggers = true; 2440 oldVersion++; 2441 } 2442 2443 if (oldVersion == 203) { 2444 upgradeViewsAndTriggers = true; 2445 oldVersion++; 2446 } 2447 2448 if (oldVersion == 204) { 2449 upgradeToVersion205(db); 2450 upgradeViewsAndTriggers = true; 2451 oldVersion++; 2452 } 2453 2454 if (oldVersion == 205) { 2455 upgrateToVersion206(db); 2456 upgradeViewsAndTriggers = true; 2457 oldVersion++; 2458 } 2459 2460 if (oldVersion == 206) { 2461 // Fix for the bug where name lookup records for organizations would get removed by 2462 // unrelated updates of the data rows. No longer needed. 2463 oldVersion = 300; 2464 } 2465 2466 if (oldVersion == 300) { 2467 upgradeViewsAndTriggers = true; 2468 oldVersion = 301; 2469 } 2470 2471 if (oldVersion == 301) { 2472 upgradeViewsAndTriggers = true; 2473 oldVersion = 302; 2474 } 2475 2476 if (oldVersion == 302) { 2477 upgradeEmailToVersion303(db); 2478 upgradeNicknameToVersion303(db); 2479 oldVersion = 303; 2480 } 2481 2482 if (oldVersion == 303) { 2483 upgradeToVersion304(db); 2484 oldVersion = 304; 2485 } 2486 2487 if (oldVersion == 304) { 2488 upgradeNameLookup = true; 2489 oldVersion = 305; 2490 } 2491 2492 if (oldVersion == 305) { 2493 upgradeToVersion306(db); 2494 oldVersion = 306; 2495 } 2496 2497 if (oldVersion == 306) { 2498 upgradeToVersion307(db); 2499 oldVersion = 307; 2500 } 2501 2502 if (oldVersion == 307) { 2503 upgradeToVersion308(db); 2504 oldVersion = 308; 2505 } 2506 2507 // Gingerbread upgrades. 2508 if (oldVersion < 350) { 2509 upgradeViewsAndTriggers = true; 2510 oldVersion = 351; 2511 } 2512 2513 if (oldVersion == 351) { 2514 upgradeNameLookup = true; 2515 oldVersion = 352; 2516 } 2517 2518 if (oldVersion == 352) { 2519 upgradeToVersion353(db); 2520 oldVersion = 353; 2521 } 2522 2523 // Honeycomb upgrades. 2524 if (oldVersion < 400) { 2525 upgradeViewsAndTriggers = true; 2526 upgradeToVersion400(db); 2527 oldVersion = 400; 2528 } 2529 2530 if (oldVersion == 400) { 2531 upgradeViewsAndTriggers = true; 2532 upgradeToVersion401(db); 2533 oldVersion = 401; 2534 } 2535 2536 if (oldVersion == 401) { 2537 upgradeToVersion402(db); 2538 oldVersion = 402; 2539 } 2540 2541 if (oldVersion == 402) { 2542 upgradeViewsAndTriggers = true; 2543 upgradeToVersion403(db); 2544 oldVersion = 403; 2545 } 2546 2547 if (oldVersion == 403) { 2548 upgradeViewsAndTriggers = true; 2549 oldVersion = 404; 2550 } 2551 2552 if (oldVersion == 404) { 2553 upgradeViewsAndTriggers = true; 2554 upgradeToVersion405(db); 2555 oldVersion = 405; 2556 } 2557 2558 if (oldVersion == 405) { 2559 upgradeViewsAndTriggers = true; 2560 upgradeToVersion406(db); 2561 oldVersion = 406; 2562 } 2563 2564 if (oldVersion == 406) { 2565 upgradeViewsAndTriggers = true; 2566 oldVersion = 407; 2567 } 2568 2569 if (oldVersion == 407) { 2570 oldVersion = 408; // Obsolete. 2571 } 2572 2573 if (oldVersion == 408) { 2574 upgradeViewsAndTriggers = true; 2575 upgradeToVersion409(db); 2576 oldVersion = 409; 2577 } 2578 2579 if (oldVersion == 409) { 2580 upgradeViewsAndTriggers = true; 2581 oldVersion = 410; 2582 } 2583 2584 if (oldVersion == 410) { 2585 upgradeToVersion411(db); 2586 oldVersion = 411; 2587 } 2588 2589 if (oldVersion == 411) { 2590 // Same upgrade as 353, only on Honeycomb devices. 2591 upgradeToVersion353(db); 2592 oldVersion = 412; 2593 } 2594 2595 if (oldVersion == 412) { 2596 upgradeToVersion413(db); 2597 oldVersion = 413; 2598 } 2599 2600 if (oldVersion == 413) { 2601 upgradeNameLookup = true; 2602 oldVersion = 414; 2603 } 2604 2605 if (oldVersion == 414) { 2606 upgradeToVersion415(db); 2607 upgradeViewsAndTriggers = true; 2608 oldVersion = 415; 2609 } 2610 2611 if (oldVersion == 415) { 2612 upgradeToVersion416(db); 2613 oldVersion = 416; 2614 } 2615 2616 if (oldVersion == 416) { 2617 upgradeLegacyApiSupport = true; 2618 oldVersion = 417; 2619 } 2620 2621 // Honeycomb-MR1 upgrades. 2622 if (oldVersion < 500) { 2623 upgradeSearchIndex = true; 2624 } 2625 2626 if (oldVersion < 501) { 2627 upgradeSearchIndex = true; 2628 upgradeToVersion501(db); 2629 oldVersion = 501; 2630 } 2631 2632 if (oldVersion < 502) { 2633 upgradeSearchIndex = true; 2634 upgradeToVersion502(db); 2635 oldVersion = 502; 2636 } 2637 2638 if (oldVersion < 503) { 2639 upgradeSearchIndex = true; 2640 oldVersion = 503; 2641 } 2642 2643 if (oldVersion < 504) { 2644 upgradeToVersion504(db); 2645 oldVersion = 504; 2646 } 2647 2648 if (oldVersion < 600) { 2649 // This change used to add the profile raw contact ID to the Accounts table. That 2650 // column is no longer needed (as of version 614) since the profile records are stored in 2651 // a separate copy of the database for security reasons. So this change is now a no-op. 2652 upgradeViewsAndTriggers = true; 2653 oldVersion = 600; 2654 } 2655 2656 if (oldVersion < 601) { 2657 upgradeToVersion601(db); 2658 oldVersion = 601; 2659 } 2660 2661 if (oldVersion < 602) { 2662 upgradeToVersion602(db); 2663 oldVersion = 602; 2664 } 2665 2666 if (oldVersion < 603) { 2667 upgradeViewsAndTriggers = true; 2668 oldVersion = 603; 2669 } 2670 2671 if (oldVersion < 604) { 2672 upgradeToVersion604(db); 2673 oldVersion = 604; 2674 } 2675 2676 if (oldVersion < 605) { 2677 upgradeViewsAndTriggers = true; 2678 // This version used to create the stream item and stream item photos tables, but 2679 // a newer version of those tables is created in version 609 below. So omitting the 2680 // creation in this upgrade step to avoid a create->drop->create. 2681 oldVersion = 605; 2682 } 2683 2684 if (oldVersion < 606) { 2685 upgradeViewsAndTriggers = true; 2686 upgradeLegacyApiSupport = true; 2687 upgradeToVersion606(db); 2688 oldVersion = 606; 2689 } 2690 2691 if (oldVersion < 607) { 2692 upgradeViewsAndTriggers = true; 2693 // We added "action" and "action_uri" to groups here, but realized this was not a smart 2694 // move. This upgrade step has been removed (all dogfood phones that executed this step 2695 // will have those columns, but that shouldn't hurt. Unfortunately, SQLite makes it 2696 // hard to remove columns). 2697 oldVersion = 607; 2698 } 2699 2700 if (oldVersion < 608) { 2701 upgradeViewsAndTriggers = true; 2702 upgradeToVersion608(db); 2703 oldVersion = 608; 2704 } 2705 2706 if (oldVersion < 609) { 2707 // This version used to create the stream item and stream item photos tables, but a 2708 // newer version of those tables is created in version 613 below. So omitting the 2709 // creation in this upgrade step to avoid a create->drop->create. 2710 oldVersion = 609; 2711 } 2712 2713 if (oldVersion < 610) { 2714 upgradeToVersion610(db); 2715 oldVersion = 610; 2716 } 2717 2718 if (oldVersion < 611) { 2719 upgradeViewsAndTriggers = true; 2720 upgradeToVersion611(db); 2721 oldVersion = 611; 2722 } 2723 2724 if (oldVersion < 612) { 2725 upgradeViewsAndTriggers = true; 2726 upgradeToVersion612(db); 2727 oldVersion = 612; 2728 } 2729 2730 if (oldVersion < 613) { 2731 upgradeToVersion613(db); 2732 oldVersion = 613; 2733 } 2734 2735 if (oldVersion < 614) { 2736 // This creates the "view_stream_items" view. 2737 upgradeViewsAndTriggers = true; 2738 oldVersion = 614; 2739 } 2740 2741 if (oldVersion < 615) { 2742 upgradeToVersion615(db); 2743 oldVersion = 615; 2744 } 2745 2746 if (oldVersion < 616) { 2747 // This updates the "view_stream_items" view. 2748 upgradeViewsAndTriggers = true; 2749 oldVersion = 616; 2750 } 2751 2752 if (oldVersion < 617) { 2753 // This version upgrade obsoleted the profile_raw_contact_id field of the Accounts 2754 // table, but we aren't removing the column because it is very little data (and not 2755 // referenced anymore). We do need to upgrade the views to handle the simplified 2756 // per-database "is profile" columns. 2757 upgradeViewsAndTriggers = true; 2758 oldVersion = 617; 2759 } 2760 2761 if (oldVersion < 618) { 2762 upgradeToVersion618(db); 2763 oldVersion = 618; 2764 } 2765 2766 if (oldVersion < 619) { 2767 upgradeViewsAndTriggers = true; 2768 oldVersion = 619; 2769 } 2770 2771 if (oldVersion < 620) { 2772 upgradeViewsAndTriggers = true; 2773 oldVersion = 620; 2774 } 2775 2776 if (oldVersion < 621) { 2777 upgradeSearchIndex = true; 2778 oldVersion = 621; 2779 } 2780 2781 if (oldVersion < 622) { 2782 upgradeToVersion622(db); 2783 oldVersion = 622; 2784 } 2785 2786 if (oldVersion < 623) { 2787 // Change FTS to normalize names using collation key. 2788 upgradeSearchIndex = true; 2789 oldVersion = 623; 2790 } 2791 2792 if (oldVersion < 624) { 2793 // Upgraded the SQLite index stats. 2794 upgradeViewsAndTriggers = true; 2795 oldVersion = 624; 2796 } 2797 2798 if (oldVersion < 625) { 2799 // Fix for search for hyphenated names 2800 upgradeSearchIndex = true; 2801 oldVersion = 625; 2802 } 2803 2804 if (oldVersion < 626) { 2805 upgradeToVersion626(db); 2806 upgradeViewsAndTriggers = true; 2807 oldVersion = 626; 2808 } 2809 2810 if (oldVersion < 700) { 2811 rescanDirectories = true; 2812 oldVersion = 700; 2813 } 2814 2815 if (oldVersion < 701) { 2816 upgradeToVersion701(db); 2817 oldVersion = 701; 2818 } 2819 2820 if (oldVersion < 702) { 2821 upgradeToVersion702(db); 2822 oldVersion = 702; 2823 } 2824 2825 if (oldVersion < 703) { 2826 // Now names like "L'Image" will be searchable. 2827 upgradeSearchIndex = true; 2828 oldVersion = 703; 2829 } 2830 2831 if (oldVersion < 704) { 2832 db.execSQL("DROP TABLE IF EXISTS activities;"); 2833 oldVersion = 704; 2834 } 2835 2836 if (oldVersion < 705) { 2837 // Before this version, we didn't rebuild the search index on locale changes, so 2838 // if the locale has changed after sync, the index contains gets stale. 2839 // To correct the issue we have to rebuild the index here. 2840 upgradeSearchIndex = true; 2841 oldVersion = 705; 2842 } 2843 2844 if (oldVersion < 706) { 2845 // Prior to this version, we didn't rebuild the stats table after drop operations, 2846 // which resulted in losing some of the rows from the stats table. 2847 rebuildSqliteStats = true; 2848 oldVersion = 706; 2849 } 2850 2851 if (oldVersion < 707) { 2852 upgradeToVersion707(db); 2853 upgradeViewsAndTriggers = true; 2854 oldVersion = 707; 2855 } 2856 2857 if (oldVersion < 708) { 2858 // Sort keys, phonebook labels and buckets, and search keys have 2859 // changed so force a rebuild. 2860 upgradeLocaleSpecificData = true; 2861 oldVersion = 708; 2862 } 2863 if (oldVersion < 709) { 2864 // Added secondary locale phonebook labels; changed Japanese 2865 // and Chinese sort keys. 2866 upgradeLocaleSpecificData = true; 2867 oldVersion = 709; 2868 } 2869 2870 if (oldVersion < 710) { 2871 upgradeToVersion710(db); 2872 upgradeViewsAndTriggers = true; 2873 oldVersion = 710; 2874 } 2875 2876 if (oldVersion < 800) { 2877 upgradeToVersion800(db); 2878 oldVersion = 800; 2879 } 2880 2881 if (oldVersion < 801) { 2882 setProperty(db, DbProperties.DATABASE_TIME_CREATED, String.valueOf( 2883 System.currentTimeMillis())); 2884 oldVersion = 801; 2885 } 2886 2887 if (oldVersion < 802) { 2888 upgradeToVersion802(db); 2889 upgradeViewsAndTriggers = true; 2890 oldVersion = 802; 2891 } 2892 2893 if (oldVersion < 803) { 2894 // Rebuild the search index so that names, organizations and nicknames are 2895 // now indexed as names. 2896 upgradeSearchIndex = true; 2897 oldVersion = 803; 2898 } 2899 2900 if (oldVersion < 804) { 2901 // Reserved. 2902 oldVersion = 804; 2903 } 2904 2905 if (oldVersion < 900) { 2906 upgradeViewsAndTriggers = true; 2907 oldVersion = 900; 2908 } 2909 2910 if (oldVersion < 901) { 2911 // Rebuild the search index to fix any search index that was previously in a 2912 // broken state due to b/11059351 2913 upgradeSearchIndex = true; 2914 oldVersion = 901; 2915 } 2916 2917 if (oldVersion < 902) { 2918 upgradeToVersion902(db); 2919 oldVersion = 902; 2920 } 2921 2922 if (oldVersion < 903) { 2923 upgradeToVersion903(db); 2924 oldVersion = 903; 2925 } 2926 2927 if (oldVersion < 904) { 2928 upgradeToVersion904(db); 2929 oldVersion = 904; 2930 } 2931 2932 if (oldVersion < 905) { 2933 upgradeToVersion905(db); 2934 oldVersion = 905; 2935 } 2936 2937 if (oldVersion < 906) { 2938 upgradeToVersion906(db); 2939 oldVersion = 906; 2940 } 2941 2942 if (oldVersion < 907) { 2943 // Rebuild NAME_LOOKUP. 2944 upgradeNameLookup = true; 2945 oldVersion = 907; 2946 } 2947 2948 if (oldVersion < 908) { 2949 upgradeToVersion908(db); 2950 oldVersion = 908; 2951 } 2952 2953 if (oldVersion < 909) { 2954 upgradeToVersion909(db); 2955 oldVersion = 909; 2956 } 2957 2958 if (oldVersion < 910) { 2959 upgradeToVersion910(db); 2960 oldVersion = 910; 2961 } 2962 if (oldVersion < 1000) { 2963 upgradeToVersion1000(db); 2964 upgradeViewsAndTriggers = true; 2965 oldVersion = 1000; 2966 } 2967 2968 if (oldVersion < 1002) { 2969 rebuildSqliteStats = true; 2970 upgradeToVersion1002(db); 2971 oldVersion = 1002; 2972 } 2973 2974 if (oldVersion < 1003) { 2975 upgradeToVersion1003(db); 2976 oldVersion = 1003; 2977 } 2978 2979 if (oldVersion < 1004) { 2980 upgradeToVersion1004(db); 2981 oldVersion = 1004; 2982 } 2983 2984 if (oldVersion < 1005) { 2985 upgradeToVersion1005(db); 2986 oldVersion = 1005; 2987 } 2988 2989 if (oldVersion < 1006) { 2990 upgradeViewsAndTriggers = true; 2991 oldVersion = 1006; 2992 } 2993 2994 if (oldVersion < 1007) { 2995 upgradeToVersion1007(db); 2996 oldVersion = 1007; 2997 } 2998 2999 if (oldVersion < 1009) { 3000 upgradeToVersion1009(db); 3001 oldVersion = 1009; 3002 } 3003 3004 if (oldVersion < 1100) { 3005 upgradeToVersion1100(db); 3006 upgradeViewsAndTriggers = true; 3007 oldVersion = 1100; 3008 } 3009 3010 if (oldVersion < 1101) { 3011 upgradeToVersion1101(db); 3012 oldVersion = 1101; 3013 } 3014 3015 if (oldVersion < 1102) { 3016 // Version 1009 was added *after* 1100/1101. For master devices 3017 // that have already been updated to 1101, we do it again. 3018 upgradeToVersion1009(db); 3019 oldVersion = 1102; 3020 } 3021 3022 if (oldVersion < 1103) { 3023 upgradeViewsAndTriggers = true; 3024 oldVersion = 1103; 3025 } 3026 3027 if (oldVersion < 1104) { 3028 upgradeToVersion1104(db); 3029 upgradeViewsAndTriggers = true; 3030 oldVersion = 1104; 3031 } 3032 3033 if (oldVersion < 1105) { 3034 upgradeToVersion1105(db); 3035 upgradeViewsAndTriggers = true; 3036 oldVersion = 1105; 3037 } 3038 3039 if (oldVersion < 1106) { 3040 upgradeToVersion1106(db); 3041 oldVersion = 1106; 3042 } 3043 3044 if (oldVersion < 1107) { 3045 upgradeToVersion1107(db); 3046 oldVersion = 1107; 3047 } 3048 3049 if (upgradeViewsAndTriggers) { 3050 createContactsViews(db); 3051 createGroupsView(db); 3052 createContactsTriggers(db); 3053 createContactsIndexes(db, false /* we build stats table later */); 3054 upgradeLegacyApiSupport = true; 3055 rebuildSqliteStats = true; 3056 } 3057 3058 if (upgradeLegacyApiSupport) { 3059 LegacyApiSupport.createViews(db); 3060 } 3061 3062 if (upgradeLocaleSpecificData) { 3063 upgradeLocaleData(db, false /* we build stats table later */); 3064 // Name lookups are rebuilt as part of the full locale rebuild 3065 upgradeNameLookup = false; 3066 upgradeSearchIndex = true; 3067 rebuildSqliteStats = true; 3068 } 3069 3070 if (upgradeNameLookup) { 3071 rebuildNameLookup(db, false /* we build stats table later */); 3072 rebuildSqliteStats = true; 3073 } 3074 3075 if (upgradeSearchIndex) { 3076 rebuildSearchIndex(db, false /* we build stats table later */); 3077 rebuildSqliteStats = true; 3078 } 3079 3080 if (rescanDirectories) { 3081 // Force the next ContactDirectoryManager.scanAllPackages() to rescan all packages. 3082 // (It's called from the BACKGROUND_TASK_UPDATE_ACCOUNTS background task.) 3083 setProperty(db, DbProperties.DIRECTORY_SCAN_COMPLETE, "0"); 3084 } 3085 3086 if (rebuildSqliteStats) { 3087 updateSqliteStats(db); 3088 } 3089 3090 if (oldVersion != newVersion) { 3091 throw new IllegalStateException( 3092 "error upgrading the database to version " + newVersion); 3093 } 3094 } 3095 3096 private void upgradeToVersion202(SQLiteDatabase db) { 3097 db.execSQL( 3098 "ALTER TABLE " + Tables.PHONE_LOOKUP + 3099 " ADD " + PhoneLookupColumns.MIN_MATCH + " TEXT;"); 3100 3101 db.execSQL("CREATE INDEX phone_lookup_min_match_index ON " + Tables.PHONE_LOOKUP + " (" + 3102 PhoneLookupColumns.MIN_MATCH + "," + 3103 PhoneLookupColumns.RAW_CONTACT_ID + "," + 3104 PhoneLookupColumns.DATA_ID + 3105 ");"); 3106 3107 updateIndexStats(db, Tables.PHONE_LOOKUP, 3108 "phone_lookup_min_match_index", "10000 2 2 1"); 3109 3110 SQLiteStatement update = db.compileStatement( 3111 "UPDATE " + Tables.PHONE_LOOKUP + 3112 " SET " + PhoneLookupColumns.MIN_MATCH + "=?" + 3113 " WHERE " + PhoneLookupColumns.DATA_ID + "=?"); 3114 3115 // Populate the new column 3116 Cursor c = db.query(Tables.PHONE_LOOKUP + " JOIN " + Tables.DATA + 3117 " ON (" + PhoneLookupColumns.DATA_ID + "=" + DataColumns.CONCRETE_ID + ")", 3118 new String[] {Data._ID, Phone.NUMBER}, null, null, null, null, null); 3119 try { 3120 while (c.moveToNext()) { 3121 long dataId = c.getLong(0); 3122 String number = c.getString(1); 3123 if (!TextUtils.isEmpty(number)) { 3124 update.bindString(1, PhoneNumberUtils.toCallerIDMinMatch(number)); 3125 update.bindLong(2, dataId); 3126 update.execute(); 3127 } 3128 } 3129 } finally { 3130 c.close(); 3131 } 3132 } 3133 3134 private void upgradeToVersion203(SQLiteDatabase db) { 3135 // Garbage-collect first. A bug in Eclair was sometimes leaving 3136 // raw_contacts in the database that no longer had contacts associated 3137 // with them. To avoid failures during this database upgrade, drop 3138 // the orphaned raw_contacts. 3139 db.execSQL( 3140 "DELETE FROM raw_contacts" + 3141 " WHERE contact_id NOT NULL" + 3142 " AND contact_id NOT IN (SELECT _id FROM contacts)"); 3143 3144 db.execSQL( 3145 "ALTER TABLE " + Tables.CONTACTS + 3146 " ADD " + Contacts.NAME_RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)"); 3147 db.execSQL( 3148 "ALTER TABLE " + Tables.RAW_CONTACTS + 3149 " ADD contact_in_visible_group INTEGER NOT NULL DEFAULT 0"); 3150 3151 // For each Contact, find the RawContact that contributed the display name 3152 db.execSQL( 3153 "UPDATE " + Tables.CONTACTS + 3154 " SET " + Contacts.NAME_RAW_CONTACT_ID + "=(" + 3155 " SELECT " + RawContacts._ID + 3156 " FROM " + Tables.RAW_CONTACTS + 3157 " WHERE " + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + 3158 " AND " + RawContactsColumns.CONCRETE_DISPLAY_NAME + "=" + 3159 Tables.CONTACTS + "." + Contacts.DISPLAY_NAME + 3160 " ORDER BY " + RawContacts._ID + 3161 " LIMIT 1)" 3162 ); 3163 3164 db.execSQL("CREATE INDEX contacts_name_raw_contact_id_index ON " + Tables.CONTACTS + " (" + 3165 Contacts.NAME_RAW_CONTACT_ID + 3166 ");"); 3167 3168 // If for some unknown reason we missed some names, let's make sure there are 3169 // no contacts without a name, picking a raw contact "at random". 3170 db.execSQL( 3171 "UPDATE " + Tables.CONTACTS + 3172 " SET " + Contacts.NAME_RAW_CONTACT_ID + "=(" + 3173 " SELECT " + RawContacts._ID + 3174 " FROM " + Tables.RAW_CONTACTS + 3175 " WHERE " + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + 3176 " ORDER BY " + RawContacts._ID + 3177 " LIMIT 1)" + 3178 " WHERE " + Contacts.NAME_RAW_CONTACT_ID + " IS NULL" 3179 ); 3180 3181 // Wipe out DISPLAY_NAME on the Contacts table as it is no longer in use. 3182 db.execSQL( 3183 "UPDATE " + Tables.CONTACTS + 3184 " SET " + Contacts.DISPLAY_NAME + "=NULL" 3185 ); 3186 3187 // Copy the IN_VISIBLE_GROUP flag down to all raw contacts to allow 3188 // indexing on (display_name, in_visible_group) 3189 db.execSQL( 3190 "UPDATE " + Tables.RAW_CONTACTS + 3191 " SET contact_in_visible_group=(" + 3192 "SELECT " + Contacts.IN_VISIBLE_GROUP + 3193 " FROM " + Tables.CONTACTS + 3194 " WHERE " + Contacts._ID + "=" + RawContacts.CONTACT_ID + ")" + 3195 " WHERE " + RawContacts.CONTACT_ID + " NOT NULL" 3196 ); 3197 3198 db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" + 3199 "contact_in_visible_group" + "," + 3200 RawContactsColumns.DISPLAY_NAME + " COLLATE LOCALIZED ASC" + 3201 ");"); 3202 3203 db.execSQL("DROP INDEX contacts_visible_index"); 3204 db.execSQL("CREATE INDEX contacts_visible_index ON " + Tables.CONTACTS + " (" + 3205 Contacts.IN_VISIBLE_GROUP + 3206 ");"); 3207 } 3208 3209 private void upgradeToVersion205(SQLiteDatabase db) { 3210 db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS 3211 + " ADD " + RawContacts.DISPLAY_NAME_ALTERNATIVE + " TEXT;"); 3212 db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS 3213 + " ADD " + RawContacts.PHONETIC_NAME + " TEXT;"); 3214 db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS 3215 + " ADD " + RawContacts.PHONETIC_NAME_STYLE + " INTEGER;"); 3216 db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS 3217 + " ADD " + RawContacts.SORT_KEY_PRIMARY 3218 + " TEXT COLLATE " + ContactsProvider2.PHONEBOOK_COLLATOR_NAME + ";"); 3219 db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS 3220 + " ADD " + RawContacts.SORT_KEY_ALTERNATIVE 3221 + " TEXT COLLATE " + ContactsProvider2.PHONEBOOK_COLLATOR_NAME + ";"); 3222 3223 NameSplitter splitter = createNameSplitter(); 3224 3225 SQLiteStatement rawContactUpdate = db.compileStatement( 3226 "UPDATE " + Tables.RAW_CONTACTS + 3227 " SET " + 3228 RawContacts.DISPLAY_NAME_PRIMARY + "=?," + 3229 RawContacts.DISPLAY_NAME_ALTERNATIVE + "=?," + 3230 RawContacts.PHONETIC_NAME + "=?," + 3231 RawContacts.PHONETIC_NAME_STYLE + "=?," + 3232 RawContacts.SORT_KEY_PRIMARY + "=?," + 3233 RawContacts.SORT_KEY_ALTERNATIVE + "=?" + 3234 " WHERE " + RawContacts._ID + "=?"); 3235 3236 upgradeStructuredNamesToVersion205(db, rawContactUpdate, splitter); 3237 upgradeOrganizationsToVersion205(db, rawContactUpdate, splitter); 3238 3239 db.execSQL("DROP INDEX raw_contact_sort_key1_index"); 3240 db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" + 3241 "contact_in_visible_group" + "," + 3242 RawContacts.SORT_KEY_PRIMARY + 3243 ");"); 3244 3245 db.execSQL("CREATE INDEX raw_contact_sort_key2_index ON " + Tables.RAW_CONTACTS + " (" + 3246 "contact_in_visible_group" + "," + 3247 RawContacts.SORT_KEY_ALTERNATIVE + 3248 ");"); 3249 } 3250 3251 private void upgradeStructuredNamesToVersion205( 3252 SQLiteDatabase db, SQLiteStatement rawContactUpdate, NameSplitter splitter) { 3253 3254 // Process structured names to detect the style of the full name and phonetic name. 3255 long mMimeType; 3256 try { 3257 mMimeType = DatabaseUtils.longForQuery(db, 3258 "SELECT " + MimetypesColumns._ID + 3259 " FROM " + Tables.MIMETYPES + 3260 " WHERE " + MimetypesColumns.MIMETYPE 3261 + "='" + StructuredName.CONTENT_ITEM_TYPE + "'", null); 3262 3263 } catch (SQLiteDoneException e) { 3264 // No structured names in the database. 3265 return; 3266 } 3267 3268 SQLiteStatement structuredNameUpdate = db.compileStatement( 3269 "UPDATE " + Tables.DATA + 3270 " SET " + 3271 StructuredName.FULL_NAME_STYLE + "=?," + 3272 StructuredName.DISPLAY_NAME + "=?," + 3273 StructuredName.PHONETIC_NAME_STYLE + "=?" + 3274 " WHERE " + Data._ID + "=?"); 3275 3276 NameSplitter.Name name = new NameSplitter.Name(); 3277 Cursor cursor = db.query(StructName205Query.TABLE, 3278 StructName205Query.COLUMNS, 3279 DataColumns.MIMETYPE_ID + "=" + mMimeType, null, null, null, null); 3280 try { 3281 while (cursor.moveToNext()) { 3282 long dataId = cursor.getLong(StructName205Query.ID); 3283 long rawContactId = cursor.getLong(StructName205Query.RAW_CONTACT_ID); 3284 int displayNameSource = cursor.getInt(StructName205Query.DISPLAY_NAME_SOURCE); 3285 3286 name.clear(); 3287 name.prefix = cursor.getString(StructName205Query.PREFIX); 3288 name.givenNames = cursor.getString(StructName205Query.GIVEN_NAME); 3289 name.middleName = cursor.getString(StructName205Query.MIDDLE_NAME); 3290 name.familyName = cursor.getString(StructName205Query.FAMILY_NAME); 3291 name.suffix = cursor.getString(StructName205Query.SUFFIX); 3292 name.phoneticFamilyName = cursor.getString(StructName205Query.PHONETIC_FAMILY_NAME); 3293 name.phoneticMiddleName = cursor.getString(StructName205Query.PHONETIC_MIDDLE_NAME); 3294 name.phoneticGivenName = cursor.getString(StructName205Query.PHONETIC_GIVEN_NAME); 3295 3296 upgradeNameToVersion205(dataId, rawContactId, displayNameSource, name, 3297 structuredNameUpdate, rawContactUpdate, splitter); 3298 } 3299 } finally { 3300 cursor.close(); 3301 } 3302 } 3303 3304 private void upgradeNameToVersion205( 3305 long dataId, 3306 long rawContactId, 3307 int displayNameSource, 3308 NameSplitter.Name name, 3309 SQLiteStatement structuredNameUpdate, 3310 SQLiteStatement rawContactUpdate, 3311 NameSplitter splitter) { 3312 3313 splitter.guessNameStyle(name); 3314 int unadjustedFullNameStyle = name.fullNameStyle; 3315 name.fullNameStyle = splitter.getAdjustedFullNameStyle(name.fullNameStyle); 3316 String displayName = splitter.join(name, true, true); 3317 3318 // Don't update database with the adjusted fullNameStyle as it is locale 3319 // related 3320 structuredNameUpdate.bindLong(1, unadjustedFullNameStyle); 3321 DatabaseUtils.bindObjectToProgram(structuredNameUpdate, 2, displayName); 3322 structuredNameUpdate.bindLong(3, name.phoneticNameStyle); 3323 structuredNameUpdate.bindLong(4, dataId); 3324 structuredNameUpdate.execute(); 3325 3326 if (displayNameSource == DisplayNameSources.STRUCTURED_NAME) { 3327 String displayNameAlternative = splitter.join(name, false, false); 3328 String phoneticName = splitter.joinPhoneticName(name); 3329 String sortKey = null; 3330 String sortKeyAlternative = null; 3331 3332 if (phoneticName != null) { 3333 sortKey = sortKeyAlternative = phoneticName; 3334 } else if (name.fullNameStyle == FullNameStyle.CHINESE || 3335 name.fullNameStyle == FullNameStyle.CJK) { 3336 sortKey = sortKeyAlternative = displayName; 3337 } 3338 3339 if (sortKey == null) { 3340 sortKey = displayName; 3341 sortKeyAlternative = displayNameAlternative; 3342 } 3343 3344 updateRawContact205(rawContactUpdate, rawContactId, displayName, 3345 displayNameAlternative, name.phoneticNameStyle, phoneticName, sortKey, 3346 sortKeyAlternative); 3347 } 3348 } 3349 3350 private void upgradeOrganizationsToVersion205( 3351 SQLiteDatabase db, SQLiteStatement rawContactUpdate, NameSplitter splitter) { 3352 3353 final long mimeType = lookupMimeTypeId(db, Organization.CONTENT_ITEM_TYPE); 3354 SQLiteStatement organizationUpdate = db.compileStatement( 3355 "UPDATE " + Tables.DATA + 3356 " SET " + 3357 Organization.PHONETIC_NAME_STYLE + "=?" + 3358 " WHERE " + Data._ID + "=?"); 3359 3360 Cursor cursor = db.query(Organization205Query.TABLE, Organization205Query.COLUMNS, 3361 DataColumns.MIMETYPE_ID + "=" + mimeType + " AND " 3362 + RawContacts.DISPLAY_NAME_SOURCE + "=" + DisplayNameSources.ORGANIZATION, 3363 null, null, null, null); 3364 try { 3365 while (cursor.moveToNext()) { 3366 long dataId = cursor.getLong(Organization205Query.ID); 3367 long rawContactId = cursor.getLong(Organization205Query.RAW_CONTACT_ID); 3368 String company = cursor.getString(Organization205Query.COMPANY); 3369 String phoneticName = cursor.getString(Organization205Query.PHONETIC_NAME); 3370 3371 int phoneticNameStyle = splitter.guessPhoneticNameStyle(phoneticName); 3372 3373 organizationUpdate.bindLong(1, phoneticNameStyle); 3374 organizationUpdate.bindLong(2, dataId); 3375 organizationUpdate.execute(); 3376 3377 String sortKey = company; 3378 3379 updateRawContact205(rawContactUpdate, rawContactId, company, 3380 company, phoneticNameStyle, phoneticName, sortKey, sortKey); 3381 } 3382 } finally { 3383 cursor.close(); 3384 } 3385 } 3386 3387 private void updateRawContact205(SQLiteStatement rawContactUpdate, long rawContactId, 3388 String displayName, String displayNameAlternative, int phoneticNameStyle, 3389 String phoneticName, String sortKeyPrimary, String sortKeyAlternative) { 3390 bindString(rawContactUpdate, 1, displayName); 3391 bindString(rawContactUpdate, 2, displayNameAlternative); 3392 bindString(rawContactUpdate, 3, phoneticName); 3393 rawContactUpdate.bindLong(4, phoneticNameStyle); 3394 bindString(rawContactUpdate, 5, sortKeyPrimary); 3395 bindString(rawContactUpdate, 6, sortKeyAlternative); 3396 rawContactUpdate.bindLong(7, rawContactId); 3397 rawContactUpdate.execute(); 3398 } 3399 3400 private void upgrateToVersion206(SQLiteDatabase db) { 3401 db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS 3402 + " ADD name_verified INTEGER NOT NULL DEFAULT 0;"); 3403 } 3404 3405 /** 3406 * The {@link ContactsProvider2#update} method was deleting name lookup for new 3407 * emails during the sync. We need to restore the lost name lookup rows. 3408 */ 3409 private void upgradeEmailToVersion303(SQLiteDatabase db) { 3410 final long mimeTypeId = lookupMimeTypeId(db, Email.CONTENT_ITEM_TYPE); 3411 if (mimeTypeId == -1) { 3412 return; 3413 } 3414 3415 ContentValues values = new ContentValues(); 3416 3417 // Find all data rows with the mime type "email" that are missing name lookup 3418 Cursor cursor = db.query(Upgrade303Query.TABLE, Upgrade303Query.COLUMNS, 3419 Upgrade303Query.SELECTION, new String[] {String.valueOf(mimeTypeId)}, 3420 null, null, null); 3421 try { 3422 while (cursor.moveToNext()) { 3423 long dataId = cursor.getLong(Upgrade303Query.ID); 3424 long rawContactId = cursor.getLong(Upgrade303Query.RAW_CONTACT_ID); 3425 String value = cursor.getString(Upgrade303Query.DATA1); 3426 value = extractHandleFromEmailAddress(value); 3427 3428 if (value != null) { 3429 values.put(NameLookupColumns.DATA_ID, dataId); 3430 values.put(NameLookupColumns.RAW_CONTACT_ID, rawContactId); 3431 values.put(NameLookupColumns.NAME_TYPE, NameLookupType.EMAIL_BASED_NICKNAME); 3432 values.put(NameLookupColumns.NORMALIZED_NAME, NameNormalizer.normalize(value)); 3433 db.insert(Tables.NAME_LOOKUP, null, values); 3434 } 3435 } 3436 } finally { 3437 cursor.close(); 3438 } 3439 } 3440 3441 /** 3442 * The {@link ContactsProvider2#update} method was deleting name lookup for new 3443 * nicknames during the sync. We need to restore the lost name lookup rows. 3444 */ 3445 private void upgradeNicknameToVersion303(SQLiteDatabase db) { 3446 final long mimeTypeId = lookupMimeTypeId(db, Nickname.CONTENT_ITEM_TYPE); 3447 if (mimeTypeId == -1) { 3448 return; 3449 } 3450 3451 ContentValues values = new ContentValues(); 3452 3453 // Find all data rows with the mime type "nickname" that are missing name lookup 3454 Cursor cursor = db.query(Upgrade303Query.TABLE, Upgrade303Query.COLUMNS, 3455 Upgrade303Query.SELECTION, new String[] {String.valueOf(mimeTypeId)}, 3456 null, null, null); 3457 try { 3458 while (cursor.moveToNext()) { 3459 long dataId = cursor.getLong(Upgrade303Query.ID); 3460 long rawContactId = cursor.getLong(Upgrade303Query.RAW_CONTACT_ID); 3461 String value = cursor.getString(Upgrade303Query.DATA1); 3462 3463 values.put(NameLookupColumns.DATA_ID, dataId); 3464 values.put(NameLookupColumns.RAW_CONTACT_ID, rawContactId); 3465 values.put(NameLookupColumns.NAME_TYPE, NameLookupType.NICKNAME); 3466 values.put(NameLookupColumns.NORMALIZED_NAME, NameNormalizer.normalize(value)); 3467 db.insert(Tables.NAME_LOOKUP, null, values); 3468 } 3469 } finally { 3470 cursor.close(); 3471 } 3472 } 3473 3474 private void upgradeToVersion304(SQLiteDatabase db) { 3475 // Mimetype table requires an index on mime type. 3476 db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS mime_type ON " + Tables.MIMETYPES + " (" + 3477 MimetypesColumns.MIMETYPE + 3478 ");"); 3479 } 3480 3481 private void upgradeToVersion306(SQLiteDatabase db) { 3482 // Fix invalid lookup that was used for Exchange contacts (it was not escaped) 3483 // It happened when a new contact was created AND synchronized 3484 final StringBuilder lookupKeyBuilder = new StringBuilder(); 3485 final SQLiteStatement updateStatement = db.compileStatement( 3486 "UPDATE contacts " + 3487 "SET lookup=? " + 3488 "WHERE _id=?"); 3489 final Cursor contactIdCursor = db.rawQuery( 3490 "SELECT DISTINCT contact_id " + 3491 "FROM raw_contacts " + 3492 "WHERE deleted=0 AND account_type='com.android.exchange'", 3493 null); 3494 try { 3495 while (contactIdCursor.moveToNext()) { 3496 final long contactId = contactIdCursor.getLong(0); 3497 lookupKeyBuilder.setLength(0); 3498 final Cursor c = db.rawQuery( 3499 "SELECT account_type, account_name, _id, sourceid, display_name " + 3500 "FROM raw_contacts " + 3501 "WHERE contact_id=? " + 3502 "ORDER BY _id", 3503 new String[] {String.valueOf(contactId)}); 3504 try { 3505 while (c.moveToNext()) { 3506 ContactLookupKey.appendToLookupKey(lookupKeyBuilder, 3507 c.getString(0), 3508 c.getString(1), 3509 c.getLong(2), 3510 c.getString(3), 3511 c.getString(4)); 3512 } 3513 } finally { 3514 c.close(); 3515 } 3516 3517 if (lookupKeyBuilder.length() == 0) { 3518 updateStatement.bindNull(1); 3519 } else { 3520 updateStatement.bindString(1, Uri.encode(lookupKeyBuilder.toString())); 3521 } 3522 updateStatement.bindLong(2, contactId); 3523 3524 updateStatement.execute(); 3525 } 3526 } finally { 3527 updateStatement.close(); 3528 contactIdCursor.close(); 3529 } 3530 } 3531 3532 private void upgradeToVersion307(SQLiteDatabase db) { 3533 db.execSQL("CREATE TABLE properties (" + 3534 "property_key TEXT PRIMARY_KEY, " + 3535 "property_value TEXT" + 3536 ");"); 3537 } 3538 3539 private void upgradeToVersion308(SQLiteDatabase db) { 3540 db.execSQL("CREATE TABLE accounts (" + 3541 "account_name TEXT, " + 3542 "account_type TEXT " + 3543 ");"); 3544 3545 db.execSQL("INSERT INTO accounts " + 3546 "SELECT DISTINCT account_name, account_type FROM raw_contacts"); 3547 } 3548 3549 private void upgradeToVersion400(SQLiteDatabase db) { 3550 db.execSQL("ALTER TABLE " + Tables.GROUPS 3551 + " ADD " + Groups.FAVORITES + " INTEGER NOT NULL DEFAULT 0;"); 3552 db.execSQL("ALTER TABLE " + Tables.GROUPS 3553 + " ADD " + Groups.AUTO_ADD + " INTEGER NOT NULL DEFAULT 0;"); 3554 } 3555 3556 private void upgradeToVersion353(SQLiteDatabase db) { 3557 db.execSQL("DELETE FROM contacts " + 3558 "WHERE NOT EXISTS (SELECT 1 FROM raw_contacts WHERE contact_id=contacts._id)"); 3559 } 3560 3561 private void rebuildNameLookup(SQLiteDatabase db, boolean rebuildSqliteStats) { 3562 db.execSQL("DROP INDEX IF EXISTS name_lookup_index"); 3563 insertNameLookup(db); 3564 createContactsIndexes(db, rebuildSqliteStats); 3565 } 3566 3567 protected void rebuildSearchIndex() { 3568 rebuildSearchIndex(getWritableDatabase(), true); 3569 } 3570 3571 private void rebuildSearchIndex(SQLiteDatabase db, boolean rebuildSqliteStats) { 3572 createSearchIndexTable(db, rebuildSqliteStats); 3573 setProperty(db, SearchIndexManager.PROPERTY_SEARCH_INDEX_VERSION, "0"); 3574 } 3575 3576 /** 3577 * Checks whether the current ICU code version matches that used to build 3578 * the locale specific data in the ContactsDB. 3579 */ 3580 public boolean needsToUpdateLocaleData(LocaleSet locales) { 3581 final String dbLocale = getProperty(DbProperties.LOCALE, ""); 3582 if (!dbLocale.equals(locales.toString())) { 3583 return true; 3584 } 3585 final String curICUVersion = ICU.getIcuVersion(); 3586 final String dbICUVersion = getProperty(DbProperties.ICU_VERSION, 3587 "(unknown)"); 3588 if (!curICUVersion.equals(dbICUVersion)) { 3589 Log.i(TAG, "ICU version has changed. Current version is " 3590 + curICUVersion + "; DB was built with " + dbICUVersion); 3591 return true; 3592 } 3593 return false; 3594 } 3595 3596 private void upgradeLocaleData(SQLiteDatabase db, boolean rebuildSqliteStats) { 3597 final LocaleSet locales = LocaleSet.getDefault(); 3598 Log.i(TAG, "Upgrading locale data for " + locales 3599 + " (ICU v" + ICU.getIcuVersion() + ")"); 3600 final long start = SystemClock.elapsedRealtime(); 3601 initializeCache(db); 3602 rebuildLocaleData(db, locales, rebuildSqliteStats); 3603 Log.i(TAG, "Locale update completed in " + (SystemClock.elapsedRealtime() - start) + "ms"); 3604 } 3605 3606 private void rebuildLocaleData(SQLiteDatabase db, LocaleSet locales, boolean rebuildSqliteStats) { 3607 db.execSQL("DROP INDEX raw_contact_sort_key1_index"); 3608 db.execSQL("DROP INDEX raw_contact_sort_key2_index"); 3609 db.execSQL("DROP INDEX IF EXISTS name_lookup_index"); 3610 3611 loadNicknameLookupTable(db); 3612 insertNameLookup(db); 3613 rebuildSortKeys(db); 3614 createContactsIndexes(db, rebuildSqliteStats); 3615 3616 FastScrollingIndexCache.getInstance(mContext).invalidate(); 3617 // Update the ICU version used to generate the locale derived data 3618 // so we can tell when we need to rebuild with new ICU versions. 3619 setProperty(db, DbProperties.ICU_VERSION, ICU.getIcuVersion()); 3620 setProperty(db, DbProperties.LOCALE, locales.toString()); 3621 } 3622 3623 /** 3624 * Regenerates all locale-sensitive data if needed: 3625 * nickname_lookup, name_lookup and sort keys. Invalidates the fast 3626 * scrolling index cache. 3627 */ 3628 public void setLocale(LocaleSet locales) { 3629 if (!needsToUpdateLocaleData(locales)) { 3630 return; 3631 } 3632 Log.i(TAG, "Switching to locale " + locales 3633 + " (ICU v" + ICU.getIcuVersion() + ")"); 3634 3635 final long start = SystemClock.elapsedRealtime(); 3636 SQLiteDatabase db = getWritableDatabase(); 3637 db.setLocale(locales.getPrimaryLocale()); 3638 db.beginTransaction(); 3639 try { 3640 rebuildLocaleData(db, locales, true); 3641 db.setTransactionSuccessful(); 3642 } finally { 3643 db.endTransaction(); 3644 } 3645 3646 Log.i(TAG, "Locale change completed in " + (SystemClock.elapsedRealtime() - start) + "ms"); 3647 } 3648 3649 /** 3650 * Regenerates sort keys for all contacts. 3651 */ 3652 private void rebuildSortKeys(SQLiteDatabase db) { 3653 Cursor cursor = db.query(Tables.RAW_CONTACTS, new String[] {RawContacts._ID}, 3654 null, null, null, null, null); 3655 try { 3656 while (cursor.moveToNext()) { 3657 long rawContactId = cursor.getLong(0); 3658 updateRawContactDisplayName(db, rawContactId); 3659 } 3660 } finally { 3661 cursor.close(); 3662 } 3663 } 3664 3665 private void insertNameLookup(SQLiteDatabase db) { 3666 db.execSQL("DELETE FROM " + Tables.NAME_LOOKUP); 3667 3668 SQLiteStatement nameLookupInsert = db.compileStatement( 3669 "INSERT OR IGNORE INTO " + Tables.NAME_LOOKUP + "(" 3670 + NameLookupColumns.RAW_CONTACT_ID + "," 3671 + NameLookupColumns.DATA_ID + "," 3672 + NameLookupColumns.NAME_TYPE + "," 3673 + NameLookupColumns.NORMALIZED_NAME + 3674 ") VALUES (?,?,?,?)"); 3675 3676 try { 3677 insertStructuredNameLookup(db, nameLookupInsert); 3678 insertEmailLookup(db, nameLookupInsert); 3679 insertNicknameLookup(db, nameLookupInsert); 3680 } finally { 3681 nameLookupInsert.close(); 3682 } 3683 } 3684 3685 /** 3686 * Inserts name lookup rows for all structured names in the database. 3687 */ 3688 private void insertStructuredNameLookup(SQLiteDatabase db, SQLiteStatement nameLookupInsert) { 3689 NameSplitter nameSplitter = createNameSplitter(); 3690 NameLookupBuilder nameLookupBuilder = new StructuredNameLookupBuilder(nameSplitter, 3691 new CommonNicknameCache(db), nameLookupInsert); 3692 final long mimeTypeId = lookupMimeTypeId(db, StructuredName.CONTENT_ITEM_TYPE); 3693 Cursor cursor = db.query(StructuredNameQuery.TABLE, StructuredNameQuery.COLUMNS, 3694 StructuredNameQuery.SELECTION, new String[] {String.valueOf(mimeTypeId)}, 3695 null, null, null); 3696 try { 3697 while (cursor.moveToNext()) { 3698 long dataId = cursor.getLong(StructuredNameQuery.ID); 3699 long rawContactId = cursor.getLong(StructuredNameQuery.RAW_CONTACT_ID); 3700 String name = cursor.getString(StructuredNameQuery.DISPLAY_NAME); 3701 int fullNameStyle = nameSplitter.guessFullNameStyle(name); 3702 fullNameStyle = nameSplitter.getAdjustedFullNameStyle(fullNameStyle); 3703 nameLookupBuilder.insertNameLookup(rawContactId, dataId, name, fullNameStyle); 3704 } 3705 } finally { 3706 cursor.close(); 3707 } 3708 } 3709 3710 /** 3711 * Inserts name lookup rows for all email addresses in the database. 3712 */ 3713 private void insertEmailLookup(SQLiteDatabase db, SQLiteStatement nameLookupInsert) { 3714 final long mimeTypeId = lookupMimeTypeId(db, Email.CONTENT_ITEM_TYPE); 3715 Cursor cursor = db.query(EmailQuery.TABLE, EmailQuery.COLUMNS, 3716 EmailQuery.SELECTION, new String[] {String.valueOf(mimeTypeId)}, 3717 null, null, null); 3718 try { 3719 while (cursor.moveToNext()) { 3720 long dataId = cursor.getLong(EmailQuery.ID); 3721 long rawContactId = cursor.getLong(EmailQuery.RAW_CONTACT_ID); 3722 String address = cursor.getString(EmailQuery.ADDRESS); 3723 address = extractHandleFromEmailAddress(address); 3724 insertNameLookup(nameLookupInsert, rawContactId, dataId, 3725 NameLookupType.EMAIL_BASED_NICKNAME, address); 3726 } 3727 } finally { 3728 cursor.close(); 3729 } 3730 } 3731 3732 /** 3733 * Inserts name lookup rows for all nicknames in the database. 3734 */ 3735 private void insertNicknameLookup(SQLiteDatabase db, SQLiteStatement nameLookupInsert) { 3736 final long mimeTypeId = lookupMimeTypeId(db, Nickname.CONTENT_ITEM_TYPE); 3737 Cursor cursor = db.query(NicknameQuery.TABLE, NicknameQuery.COLUMNS, 3738 NicknameQuery.SELECTION, new String[]{String.valueOf(mimeTypeId)}, 3739 null, null, null); 3740 try { 3741 while (cursor.moveToNext()) { 3742 long dataId = cursor.getLong(NicknameQuery.ID); 3743 long rawContactId = cursor.getLong(NicknameQuery.RAW_CONTACT_ID); 3744 String nickname = cursor.getString(NicknameQuery.NAME); 3745 insertNameLookup(nameLookupInsert, rawContactId, dataId, 3746 NameLookupType.NICKNAME, nickname); 3747 } 3748 } finally { 3749 cursor.close(); 3750 } 3751 } 3752 3753 /** 3754 * Inserts a record in the {@link Tables#NAME_LOOKUP} table. 3755 */ 3756 public void insertNameLookup(SQLiteStatement stmt, long rawContactId, long dataId, 3757 int lookupType, String name) { 3758 if (TextUtils.isEmpty(name)) { 3759 return; 3760 } 3761 3762 String normalized = NameNormalizer.normalize(name); 3763 if (TextUtils.isEmpty(normalized)) { 3764 return; 3765 } 3766 3767 insertNormalizedNameLookup(stmt, rawContactId, dataId, lookupType, normalized); 3768 } 3769 3770 private void insertNormalizedNameLookup(SQLiteStatement stmt, long rawContactId, long dataId, 3771 int lookupType, String normalizedName) { 3772 stmt.bindLong(1, rawContactId); 3773 stmt.bindLong(2, dataId); 3774 stmt.bindLong(3, lookupType); 3775 stmt.bindString(4, normalizedName); 3776 stmt.executeInsert(); 3777 } 3778 3779 /** 3780 * Changing the VISIBLE bit from a field on both RawContacts and Contacts to a separate table. 3781 */ 3782 private void upgradeToVersion401(SQLiteDatabase db) { 3783 db.execSQL("CREATE TABLE " + Tables.VISIBLE_CONTACTS + " (" + 3784 Contacts._ID + " INTEGER PRIMARY KEY" + 3785 ");"); 3786 db.execSQL("INSERT INTO " + Tables.VISIBLE_CONTACTS + 3787 " SELECT " + Contacts._ID + 3788 " FROM " + Tables.CONTACTS + 3789 " WHERE " + Contacts.IN_VISIBLE_GROUP + "!=0"); 3790 db.execSQL("DROP INDEX contacts_visible_index"); 3791 } 3792 3793 /** 3794 * Introducing a new table: directories. 3795 */ 3796 private void upgradeToVersion402(SQLiteDatabase db) { 3797 createDirectoriesTable(db); 3798 } 3799 3800 private void upgradeToVersion403(SQLiteDatabase db) { 3801 db.execSQL("DROP TABLE IF EXISTS directories;"); 3802 createDirectoriesTable(db); 3803 3804 db.execSQL("ALTER TABLE raw_contacts" 3805 + " ADD raw_contact_is_read_only INTEGER NOT NULL DEFAULT 0;"); 3806 3807 db.execSQL("ALTER TABLE data" 3808 + " ADD is_read_only INTEGER NOT NULL DEFAULT 0;"); 3809 } 3810 3811 private void upgradeToVersion405(SQLiteDatabase db) { 3812 db.execSQL("DROP TABLE IF EXISTS phone_lookup;"); 3813 // Private phone numbers table used for lookup 3814 db.execSQL("CREATE TABLE " + Tables.PHONE_LOOKUP + " (" + 3815 PhoneLookupColumns.DATA_ID 3816 + " INTEGER REFERENCES data(_id) NOT NULL," + 3817 PhoneLookupColumns.RAW_CONTACT_ID 3818 + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," + 3819 PhoneLookupColumns.NORMALIZED_NUMBER + " TEXT NOT NULL," + 3820 PhoneLookupColumns.MIN_MATCH + " TEXT NOT NULL" + 3821 ");"); 3822 3823 db.execSQL("CREATE INDEX phone_lookup_index ON " + Tables.PHONE_LOOKUP + " (" + 3824 PhoneLookupColumns.NORMALIZED_NUMBER + "," + 3825 PhoneLookupColumns.RAW_CONTACT_ID + "," + 3826 PhoneLookupColumns.DATA_ID + 3827 ");"); 3828 3829 db.execSQL("CREATE INDEX phone_lookup_min_match_index ON " + Tables.PHONE_LOOKUP + " (" + 3830 PhoneLookupColumns.MIN_MATCH + "," + 3831 PhoneLookupColumns.RAW_CONTACT_ID + "," + 3832 PhoneLookupColumns.DATA_ID + 3833 ");"); 3834 3835 final long mimeTypeId = lookupMimeTypeId(db, Phone.CONTENT_ITEM_TYPE); 3836 if (mimeTypeId == -1) { 3837 return; 3838 } 3839 3840 Cursor cursor = db.rawQuery( 3841 "SELECT _id, " + Phone.RAW_CONTACT_ID + ", " + Phone.NUMBER + 3842 " FROM " + Tables.DATA + 3843 " WHERE " + DataColumns.MIMETYPE_ID + "=" + mimeTypeId 3844 + " AND " + Phone.NUMBER + " NOT NULL", null); 3845 3846 ContentValues phoneValues = new ContentValues(); 3847 try { 3848 while (cursor.moveToNext()) { 3849 long dataID = cursor.getLong(0); 3850 long rawContactID = cursor.getLong(1); 3851 String number = cursor.getString(2); 3852 String normalizedNumber = PhoneNumberUtils.normalizeNumber(number); 3853 if (!TextUtils.isEmpty(normalizedNumber)) { 3854 phoneValues.clear(); 3855 phoneValues.put(PhoneLookupColumns.RAW_CONTACT_ID, rawContactID); 3856 phoneValues.put(PhoneLookupColumns.DATA_ID, dataID); 3857 phoneValues.put(PhoneLookupColumns.NORMALIZED_NUMBER, normalizedNumber); 3858 phoneValues.put(PhoneLookupColumns.MIN_MATCH, 3859 PhoneNumberUtils.toCallerIDMinMatch(normalizedNumber)); 3860 db.insert(Tables.PHONE_LOOKUP, null, phoneValues); 3861 } 3862 } 3863 } finally { 3864 cursor.close(); 3865 } 3866 } 3867 3868 private void upgradeToVersion406(SQLiteDatabase db) { 3869 db.execSQL("ALTER TABLE calls ADD countryiso TEXT;"); 3870 } 3871 3872 private void upgradeToVersion409(SQLiteDatabase db) { 3873 db.execSQL("DROP TABLE IF EXISTS directories;"); 3874 createDirectoriesTable(db); 3875 } 3876 3877 /** 3878 * Adding DEFAULT_DIRECTORY table. 3879 * DEFAULT_DIRECTORY should contain every contact which should be shown to users in default. 3880 * - if a contact doesn't belong to any account (local contact), it should be in 3881 * default_directory 3882 * - if a contact belongs to an account that doesn't have a "default" group, it should be in 3883 * default_directory 3884 * - if a contact belongs to an account that has a "default" group (like Google directory, 3885 * which has "My contacts" group as default), it should be in default_directory. 3886 * 3887 * This logic assumes that accounts with the "default" group should have at least one 3888 * group with AUTO_ADD (implying it is the default group) flag in the groups table. 3889 */ 3890 private void upgradeToVersion411(SQLiteDatabase db) { 3891 db.execSQL("DROP TABLE IF EXISTS " + Tables.DEFAULT_DIRECTORY); 3892 db.execSQL("CREATE TABLE default_directory (_id INTEGER PRIMARY KEY);"); 3893 3894 // Process contacts without an account 3895 db.execSQL("INSERT OR IGNORE INTO default_directory " + 3896 " SELECT contact_id " + 3897 " FROM raw_contacts " + 3898 " WHERE raw_contacts.account_name IS NULL " + 3899 " AND raw_contacts.account_type IS NULL "); 3900 3901 // Process accounts that don't have a default group (e.g. Exchange). 3902 db.execSQL("INSERT OR IGNORE INTO default_directory " + 3903 " SELECT contact_id " + 3904 " FROM raw_contacts " + 3905 " WHERE NOT EXISTS" + 3906 " (SELECT _id " + 3907 " FROM groups " + 3908 " WHERE raw_contacts.account_name = groups.account_name" + 3909 " AND raw_contacts.account_type = groups.account_type" + 3910 " AND groups.auto_add != 0)"); 3911 3912 final long mimetype = lookupMimeTypeId(db, GroupMembership.CONTENT_ITEM_TYPE); 3913 3914 // Process accounts that do have a default group (e.g. Google) 3915 db.execSQL("INSERT OR IGNORE INTO default_directory " + 3916 " SELECT contact_id " + 3917 " FROM raw_contacts " + 3918 " JOIN data " + 3919 " ON (raw_contacts._id=raw_contact_id)" + 3920 " WHERE mimetype_id=" + mimetype + 3921 " AND EXISTS" + 3922 " (SELECT _id" + 3923 " FROM groups" + 3924 " WHERE raw_contacts.account_name = groups.account_name" + 3925 " AND raw_contacts.account_type = groups.account_type" + 3926 " AND groups.auto_add != 0)"); 3927 } 3928 3929 private void upgradeToVersion413(SQLiteDatabase db) { 3930 db.execSQL("DROP TABLE IF EXISTS directories;"); 3931 createDirectoriesTable(db); 3932 } 3933 3934 private void upgradeToVersion415(SQLiteDatabase db) { 3935 db.execSQL( 3936 "ALTER TABLE " + Tables.GROUPS + 3937 " ADD " + Groups.GROUP_IS_READ_ONLY + " INTEGER NOT NULL DEFAULT 0"); 3938 db.execSQL( 3939 "UPDATE " + Tables.GROUPS + 3940 " SET " + Groups.GROUP_IS_READ_ONLY + "=1" + 3941 " WHERE " + Groups.SYSTEM_ID + " NOT NULL"); 3942 } 3943 3944 private void upgradeToVersion416(SQLiteDatabase db) { 3945 db.execSQL("CREATE INDEX phone_lookup_data_id_min_match_index ON " + Tables.PHONE_LOOKUP + 3946 " (" + PhoneLookupColumns.DATA_ID + ", " + PhoneLookupColumns.MIN_MATCH + ");"); 3947 } 3948 3949 private void upgradeToVersion501(SQLiteDatabase db) { 3950 // Remove organization rows from the name lookup, we now use search index for that 3951 db.execSQL("DELETE FROM name_lookup WHERE name_type=5"); 3952 } 3953 3954 private void upgradeToVersion502(SQLiteDatabase db) { 3955 // Remove Chinese and Korean name lookup - this data is now in the search index 3956 db.execSQL("DELETE FROM name_lookup WHERE name_type IN (6, 7)"); 3957 } 3958 3959 private void upgradeToVersion504(SQLiteDatabase db) { 3960 initializeCache(db); 3961 3962 // Find all names with prefixes and recreate display name 3963 Cursor cursor = db.rawQuery( 3964 "SELECT " + StructuredName.RAW_CONTACT_ID + 3965 " FROM " + Tables.DATA + 3966 " WHERE " + DataColumns.MIMETYPE_ID + "=?" 3967 + " AND " + StructuredName.PREFIX + " NOT NULL", 3968 new String[] {String.valueOf(mMimeTypeIdStructuredName)}); 3969 3970 try { 3971 while(cursor.moveToNext()) { 3972 long rawContactId = cursor.getLong(0); 3973 updateRawContactDisplayName(db, rawContactId); 3974 } 3975 3976 } finally { 3977 cursor.close(); 3978 } 3979 } 3980 3981 private void upgradeToVersion601(SQLiteDatabase db) { 3982 db.execSQL("CREATE TABLE data_usage_stat(" + 3983 "stat_id INTEGER PRIMARY KEY AUTOINCREMENT, " + 3984 "data_id INTEGER NOT NULL, " + 3985 "usage_type INTEGER NOT NULL DEFAULT 0, " + 3986 "times_used INTEGER NOT NULL DEFAULT 0, " + 3987 "last_time_used INTERGER NOT NULL DEFAULT 0, " + 3988 "FOREIGN KEY(data_id) REFERENCES data(_id));"); 3989 db.execSQL("CREATE UNIQUE INDEX data_usage_stat_index ON " + 3990 "data_usage_stat (data_id, usage_type)"); 3991 } 3992 3993 private void upgradeToVersion602(SQLiteDatabase db) { 3994 db.execSQL("ALTER TABLE calls ADD voicemail_uri TEXT;"); 3995 db.execSQL("ALTER TABLE calls ADD _data TEXT;"); 3996 db.execSQL("ALTER TABLE calls ADD has_content INTEGER;"); 3997 db.execSQL("ALTER TABLE calls ADD mime_type TEXT;"); 3998 db.execSQL("ALTER TABLE calls ADD source_data TEXT;"); 3999 db.execSQL("ALTER TABLE calls ADD source_package TEXT;"); 4000 db.execSQL("ALTER TABLE calls ADD state INTEGER;"); 4001 } 4002 4003 private void upgradeToVersion604(SQLiteDatabase db) { 4004 db.execSQL("CREATE TABLE voicemail_status (" + 4005 "_id INTEGER PRIMARY KEY AUTOINCREMENT," + 4006 "source_package TEXT UNIQUE NOT NULL," + 4007 "settings_uri TEXT," + 4008 "voicemail_access_uri TEXT," + 4009 "configuration_state INTEGER," + 4010 "data_channel_state INTEGER," + 4011 "notification_channel_state INTEGER" + 4012 ");"); 4013 } 4014 4015 private void upgradeToVersion606(SQLiteDatabase db) { 4016 db.execSQL("DROP VIEW IF EXISTS view_contacts_restricted;"); 4017 db.execSQL("DROP VIEW IF EXISTS view_data_restricted;"); 4018 db.execSQL("DROP VIEW IF EXISTS view_raw_contacts_restricted;"); 4019 db.execSQL("DROP VIEW IF EXISTS view_raw_entities_restricted;"); 4020 db.execSQL("DROP VIEW IF EXISTS view_entities_restricted;"); 4021 db.execSQL("DROP VIEW IF EXISTS view_data_usage_stat_restricted;"); 4022 db.execSQL("DROP INDEX IF EXISTS contacts_restricted_index"); 4023 4024 // We should remove the restricted columns here as well, but unfortunately SQLite doesn't 4025 // provide ALTER TABLE DROP COLUMN. As they have DEFAULT 0, we can keep but ignore them 4026 } 4027 4028 private void upgradeToVersion608(SQLiteDatabase db) { 4029 db.execSQL("ALTER TABLE contacts ADD photo_file_id INTEGER REFERENCES photo_files(_id);"); 4030 4031 db.execSQL("CREATE TABLE photo_files(" + 4032 "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + 4033 "height INTEGER NOT NULL, " + 4034 "width INTEGER NOT NULL, " + 4035 "filesize INTEGER NOT NULL);"); 4036 } 4037 4038 private void upgradeToVersion610(SQLiteDatabase db) { 4039 db.execSQL("ALTER TABLE calls ADD is_read INTEGER;"); 4040 } 4041 4042 private void upgradeToVersion611(SQLiteDatabase db) { 4043 db.execSQL("ALTER TABLE raw_contacts ADD data_set TEXT DEFAULT NULL;"); 4044 db.execSQL("ALTER TABLE groups ADD data_set TEXT DEFAULT NULL;"); 4045 db.execSQL("ALTER TABLE accounts ADD data_set TEXT DEFAULT NULL;"); 4046 4047 db.execSQL("CREATE INDEX raw_contacts_source_id_data_set_index ON raw_contacts " + 4048 "(sourceid, account_type, account_name, data_set);"); 4049 4050 db.execSQL("CREATE INDEX groups_source_id_data_set_index ON groups " + 4051 "(sourceid, account_type, account_name, data_set);"); 4052 } 4053 4054 private void upgradeToVersion612(SQLiteDatabase db) { 4055 db.execSQL("ALTER TABLE calls ADD geocoded_location TEXT DEFAULT NULL;"); 4056 // Old calls will not have a geocoded location; new calls will get it when inserted. 4057 } 4058 4059 private void upgradeToVersion613(SQLiteDatabase db) { 4060 // The stream item and stream item photos APIs were not in-use by anyone in the time 4061 // between their initial creation (in v609) and this update. So we're just dropping 4062 // and re-creating them to get appropriate columns. The delta is as follows: 4063 // - In stream_items, package_id was replaced by res_package. 4064 // - In stream_item_photos, picture was replaced by photo_file_id. 4065 // - Instead of resource IDs for icon and label, we use resource name strings now 4066 // - Added sync columns 4067 // - Removed action and action_uri 4068 // - Text and comments are now nullable 4069 4070 db.execSQL("DROP TABLE IF EXISTS stream_items"); 4071 db.execSQL("DROP TABLE IF EXISTS stream_item_photos"); 4072 4073 db.execSQL("CREATE TABLE stream_items(" + 4074 "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + 4075 "raw_contact_id INTEGER NOT NULL, " + 4076 "res_package TEXT, " + 4077 "icon TEXT, " + 4078 "label TEXT, " + 4079 "text TEXT, " + 4080 "timestamp INTEGER NOT NULL, " + 4081 "comments TEXT, " + 4082 "stream_item_sync1 TEXT, " + 4083 "stream_item_sync2 TEXT, " + 4084 "stream_item_sync3 TEXT, " + 4085 "stream_item_sync4 TEXT, " + 4086 "FOREIGN KEY(raw_contact_id) REFERENCES raw_contacts(_id));"); 4087 4088 db.execSQL("CREATE TABLE stream_item_photos(" + 4089 "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + 4090 "stream_item_id INTEGER NOT NULL, " + 4091 "sort_index INTEGER, " + 4092 "photo_file_id INTEGER NOT NULL, " + 4093 "stream_item_photo_sync1 TEXT, " + 4094 "stream_item_photo_sync2 TEXT, " + 4095 "stream_item_photo_sync3 TEXT, " + 4096 "stream_item_photo_sync4 TEXT, " + 4097 "FOREIGN KEY(stream_item_id) REFERENCES stream_items(_id));"); 4098 } 4099 4100 private void upgradeToVersion615(SQLiteDatabase db) { 4101 // Old calls will not have up to date values for these columns, they will be filled in 4102 // as needed. 4103 db.execSQL("ALTER TABLE calls ADD lookup_uri TEXT DEFAULT NULL;"); 4104 db.execSQL("ALTER TABLE calls ADD matched_number TEXT DEFAULT NULL;"); 4105 db.execSQL("ALTER TABLE calls ADD normalized_number TEXT DEFAULT NULL;"); 4106 db.execSQL("ALTER TABLE calls ADD photo_id INTEGER NOT NULL DEFAULT 0;"); 4107 } 4108 4109 private void upgradeToVersion618(SQLiteDatabase db) { 4110 // The Settings table needs a data_set column which technically should be part of the 4111 // primary key but can't be because it may be null. Since SQLite doesn't support nuking 4112 // the primary key, we'll drop the old table, re-create it, and copy the settings back in. 4113 db.execSQL("CREATE TEMPORARY TABLE settings_backup(" + 4114 "account_name STRING NOT NULL," + 4115 "account_type STRING NOT NULL," + 4116 "ungrouped_visible INTEGER NOT NULL DEFAULT 0," + 4117 "should_sync INTEGER NOT NULL DEFAULT 1" + 4118 ");"); 4119 db.execSQL("INSERT INTO settings_backup " + 4120 "SELECT account_name, account_type, ungrouped_visible, should_sync" + 4121 " FROM settings"); 4122 db.execSQL("DROP TABLE settings"); 4123 db.execSQL("CREATE TABLE settings (" + 4124 "account_name STRING NOT NULL," + 4125 "account_type STRING NOT NULL," + 4126 "data_set STRING," + 4127 "ungrouped_visible INTEGER NOT NULL DEFAULT 0," + 4128 "should_sync INTEGER NOT NULL DEFAULT 1" + 4129 ");"); 4130 db.execSQL("INSERT INTO settings " + 4131 "SELECT account_name, account_type, NULL, ungrouped_visible, should_sync " + 4132 "FROM settings_backup"); 4133 db.execSQL("DROP TABLE settings_backup"); 4134 } 4135 4136 private void upgradeToVersion622(SQLiteDatabase db) { 4137 db.execSQL("ALTER TABLE calls ADD formatted_number TEXT DEFAULT NULL;"); 4138 } 4139 4140 private void upgradeToVersion626(SQLiteDatabase db) { 4141 db.execSQL("DROP TABLE IF EXISTS accounts"); 4142 4143 db.execSQL("CREATE TABLE accounts (" + 4144 "_id INTEGER PRIMARY KEY AUTOINCREMENT," + 4145 "account_name TEXT, " + 4146 "account_type TEXT, " + 4147 "data_set TEXT" + 4148 ");"); 4149 4150 // Add "account_id" column to groups and raw_contacts 4151 db.execSQL("ALTER TABLE raw_contacts ADD " + 4152 "account_id INTEGER REFERENCES accounts(_id)"); 4153 db.execSQL("ALTER TABLE groups ADD " + 4154 "account_id INTEGER REFERENCES accounts(_id)"); 4155 4156 // Update indexes. 4157 db.execSQL("DROP INDEX IF EXISTS raw_contacts_source_id_index"); 4158 db.execSQL("DROP INDEX IF EXISTS raw_contacts_source_id_data_set_index"); 4159 db.execSQL("DROP INDEX IF EXISTS groups_source_id_index"); 4160 db.execSQL("DROP INDEX IF EXISTS groups_source_id_data_set_index"); 4161 4162 db.execSQL("CREATE INDEX raw_contacts_source_id_account_id_index ON raw_contacts (" 4163 + "sourceid, account_id);"); 4164 db.execSQL("CREATE INDEX groups_source_id_account_id_index ON groups (" 4165 + "sourceid, account_id);"); 4166 4167 // Migrate account_name/account_type/data_set to accounts table 4168 4169 final Set<AccountWithDataSet> accountsWithDataSets = Sets.newHashSet(); 4170 upgradeToVersion626_findAccountsWithDataSets(accountsWithDataSets, db, "raw_contacts"); 4171 upgradeToVersion626_findAccountsWithDataSets(accountsWithDataSets, db, "groups"); 4172 4173 for (AccountWithDataSet accountWithDataSet : accountsWithDataSets) { 4174 db.execSQL("INSERT INTO accounts (account_name,account_type,data_set)VALUES(?, ?, ?)", 4175 new String[] { 4176 accountWithDataSet.getAccountName(), 4177 accountWithDataSet.getAccountType(), 4178 accountWithDataSet.getDataSet() 4179 }); 4180 } 4181 upgradeToVersion626_fillAccountId(db, "raw_contacts"); 4182 upgradeToVersion626_fillAccountId(db, "groups"); 4183 } 4184 4185 private static void upgradeToVersion626_findAccountsWithDataSets( 4186 Set<AccountWithDataSet> result, SQLiteDatabase db, String table) { 4187 Cursor c = db.rawQuery( 4188 "SELECT DISTINCT account_name, account_type, data_set FROM " + table, null); 4189 try { 4190 while (c.moveToNext()) { 4191 result.add(AccountWithDataSet.get(c.getString(0), c.getString(1), c.getString(2))); 4192 } 4193 } finally { 4194 c.close(); 4195 } 4196 } 4197 4198 private static void upgradeToVersion626_fillAccountId(SQLiteDatabase db, String table) { 4199 StringBuilder sb = new StringBuilder(); 4200 4201 // Set account_id and null out account_name, account_type and data_set 4202 4203 sb.append("UPDATE " + table + " SET account_id = (SELECT _id FROM accounts WHERE "); 4204 4205 addJoinExpressionAllowingNull(sb, table + ".account_name", "accounts.account_name"); 4206 sb.append("AND"); 4207 addJoinExpressionAllowingNull(sb, table + ".account_type", "accounts.account_type"); 4208 sb.append("AND"); 4209 addJoinExpressionAllowingNull(sb, table + ".data_set", "accounts.data_set"); 4210 4211 sb.append("), account_name = null, account_type = null, data_set = null"); 4212 db.execSQL(sb.toString()); 4213 } 4214 4215 private void upgradeToVersion701(SQLiteDatabase db) { 4216 db.execSQL("UPDATE raw_contacts SET last_time_contacted =" + 4217 " max(ifnull(last_time_contacted, 0), " + 4218 " ifnull((SELECT max(last_time_used) " + 4219 " FROM data JOIN data_usage_stat ON (data._id = data_usage_stat.data_id)" + 4220 " WHERE data.raw_contact_id = raw_contacts._id), 0))"); 4221 // Replace 0 with null. This isn't really necessary, but we do this anyway for consistency. 4222 db.execSQL("UPDATE raw_contacts SET last_time_contacted = null" + 4223 " where last_time_contacted = 0"); 4224 } 4225 4226 /** 4227 * Pre-HC devices don't have correct "NORMALIZED_NUMBERS". Clear them up. 4228 */ 4229 private void upgradeToVersion702(SQLiteDatabase db) { 4230 // All the "correct" Phone.NORMALIZED_NUMBERS should begin with "+". The upgraded data 4231 // don't. Find all Phone.NORMALIZED_NUMBERS that don't begin with "+". 4232 final int count; 4233 final long[] dataIds; 4234 final long[] rawContactIds; 4235 final String[] phoneNumbers; 4236 final StringBuilder sbDataIds; 4237 final Cursor c = db.rawQuery( 4238 "SELECT _id, raw_contact_id, data1 FROM data " + 4239 " WHERE mimetype_id=" + 4240 "(SELECT _id FROM mimetypes" + 4241 " WHERE mimetype='vnd.android.cursor.item/phone_v2')" + 4242 " AND data4 not like '+%'", // "Not like" will exclude nulls too. 4243 null); 4244 try { 4245 count = c.getCount(); 4246 if (count == 0) { 4247 return; 4248 } 4249 dataIds = new long[count]; 4250 rawContactIds = new long[count]; 4251 phoneNumbers = new String[count]; 4252 sbDataIds = new StringBuilder(); 4253 4254 c.moveToPosition(-1); 4255 while (c.moveToNext()) { 4256 final int i = c.getPosition(); 4257 dataIds[i] = c.getLong(0); 4258 rawContactIds[i] = c.getLong(1); 4259 phoneNumbers[i] = c.getString(2); 4260 4261 if (sbDataIds.length() > 0) { 4262 sbDataIds.append(","); 4263 } 4264 sbDataIds.append(dataIds[i]); 4265 } 4266 } finally { 4267 c.close(); 4268 } 4269 4270 final String dataIdList = sbDataIds.toString(); 4271 4272 // Then, update the Data and PhoneLookup tables. 4273 4274 // First, just null out all Phone.NORMALIZED_NUMBERS for those. 4275 db.execSQL("UPDATE data SET data4 = null" + 4276 " WHERE _id IN (" + dataIdList + ")"); 4277 4278 // Then, re-create phone_lookup for them. 4279 db.execSQL("DELETE FROM phone_lookup" + 4280 " WHERE data_id IN (" + dataIdList + ")"); 4281 4282 for (int i = 0; i < count; i++) { 4283 // Mimic how DataRowHandlerForPhoneNumber.insert() works when it can't normalize 4284 // numbers. 4285 final String phoneNumber = phoneNumbers[i]; 4286 if (TextUtils.isEmpty(phoneNumber)) continue; 4287 4288 final String normalized = PhoneNumberUtils.normalizeNumber(phoneNumber); 4289 if (!TextUtils.isEmpty(normalized)) { 4290 db.execSQL("INSERT INTO phone_lookup" + 4291 "(data_id, raw_contact_id, normalized_number, min_match)" + 4292 " VALUES(?,?,?,?)", 4293 new String[] { 4294 String.valueOf(dataIds[i]), 4295 String.valueOf(rawContactIds[i]), 4296 normalized, 4297 PhoneNumberUtils.toCallerIDMinMatch(normalized)}); 4298 } 4299 } 4300 } 4301 4302 private void upgradeToVersion707(SQLiteDatabase db) { 4303 db.execSQL("ALTER TABLE raw_contacts ADD phonebook_label TEXT;"); 4304 db.execSQL("ALTER TABLE raw_contacts ADD phonebook_bucket INTEGER;"); 4305 db.execSQL("ALTER TABLE raw_contacts ADD phonebook_label_alt TEXT;"); 4306 db.execSQL("ALTER TABLE raw_contacts ADD phonebook_bucket_alt INTEGER;"); 4307 } 4308 4309 private void upgradeToVersion710(SQLiteDatabase db) { 4310 4311 // Adding timestamp to contacts table. 4312 db.execSQL("ALTER TABLE contacts" 4313 + " ADD contact_last_updated_timestamp INTEGER;"); 4314 4315 db.execSQL("UPDATE contacts" 4316 + " SET contact_last_updated_timestamp" 4317 + " = " + System.currentTimeMillis()); 4318 4319 db.execSQL("CREATE INDEX contacts_contact_last_updated_timestamp_index " 4320 + "ON contacts(contact_last_updated_timestamp)"); 4321 4322 // New deleted contacts table. 4323 db.execSQL("CREATE TABLE deleted_contacts (" + 4324 "contact_id INTEGER PRIMARY KEY," + 4325 "contact_deleted_timestamp INTEGER NOT NULL default 0" 4326 + ");"); 4327 4328 db.execSQL("CREATE INDEX deleted_contacts_contact_deleted_timestamp_index " 4329 + "ON deleted_contacts(contact_deleted_timestamp)"); 4330 } 4331 4332 private void upgradeToVersion800(SQLiteDatabase db) { 4333 // Default Calls.PRESENTATION_ALLOWED=1 4334 db.execSQL("ALTER TABLE calls ADD presentation INTEGER NOT NULL DEFAULT 1;"); 4335 4336 // Re-map CallerInfo.{..}_NUMBER strings to Calls.PRESENTATION_{..} ints 4337 // PRIVATE_NUMBER="-2" -> PRESENTATION_RESTRICTED=2 4338 // UNKNOWN_NUMBER="-1" -> PRESENTATION_UNKNOWN =3 4339 // PAYPHONE_NUMBER="-3" -> PRESENTATION_PAYPHONE =4 4340 db.execSQL("UPDATE calls SET presentation=2, number='' WHERE number='-2';"); 4341 db.execSQL("UPDATE calls SET presentation=3, number='' WHERE number='-1';"); 4342 db.execSQL("UPDATE calls SET presentation=4, number='' WHERE number='-3';"); 4343 } 4344 4345 private void upgradeToVersion802(SQLiteDatabase db) { 4346 db.execSQL("ALTER TABLE contacts ADD pinned INTEGER NOT NULL DEFAULT " + 4347 ContactsContract.PinnedPositions.UNPINNED + ";"); 4348 db.execSQL("ALTER TABLE raw_contacts ADD pinned INTEGER NOT NULL DEFAULT " + 4349 ContactsContract.PinnedPositions.UNPINNED + ";"); 4350 } 4351 4352 private void upgradeToVersion902(SQLiteDatabase db) { 4353 // adding account identifier to call log table 4354 db.execSQL("ALTER TABLE calls ADD subscription_component_name TEXT;"); 4355 db.execSQL("ALTER TABLE calls ADD subscription_id TEXT;"); 4356 } 4357 4358 /** 4359 * Searches for any calls in the call log with no normalized phone number and attempts to add 4360 * one if the number can be normalized. 4361 * 4362 * @param db The database. 4363 */ 4364 private void upgradeToVersion903(SQLiteDatabase db) { 4365 // Find the calls in the call log with no normalized phone number. 4366 final Cursor c = db.rawQuery( 4367 "SELECT _id, number, countryiso FROM calls " + 4368 " WHERE (normalized_number is null OR normalized_number = '') " + 4369 " AND countryiso != '' AND countryiso is not null " + 4370 " AND number != '' AND number is not null;", 4371 null 4372 ); 4373 4374 try { 4375 if (c.getCount() == 0) { 4376 return; 4377 } 4378 4379 db.beginTransaction(); 4380 try { 4381 c.moveToPosition(-1); 4382 while (c.moveToNext()) { 4383 final long callId = c.getLong(0); 4384 final String unNormalizedNumber = c.getString(1); 4385 final String countryIso = c.getString(2); 4386 4387 // Attempt to get normalized number. 4388 String normalizedNumber = PhoneNumberUtils 4389 .formatNumberToE164(unNormalizedNumber, countryIso); 4390 4391 if (!TextUtils.isEmpty(normalizedNumber)) { 4392 db.execSQL("UPDATE calls set normalized_number = ? " + 4393 "where _id = ?;", 4394 new String[]{ 4395 normalizedNumber, 4396 String.valueOf(callId), 4397 } 4398 ); 4399 } 4400 } 4401 4402 db.setTransactionSuccessful(); 4403 } finally { 4404 db.endTransaction(); 4405 } 4406 } finally { 4407 c.close(); 4408 } 4409 } 4410 4411 /** 4412 * Updates the calls table in the database to include the call_duration and features columns. 4413 * @param db The database to update. 4414 */ 4415 private void upgradeToVersion904(SQLiteDatabase db) { 4416 db.execSQL("ALTER TABLE calls ADD features INTEGER NOT NULL DEFAULT 0;"); 4417 db.execSQL("ALTER TABLE calls ADD data_usage INTEGER;"); 4418 } 4419 4420 /** 4421 * Adds the voicemail transcription to the Table.Calls 4422 */ 4423 private void upgradeToVersion905(SQLiteDatabase db) { 4424 db.execSQL("ALTER TABLE calls ADD transcription TEXT;"); 4425 } 4426 4427 /** 4428 * Upgrades the database with the new value for {@link PinnedPositions#UNPINNED}. In this 4429 * database version upgrade, the value is changed from 2147483647 (Integer.MAX_VALUE) to 0. 4430 * 4431 * The first pinned contact now starts from position 1. 4432 */ 4433 @VisibleForTesting 4434 public void upgradeToVersion906(SQLiteDatabase db) { 4435 db.execSQL("UPDATE contacts SET pinned = pinned + 1" 4436 + " WHERE pinned >= 0 AND pinned < 2147483647;"); 4437 db.execSQL("UPDATE raw_contacts SET pinned = pinned + 1" 4438 + " WHERE pinned >= 0 AND pinned < 2147483647;"); 4439 4440 db.execSQL("UPDATE contacts SET pinned = 0" 4441 + " WHERE pinned = 2147483647;"); 4442 db.execSQL("UPDATE raw_contacts SET pinned = 0" 4443 + " WHERE pinned = 2147483647;"); 4444 } 4445 4446 private void upgradeToVersion908(SQLiteDatabase db) { 4447 db.execSQL("UPDATE contacts SET pinned = 0 WHERE pinned = 2147483647;"); 4448 db.execSQL("UPDATE raw_contacts SET pinned = 0 WHERE pinned = 2147483647;"); 4449 } 4450 4451 private void upgradeToVersion909(SQLiteDatabase db) { 4452 try { 4453 db.execSQL("ALTER TABLE calls ADD sub_id INTEGER DEFAULT -1;"); 4454 } catch (SQLiteException e) { 4455 // The column already exists--copy over data 4456 db.execSQL("UPDATE calls SET subscription_component_name='com.android.phone/" 4457 + "com.android.services.telephony.TelephonyConnectionService';"); 4458 db.execSQL("UPDATE calls SET subscription_id=sub_id;"); 4459 } 4460 } 4461 4462 /** 4463 * Delete any remaining rows in the calls table if the user is a profile of another user. 4464 * b/17096027 4465 */ 4466 @VisibleForTesting 4467 public void upgradeToVersion910(SQLiteDatabase db) { 4468 final UserManager userManager = (UserManager) mContext.getSystemService( 4469 Context.USER_SERVICE); 4470 final UserInfo user = userManager.getUserInfo(userManager.getUserHandle()); 4471 if (user.isManagedProfile()) { 4472 db.execSQL("DELETE FROM calls;"); 4473 } 4474 } 4475 4476 /** 4477 * Add backup_id column to raw_contacts table and hash_id column to data table. 4478 */ 4479 private void upgradeToVersion1000(SQLiteDatabase db) { 4480 db.execSQL("ALTER TABLE raw_contacts ADD backup_id TEXT;"); 4481 db.execSQL("ALTER TABLE data ADD hash_id TEXT;"); 4482 db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS raw_contacts_backup_id_account_id_index ON " + 4483 "raw_contacts (backup_id, account_id);"); 4484 db.execSQL("CREATE INDEX IF NOT EXISTS data_hash_id_index ON data (hash_id);"); 4485 } 4486 4487 @VisibleForTesting 4488 public void upgradeToVersion1002(SQLiteDatabase db) { 4489 db.execSQL("DROP TABLE IF EXISTS pre_authorized_uris;"); 4490 db.execSQL("CREATE TABLE pre_authorized_uris ("+ 4491 "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + 4492 "uri STRING NOT NULL, " + 4493 "expiration INTEGER NOT NULL DEFAULT 0);"); 4494 } 4495 4496 public void upgradeToVersion1003(SQLiteDatabase db) { 4497 db.execSQL("ALTER TABLE calls ADD phone_account_address TEXT;"); 4498 4499 // After version 1003, we are using the ICC ID as the phone-account ID. This code updates 4500 // any existing telephony connection-service calllog entries to the ICC ID from the 4501 // previously used subscription ID. 4502 // TODO: This is inconsistent, depending on the initialization state of SubscriptionManager. 4503 // Sometimes it returns zero subscriptions. May want to move this upgrade to run after 4504 // ON_BOOT_COMPLETE instead of PRE_BOOT_COMPLETE. 4505 SubscriptionManager sm = SubscriptionManager.from(mContext); 4506 if (sm != null) { 4507 Log.i(TAG, "count: " + sm.getAllSubscriptionInfoCount()); 4508 for (SubscriptionInfo info : sm.getAllSubscriptionInfoList()) { 4509 String iccId = info.getIccId(); 4510 int subId = info.getSubscriptionId(); 4511 if (!TextUtils.isEmpty(iccId) && 4512 subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 4513 StringBuilder sb = new StringBuilder(); 4514 sb.append("UPDATE calls SET subscription_id="); 4515 DatabaseUtils.appendEscapedSQLString(sb, iccId); 4516 sb.append(" WHERE subscription_id="); 4517 sb.append(subId); 4518 sb.append(" AND subscription_component_name='com.android.phone/" 4519 + "com.android.services.telephony.TelephonyConnectionService';"); 4520 4521 db.execSQL(sb.toString()); 4522 } 4523 } 4524 } 4525 } 4526 4527 /** 4528 * Add a "hidden" column for call log entries we want to hide after an upgrade until the user 4529 * adds the right phone account to the device. 4530 */ 4531 public void upgradeToVersion1004(SQLiteDatabase db) { 4532 db.execSQL("ALTER TABLE calls ADD phone_account_hidden INTEGER NOT NULL DEFAULT 0;"); 4533 } 4534 4535 public void upgradeToVersion1005(SQLiteDatabase db) { 4536 db.execSQL("ALTER TABLE calls ADD photo_uri TEXT;"); 4537 } 4538 4539 /** 4540 * The try/catch pattern exists because some devices have the upgrade and some do not. This is 4541 * because the below updates were merged into version 1005 after some devices had already 4542 * upgraded to version 1005 and hence did not receive the below upgrades. 4543 */ 4544 public void upgradeToVersion1007(SQLiteDatabase db) { 4545 try { 4546 // Add multi-sim fields 4547 db.execSQL("ALTER TABLE voicemail_status ADD phone_account_component_name TEXT;"); 4548 db.execSQL("ALTER TABLE voicemail_status ADD phone_account_id TEXT;"); 4549 4550 // For use by the sync adapter 4551 db.execSQL("ALTER TABLE calls ADD dirty INTEGER NOT NULL DEFAULT 0;"); 4552 db.execSQL("ALTER TABLE calls ADD deleted INTEGER NOT NULL DEFAULT 0;"); 4553 } catch (SQLiteException e) { 4554 // These columns already exist. Do nothing. 4555 // Log verbose because this should be the majority case. 4556 Log.v(TAG, "Version 1007: Columns already exist, skipping upgrade steps."); 4557 } 4558 } 4559 4560 4561 public void upgradeToVersion1009(SQLiteDatabase db) { 4562 try { 4563 db.execSQL("ALTER TABLE data ADD carrier_presence INTEGER NOT NULL DEFAULT 0"); 4564 } catch (SQLiteException ignore) { 4565 } 4566 } 4567 4568 private void upgradeToVersion1100(SQLiteDatabase db) { 4569 db.execSQL("ALTER TABLE raw_contacts ADD metadata_dirty INTEGER NOT NULL DEFAULT 0;"); 4570 } 4571 4572 // Data.hash_id column is used for metadata backup, and this upgrade is to generate 4573 // hash_id column. Usually data1 and data2 are two main columns to store data info. 4574 // But for photo, we don't use data1 and data2, instead, use data15 to store photo blob. 4575 // So this upgrade generates hash_id from (data1 + data2) or (data15) using sha-1. 4576 public void upgradeToVersion1101(SQLiteDatabase db) { 4577 final SQLiteStatement update = db.compileStatement( 4578 "UPDATE " + Tables.DATA + 4579 " SET " + Data.HASH_ID + "=?" + 4580 " WHERE " + Data._ID + "=?" 4581 ); 4582 final Cursor c = db.query(Tables.DATA, 4583 new String[] {Data._ID, Data.DATA1, Data.DATA2, Data.DATA15}, 4584 null, null, null, null, Data._ID); 4585 try { 4586 while (c.moveToNext()) { 4587 final long dataId = c.getLong(0); 4588 final String data1 = c.getString(1); 4589 final String data2 = c.getString(2); 4590 final byte[] data15 = c.getBlob(3); 4591 final String hashId = generateHashId(data1, data2, data15); 4592 if (!TextUtils.isEmpty(hashId)) { 4593 update.bindString(1, hashId); 4594 update.bindLong(2, dataId); 4595 update.execute(); 4596 } 4597 } 4598 } finally { 4599 c.close(); 4600 } 4601 } 4602 4603 /** 4604 * Add new metadata_sync table to cache the meta data on raw contacts level from server before 4605 * they are merged into other CP2 tables. The data column is the blob column containing all 4606 * the backed up metadata for this raw_contact. This table should only be used by metadata 4607 * sync adapter. 4608 */ 4609 public void upgradeToVersion1104(SQLiteDatabase db) { 4610 db.execSQL("DROP TABLE IF EXISTS metadata_sync;"); 4611 db.execSQL("CREATE TABLE metadata_sync (" + 4612 "_id INTEGER PRIMARY KEY AUTOINCREMENT, raw_contact_backup_id TEXT NOT NULL, " + 4613 "account_id INTEGER NOT NULL, data TEXT, deleted INTEGER NOT NULL DEFAULT 0);"); 4614 db.execSQL("CREATE UNIQUE INDEX metadata_sync_index ON metadata_sync (" + 4615 "raw_contact_backup_id, account_id);"); 4616 } 4617 4618 /** 4619 * Add new metadata_sync_state table to store the metadata sync state for a set of accounts. 4620 */ 4621 public void upgradeToVersion1105(SQLiteDatabase db) { 4622 db.execSQL("DROP TABLE IF EXISTS metadata_sync_state;"); 4623 db.execSQL("CREATE TABLE metadata_sync_state (" + 4624 "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + 4625 "account_id INTEGER NOT NULL, state BLOB);"); 4626 db.execSQL("CREATE UNIQUE INDEX metadata_sync_state_index ON metadata_sync_state (" + 4627 "account_id);"); 4628 } 4629 4630 public void upgradeToVersion1106(SQLiteDatabase db) { 4631 db.execSQL("ALTER TABLE calls ADD post_dial_digits TEXT NOT NULL DEFAULT ''"); 4632 } 4633 4634 public void upgradeToVersion1107(SQLiteDatabase db) { 4635 try { 4636 db.execSQL("ALTER TABLE calls ADD post_dial_digits TEXT NOT NULL DEFAULT ''"); 4637 } catch (SQLiteException ignore) { 4638 // This is for devices which got initialized without a post_dial_digits 4639 // column from version 1106. The exception indicates that the column is 4640 // already present, so nothing needs to be done. 4641 } 4642 } 4643 4644 /** 4645 * Generate hash_id from data1, data2 and data15 columns. 4646 * If one of data1 and data2 is not null, using data1 and data2 to get hash_id, 4647 * otherwise, using data15 to generate. 4648 */ 4649 public String generateHashId(String data1, String data2, byte[] data15) { 4650 final StringBuilder sb = new StringBuilder(); 4651 byte[] hashInput = null; 4652 if (!TextUtils.isEmpty(data1) || !TextUtils.isEmpty(data2)) { 4653 sb.append(data1); 4654 sb.append(data2); 4655 hashInput = sb.toString().getBytes(); 4656 } else if (data15 != null) { 4657 hashInput = data15; 4658 } 4659 if (hashInput != null) { 4660 final String hashId = generateHashIdForData(hashInput); 4661 return hashId; 4662 } else { 4663 return null; 4664 } 4665 } 4666 4667 // Use SHA-1 hash method to generate hash string for the input. 4668 @VisibleForTesting 4669 String generateHashIdForData(byte[] input) { 4670 synchronized (mMessageDigest) { 4671 final byte[] hashResult = mMessageDigest.digest(input); 4672 return Base64.encodeToString(hashResult, Base64.DEFAULT); 4673 } 4674 } 4675 4676 public String extractHandleFromEmailAddress(String email) { 4677 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email); 4678 if (tokens.length == 0) { 4679 return null; 4680 } 4681 4682 String address = tokens[0].getAddress(); 4683 int index = address.indexOf('@'); 4684 if (index != -1) { 4685 return address.substring(0, index); 4686 } 4687 return null; 4688 } 4689 4690 public String extractAddressFromEmailAddress(String email) { 4691 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email); 4692 if (tokens.length == 0) { 4693 return null; 4694 } 4695 return tokens[0].getAddress().trim(); 4696 } 4697 4698 private static long lookupMimeTypeId(SQLiteDatabase db, String mimeType) { 4699 try { 4700 return DatabaseUtils.longForQuery(db, 4701 "SELECT " + MimetypesColumns._ID + 4702 " FROM " + Tables.MIMETYPES + 4703 " WHERE " + MimetypesColumns.MIMETYPE 4704 + "='" + mimeType + "'", null); 4705 } catch (SQLiteDoneException e) { 4706 // No rows of this type in the database. 4707 return -1; 4708 } 4709 } 4710 4711 private void bindString(SQLiteStatement stmt, int index, String value) { 4712 if (value == null) { 4713 stmt.bindNull(index); 4714 } else { 4715 stmt.bindString(index, value); 4716 } 4717 } 4718 4719 private void bindLong(SQLiteStatement stmt, int index, Number value) { 4720 if (value == null) { 4721 stmt.bindNull(index); 4722 } else { 4723 stmt.bindLong(index, value.longValue()); 4724 } 4725 } 4726 4727 /** 4728 * Add a string like "(((column1) = (column2)) OR ((column1) IS NULL AND (column2) IS NULL))" 4729 */ 4730 private static StringBuilder addJoinExpressionAllowingNull( 4731 StringBuilder sb, String column1, String column2) { 4732 4733 sb.append("(((").append(column1).append(")=(").append(column2); 4734 sb.append("))OR(("); 4735 sb.append(column1).append(") IS NULL AND (").append(column2).append(") IS NULL))"); 4736 return sb; 4737 } 4738 4739 /** 4740 * Adds index stats into the SQLite database to force it to always use the lookup indexes. 4741 * 4742 * Note if you drop a table or an index, the corresponding row will be removed from this table. 4743 * Make sure to call this method after such operations. 4744 */ 4745 private void updateSqliteStats(SQLiteDatabase db) { 4746 if (!mDatabaseOptimizationEnabled) { 4747 return; // We don't use sqlite_stat1 during tests. 4748 } 4749 4750 // Specific stats strings are based on an actual large database after running ANALYZE 4751 // Important here are relative sizes. Raw-Contacts is slightly bigger than Contacts 4752 // Warning: Missing tables in here will make SQLite assume to contain 1000000 rows, 4753 // which can lead to catastrophic query plans for small tables 4754 4755 // What these numbers mean is described in this file. 4756 // http://www.sqlite.org/cgi/src/finfo?name=src/analyze.c 4757 4758 // Excerpt: 4759 /* 4760 ** Format of sqlite_stat1: 4761 ** 4762 ** There is normally one row per index, with the index identified by the 4763 ** name in the idx column. The tbl column is the name of the table to 4764 ** which the index belongs. In each such row, the stat column will be 4765 ** a string consisting of a list of integers. The first integer in this 4766 ** list is the number of rows in the index and in the table. The second 4767 ** integer is the average number of rows in the index that have the same 4768 ** value in the first column of the index. The third integer is the average 4769 ** number of rows in the index that have the same value for the first two 4770 ** columns. The N-th integer (for N>1) is the average number of rows in 4771 ** the index which have the same value for the first N-1 columns. For 4772 ** a K-column index, there will be K+1 integers in the stat column. If 4773 ** the index is unique, then the last integer will be 1. 4774 ** 4775 ** The list of integers in the stat column can optionally be followed 4776 ** by the keyword "unordered". The "unordered" keyword, if it is present, 4777 ** must be separated from the last integer by a single space. If the 4778 ** "unordered" keyword is present, then the query planner assumes that 4779 ** the index is unordered and will not use the index for a range query. 4780 ** 4781 ** If the sqlite_stat1.idx column is NULL, then the sqlite_stat1.stat 4782 ** column contains a single integer which is the (estimated) number of 4783 ** rows in the table identified by sqlite_stat1.tbl. 4784 */ 4785 4786 try { 4787 db.execSQL("DELETE FROM sqlite_stat1"); 4788 updateIndexStats(db, Tables.CONTACTS, 4789 "contacts_has_phone_index", "9000 500"); 4790 updateIndexStats(db, Tables.CONTACTS, 4791 "contacts_name_raw_contact_id_index", "9000 1"); 4792 updateIndexStats(db, Tables.CONTACTS, MoreDatabaseUtils.buildIndexName(Tables.CONTACTS, 4793 Contacts.CONTACT_LAST_UPDATED_TIMESTAMP), "9000 10"); 4794 4795 updateIndexStats(db, Tables.RAW_CONTACTS, 4796 "raw_contacts_contact_id_index", "10000 2"); 4797 updateIndexStats(db, Tables.RAW_CONTACTS, 4798 "raw_contact_sort_key2_index", "10000 2"); 4799 updateIndexStats(db, Tables.RAW_CONTACTS, 4800 "raw_contact_sort_key1_index", "10000 2"); 4801 updateIndexStats(db, Tables.RAW_CONTACTS, 4802 "raw_contacts_source_id_account_id_index", "10000 1 1"); 4803 4804 updateIndexStats(db, Tables.NAME_LOOKUP, 4805 "name_lookup_raw_contact_id_index", "35000 4"); 4806 updateIndexStats(db, Tables.NAME_LOOKUP, 4807 "name_lookup_index", "35000 2 2 2 1"); 4808 updateIndexStats(db, Tables.NAME_LOOKUP, 4809 "sqlite_autoindex_name_lookup_1", "35000 3 2 1"); 4810 4811 updateIndexStats(db, Tables.PHONE_LOOKUP, 4812 "phone_lookup_index", "3500 3 2 1"); 4813 updateIndexStats(db, Tables.PHONE_LOOKUP, 4814 "phone_lookup_min_match_index", "3500 3 2 2"); 4815 updateIndexStats(db, Tables.PHONE_LOOKUP, 4816 "phone_lookup_data_id_min_match_index", "3500 2 2"); 4817 4818 updateIndexStats(db, Tables.DATA, 4819 "data_mimetype_data1_index", "60000 5000 2"); 4820 updateIndexStats(db, Tables.DATA, 4821 "data_raw_contact_id", "60000 10"); 4822 4823 updateIndexStats(db, Tables.GROUPS, 4824 "groups_source_id_account_id_index", "50 2 2 1 1"); 4825 4826 updateIndexStats(db, Tables.NICKNAME_LOOKUP, 4827 "nickname_lookup_index", "500 2 1"); 4828 4829 updateIndexStats(db, Tables.CALLS, 4830 null, "250"); 4831 4832 updateIndexStats(db, Tables.STATUS_UPDATES, 4833 null, "100"); 4834 4835 updateIndexStats(db, Tables.STREAM_ITEMS, 4836 null, "500"); 4837 updateIndexStats(db, Tables.STREAM_ITEM_PHOTOS, 4838 null, "50"); 4839 4840 updateIndexStats(db, Tables.VOICEMAIL_STATUS, 4841 null, "5"); 4842 4843 updateIndexStats(db, Tables.ACCOUNTS, 4844 null, "3"); 4845 4846 updateIndexStats(db, Tables.PRE_AUTHORIZED_URIS, 4847 null, "1"); 4848 4849 updateIndexStats(db, Tables.VISIBLE_CONTACTS, 4850 null, "2000"); 4851 4852 updateIndexStats(db, Tables.PHOTO_FILES, 4853 null, "50"); 4854 4855 updateIndexStats(db, Tables.DEFAULT_DIRECTORY, 4856 null, "1500"); 4857 4858 updateIndexStats(db, Tables.MIMETYPES, 4859 "mime_type", "18 1"); 4860 4861 updateIndexStats(db, Tables.DATA_USAGE_STAT, 4862 "data_usage_stat_index", "20 2 1"); 4863 4864 updateIndexStats(db, Tables.METADATA_SYNC, 4865 "metadata_sync_index", "10000 1 1"); 4866 4867 // Tiny tables 4868 updateIndexStats(db, Tables.AGGREGATION_EXCEPTIONS, 4869 null, "10"); 4870 updateIndexStats(db, Tables.SETTINGS, 4871 null, "10"); 4872 updateIndexStats(db, Tables.PACKAGES, 4873 null, "0"); 4874 updateIndexStats(db, Tables.DIRECTORIES, 4875 null, "3"); 4876 updateIndexStats(db, LegacyApiSupport.LegacyTables.SETTINGS, 4877 null, "0"); 4878 updateIndexStats(db, "android_metadata", 4879 null, "1"); 4880 updateIndexStats(db, "_sync_state", 4881 "sqlite_autoindex__sync_state_1", "2 1 1"); 4882 updateIndexStats(db, "_sync_state_metadata", 4883 null, "1"); 4884 updateIndexStats(db, "properties", 4885 "sqlite_autoindex_properties_1", "4 1"); 4886 4887 updateIndexStats(db, Tables.METADATA_SYNC_STATE, 4888 "metadata_sync_state_index", "2 1 1"); 4889 4890 // Search index 4891 updateIndexStats(db, "search_index_docsize", 4892 null, "9000"); 4893 updateIndexStats(db, "search_index_content", 4894 null, "9000"); 4895 updateIndexStats(db, "search_index_stat", 4896 null, "1"); 4897 updateIndexStats(db, "search_index_segments", 4898 null, "450"); 4899 updateIndexStats(db, "search_index_segdir", 4900 "sqlite_autoindex_search_index_segdir_1", "9 5 1"); 4901 4902 // Force SQLite to reload sqlite_stat1. 4903 db.execSQL("ANALYZE sqlite_master;"); 4904 } catch (SQLException e) { 4905 Log.e(TAG, "Could not update index stats", e); 4906 } 4907 } 4908 4909 /** 4910 * Stores statistics for a given index. 4911 * 4912 * @param stats has the following structure: the first index is the expected size of 4913 * the table. The following integer(s) are the expected number of records selected with the 4914 * index. There should be one integer per indexed column. 4915 */ 4916 private void updateIndexStats(SQLiteDatabase db, String table, String index, String stats) { 4917 if (index == null) { 4918 db.execSQL("DELETE FROM sqlite_stat1 WHERE tbl=? AND idx IS NULL", 4919 new String[] {table}); 4920 } else { 4921 db.execSQL("DELETE FROM sqlite_stat1 WHERE tbl=? AND idx=?", 4922 new String[] {table, index}); 4923 } 4924 db.execSQL("INSERT INTO sqlite_stat1 (tbl,idx,stat) VALUES (?,?,?)", 4925 new String[] {table, index, stats}); 4926 } 4927 4928 /** 4929 * Wipes all data except mime type and package lookup tables. 4930 */ 4931 public void wipeData() { 4932 SQLiteDatabase db = getWritableDatabase(); 4933 4934 db.execSQL("DELETE FROM " + Tables.ACCOUNTS + ";"); 4935 db.execSQL("DELETE FROM " + Tables.CONTACTS + ";"); 4936 db.execSQL("DELETE FROM " + Tables.RAW_CONTACTS + ";"); 4937 db.execSQL("DELETE FROM " + Tables.STREAM_ITEMS + ";"); 4938 db.execSQL("DELETE FROM " + Tables.STREAM_ITEM_PHOTOS + ";"); 4939 db.execSQL("DELETE FROM " + Tables.PHOTO_FILES + ";"); 4940 db.execSQL("DELETE FROM " + Tables.DATA + ";"); 4941 db.execSQL("DELETE FROM " + Tables.PHONE_LOOKUP + ";"); 4942 db.execSQL("DELETE FROM " + Tables.NAME_LOOKUP + ";"); 4943 db.execSQL("DELETE FROM " + Tables.GROUPS + ";"); 4944 db.execSQL("DELETE FROM " + Tables.AGGREGATION_EXCEPTIONS + ";"); 4945 db.execSQL("DELETE FROM " + Tables.SETTINGS + ";"); 4946 db.execSQL("DELETE FROM " + Tables.CALLS + ";"); 4947 db.execSQL("DELETE FROM " + Tables.DIRECTORIES + ";"); 4948 db.execSQL("DELETE FROM " + Tables.SEARCH_INDEX + ";"); 4949 db.execSQL("DELETE FROM " + Tables.DELETED_CONTACTS + ";"); 4950 db.execSQL("DELETE FROM " + Tables.MIMETYPES + ";"); 4951 db.execSQL("DELETE FROM " + Tables.PACKAGES + ";"); 4952 4953 initializeCache(db); 4954 4955 // Note: we are not removing reference data from Tables.NICKNAME_LOOKUP 4956 } 4957 4958 public NameSplitter createNameSplitter() { 4959 return createNameSplitter(Locale.getDefault()); 4960 } 4961 4962 public NameSplitter createNameSplitter(Locale locale) { 4963 mNameSplitter = new NameSplitter( 4964 mContext.getString(com.android.internal.R.string.common_name_prefixes), 4965 mContext.getString(com.android.internal.R.string.common_last_name_prefixes), 4966 mContext.getString(com.android.internal.R.string.common_name_suffixes), 4967 mContext.getString(com.android.internal.R.string.common_name_conjunctions), 4968 locale); 4969 return mNameSplitter; 4970 } 4971 4972 /** 4973 * Return the {@link ApplicationInfo#uid} for the given package name. 4974 */ 4975 public static int getUidForPackageName(PackageManager pm, String packageName) { 4976 try { 4977 ApplicationInfo clientInfo = pm.getApplicationInfo(packageName, 0 /* no flags */); 4978 return clientInfo.uid; 4979 } catch (NameNotFoundException e) { 4980 throw new RuntimeException(e); 4981 } 4982 } 4983 4984 /** 4985 * Internal method used by {@link #getPackageId} and {@link #getMimeTypeId}. 4986 * 4987 * Note in the contacts provider we avoid using synchronization because it could risk deadlocks. 4988 * So here, instead of using locks, we use ConcurrentHashMap + retry. 4989 * 4990 * Note we can't use a transaction here becuause this method is called from 4991 * onCommitTransaction() too, unfortunately. 4992 */ 4993 private static long getIdCached(SQLiteDatabase db, ConcurrentHashMap<String, Long> cache, 4994 String querySql, String insertSql, String value) { 4995 // First, try the in-memory cache. 4996 if (cache.containsKey(value)) { 4997 return cache.get(value); 4998 } 4999 5000 // Then, try the database. 5001 long id = queryIdWithOneArg(db, querySql, value); 5002 if (id >= 0) { 5003 cache.put(value, id); 5004 return id; 5005 } 5006 5007 // Not found in the database. Try inserting. 5008 id = insertWithOneArgAndReturnId(db, insertSql, value); 5009 if (id >= 0) { 5010 cache.put(value, id); 5011 return id; 5012 } 5013 5014 // Insert failed, which means a race. Let's retry... 5015 5016 // We log here to detect an infinity loop (which shouldn't happen). 5017 // Conflicts should be pretty rare, so it shouldn't spam logcat. 5018 Log.i(TAG, "Cache conflict detected: value=" + value); 5019 try { 5020 Thread.sleep(1); // Just wait a little bit before retry. 5021 } catch (InterruptedException ignore) { 5022 } 5023 return getIdCached(db, cache, querySql, insertSql, value); 5024 } 5025 5026 @VisibleForTesting 5027 static long queryIdWithOneArg(SQLiteDatabase db, String sql, String sqlArgument) { 5028 final SQLiteStatement query = db.compileStatement(sql); 5029 try { 5030 DatabaseUtils.bindObjectToProgram(query, 1, sqlArgument); 5031 try { 5032 return query.simpleQueryForLong(); 5033 } catch (SQLiteDoneException notFound) { 5034 return -1; 5035 } 5036 } finally { 5037 query.close(); 5038 } 5039 } 5040 5041 @VisibleForTesting 5042 static long insertWithOneArgAndReturnId(SQLiteDatabase db, String sql, String sqlArgument) { 5043 final SQLiteStatement insert = db.compileStatement(sql); 5044 try { 5045 DatabaseUtils.bindObjectToProgram(insert, 1, sqlArgument); 5046 try { 5047 return insert.executeInsert(); 5048 } catch (SQLiteConstraintException conflict) { 5049 return -1; 5050 } 5051 } finally { 5052 insert.close(); 5053 } 5054 } 5055 5056 /** 5057 * Convert a package name into an integer, using {@link Tables#PACKAGES} for 5058 * lookups and possible allocation of new IDs as needed. 5059 */ 5060 public long getPackageId(String packageName) { 5061 final String query = 5062 "SELECT " + PackagesColumns._ID + 5063 " FROM " + Tables.PACKAGES + 5064 " WHERE " + PackagesColumns.PACKAGE + "=?"; 5065 5066 final String insert = 5067 "INSERT INTO " + Tables.PACKAGES + "(" 5068 + PackagesColumns.PACKAGE + 5069 ") VALUES (?)"; 5070 return getIdCached(getWritableDatabase(), mPackageCache, query, insert, packageName); 5071 } 5072 5073 /** 5074 * Convert a mimetype into an integer, using {@link Tables#MIMETYPES} for 5075 * lookups and possible allocation of new IDs as needed. 5076 */ 5077 public long getMimeTypeId(String mimetype) { 5078 return lookupMimeTypeId(mimetype, getWritableDatabase()); 5079 } 5080 5081 private long lookupMimeTypeId(String mimetype, SQLiteDatabase db) { 5082 final String query = 5083 "SELECT " + MimetypesColumns._ID + 5084 " FROM " + Tables.MIMETYPES + 5085 " WHERE " + MimetypesColumns.MIMETYPE + "=?"; 5086 5087 final String insert = 5088 "INSERT INTO " + Tables.MIMETYPES + "(" 5089 + MimetypesColumns.MIMETYPE + 5090 ") VALUES (?)"; 5091 5092 return getIdCached(db, mMimetypeCache, query, insert, mimetype); 5093 } 5094 5095 public long getMimeTypeIdForStructuredName() { 5096 return mMimeTypeIdStructuredName; 5097 } 5098 5099 public long getMimeTypeIdForStructuredPostal() { 5100 return mMimeTypeIdStructuredPostal; 5101 } 5102 5103 public long getMimeTypeIdForOrganization() { 5104 return mMimeTypeIdOrganization; 5105 } 5106 5107 public long getMimeTypeIdForIm() { 5108 return mMimeTypeIdIm; 5109 } 5110 5111 public long getMimeTypeIdForEmail() { 5112 return mMimeTypeIdEmail; 5113 } 5114 5115 public long getMimeTypeIdForPhone() { 5116 return mMimeTypeIdPhone; 5117 } 5118 5119 public long getMimeTypeIdForSip() { 5120 return mMimeTypeIdSip; 5121 } 5122 5123 /** 5124 * Returns a {@link ContactsContract.DisplayNameSources} value based on {@param mimeTypeId}. 5125 * This does not return {@link ContactsContract.DisplayNameSources#STRUCTURED_PHONETIC_NAME}. 5126 * The calling client needs to inspect the structured name itself to distinguish between 5127 * {@link ContactsContract.DisplayNameSources#STRUCTURED_NAME} and 5128 * {@code STRUCTURED_PHONETIC_NAME}. 5129 */ 5130 private int getDisplayNameSourceForMimeTypeId(int mimeTypeId) { 5131 if (mimeTypeId == mMimeTypeIdStructuredName) { 5132 return DisplayNameSources.STRUCTURED_NAME; 5133 } 5134 if (mimeTypeId == mMimeTypeIdEmail) { 5135 return DisplayNameSources.EMAIL; 5136 } 5137 if (mimeTypeId == mMimeTypeIdPhone) { 5138 return DisplayNameSources.PHONE; 5139 } 5140 if (mimeTypeId == mMimeTypeIdOrganization) { 5141 return DisplayNameSources.ORGANIZATION; 5142 } 5143 if (mimeTypeId == mMimeTypeIdNickname) { 5144 return DisplayNameSources.NICKNAME; 5145 } 5146 return DisplayNameSources.UNDEFINED; 5147 } 5148 5149 /** 5150 * Find the mimetype for the given {@link Data#_ID}. 5151 */ 5152 public String getDataMimeType(long dataId) { 5153 if (mDataMimetypeQuery == null) { 5154 mDataMimetypeQuery = getWritableDatabase().compileStatement( 5155 "SELECT " + MimetypesColumns.MIMETYPE + 5156 " FROM " + Tables.DATA_JOIN_MIMETYPES + 5157 " WHERE " + Tables.DATA + "." + Data._ID + "=?"); 5158 } 5159 try { 5160 // Try database query to find mimetype 5161 DatabaseUtils.bindObjectToProgram(mDataMimetypeQuery, 1, dataId); 5162 String mimetype = mDataMimetypeQuery.simpleQueryForString(); 5163 return mimetype; 5164 } catch (SQLiteDoneException e) { 5165 // No valid mapping found, so return null 5166 return null; 5167 } 5168 } 5169 5170 public void invalidateAllCache() { 5171 Log.w(TAG, "invalidateAllCache: [" + getClass().getSimpleName() + "]"); 5172 5173 mMimetypeCache.clear(); 5174 mPackageCache.clear(); 5175 } 5176 5177 /** 5178 * Gets all accounts in the accounts table. 5179 */ 5180 public Set<AccountWithDataSet> getAllAccountsWithDataSets() { 5181 final Set<AccountWithDataSet> result = Sets.newHashSet(); 5182 Cursor c = getReadableDatabase().rawQuery( 5183 "SELECT DISTINCT " + AccountsColumns._ID + "," + AccountsColumns.ACCOUNT_NAME + 5184 "," + AccountsColumns.ACCOUNT_TYPE + "," + AccountsColumns.DATA_SET + 5185 " FROM " + Tables.ACCOUNTS, null); 5186 try { 5187 while (c.moveToNext()) { 5188 result.add(AccountWithDataSet.get(c.getString(1), c.getString(2), c.getString(3))); 5189 } 5190 } finally { 5191 c.close(); 5192 } 5193 return result; 5194 } 5195 5196 /** 5197 * @return ID of the specified account, or null if the account doesn't exist. 5198 */ 5199 public Long getAccountIdOrNull(AccountWithDataSet accountWithDataSet) { 5200 if (accountWithDataSet == null) { 5201 accountWithDataSet = AccountWithDataSet.LOCAL; 5202 } 5203 final SQLiteStatement select = getWritableDatabase().compileStatement( 5204 "SELECT " + AccountsColumns._ID + 5205 " FROM " + Tables.ACCOUNTS + 5206 " WHERE " + 5207 "((?1 IS NULL AND " + AccountsColumns.ACCOUNT_NAME + " IS NULL) OR " + 5208 "(" + AccountsColumns.ACCOUNT_NAME + "=?1)) AND " + 5209 "((?2 IS NULL AND " + AccountsColumns.ACCOUNT_TYPE + " IS NULL) OR " + 5210 "(" + AccountsColumns.ACCOUNT_TYPE + "=?2)) AND " + 5211 "((?3 IS NULL AND " + AccountsColumns.DATA_SET + " IS NULL) OR " + 5212 "(" + AccountsColumns.DATA_SET + "=?3))"); 5213 try { 5214 DatabaseUtils.bindObjectToProgram(select, 1, accountWithDataSet.getAccountName()); 5215 DatabaseUtils.bindObjectToProgram(select, 2, accountWithDataSet.getAccountType()); 5216 DatabaseUtils.bindObjectToProgram(select, 3, accountWithDataSet.getDataSet()); 5217 try { 5218 return select.simpleQueryForLong(); 5219 } catch (SQLiteDoneException notFound) { 5220 return null; 5221 } 5222 } finally { 5223 select.close(); 5224 } 5225 } 5226 5227 /** 5228 * @return ID of the specified account. This method will create a record in the accounts table 5229 * if the account doesn't exist in the accounts table. 5230 * 5231 * This must be used in a transaction, so there's no need for synchronization. 5232 */ 5233 public long getOrCreateAccountIdInTransaction(AccountWithDataSet accountWithDataSet) { 5234 if (accountWithDataSet == null) { 5235 accountWithDataSet = AccountWithDataSet.LOCAL; 5236 } 5237 Long id = getAccountIdOrNull(accountWithDataSet); 5238 if (id != null) { 5239 return id; 5240 } 5241 final SQLiteStatement insert = getWritableDatabase().compileStatement( 5242 "INSERT INTO " + Tables.ACCOUNTS + 5243 " (" + AccountsColumns.ACCOUNT_NAME + ", " + 5244 AccountsColumns.ACCOUNT_TYPE + ", " + 5245 AccountsColumns.DATA_SET + ") VALUES (?, ?, ?)"); 5246 try { 5247 DatabaseUtils.bindObjectToProgram(insert, 1, accountWithDataSet.getAccountName()); 5248 DatabaseUtils.bindObjectToProgram(insert, 2, accountWithDataSet.getAccountType()); 5249 DatabaseUtils.bindObjectToProgram(insert, 3, accountWithDataSet.getDataSet()); 5250 id = insert.executeInsert(); 5251 } finally { 5252 insert.close(); 5253 } 5254 5255 return id; 5256 } 5257 5258 /** 5259 * Update {@link Contacts#IN_VISIBLE_GROUP} for all contacts. 5260 */ 5261 public void updateAllVisible() { 5262 updateCustomContactVisibility(getWritableDatabase(), -1); 5263 } 5264 5265 /** 5266 * Updates contact visibility and return true iff the visibility was actually changed. 5267 */ 5268 public boolean updateContactVisibleOnlyIfChanged(TransactionContext txContext, long contactId) { 5269 return updateContactVisible(txContext, contactId, true); 5270 } 5271 5272 /** 5273 * Update {@link Contacts#IN_VISIBLE_GROUP} and 5274 * {@link Tables#DEFAULT_DIRECTORY} for a specific contact. 5275 */ 5276 public void updateContactVisible(TransactionContext txContext, long contactId) { 5277 updateContactVisible(txContext, contactId, false); 5278 } 5279 5280 public boolean updateContactVisible( 5281 TransactionContext txContext, long contactId, boolean onlyIfChanged) { 5282 SQLiteDatabase db = getWritableDatabase(); 5283 updateCustomContactVisibility(db, contactId); 5284 5285 String contactIdAsString = String.valueOf(contactId); 5286 long mimetype = getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE); 5287 5288 // The contact will be included in the default directory if contains a raw contact that is 5289 // in any group or in an account that does not have any AUTO_ADD groups. 5290 boolean newVisibility = DatabaseUtils.longForQuery(db, 5291 "SELECT EXISTS (" + 5292 "SELECT " + RawContacts.CONTACT_ID + 5293 " FROM " + Tables.RAW_CONTACTS + 5294 " JOIN " + Tables.DATA + 5295 " ON (" + RawContactsColumns.CONCRETE_ID + "=" 5296 + Data.RAW_CONTACT_ID + ")" + 5297 " WHERE " + RawContacts.CONTACT_ID + "=?1" + 5298 " AND " + DataColumns.MIMETYPE_ID + "=?2" + 5299 ") OR EXISTS (" + 5300 "SELECT " + RawContacts._ID + 5301 " FROM " + Tables.RAW_CONTACTS + 5302 " WHERE " + RawContacts.CONTACT_ID + "=?1" + 5303 " AND NOT EXISTS" + 5304 " (SELECT " + Groups._ID + 5305 " FROM " + Tables.GROUPS + 5306 " WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID + " = " 5307 + GroupsColumns.CONCRETE_ACCOUNT_ID + 5308 " AND " + Groups.AUTO_ADD + " != 0" + 5309 ")" + 5310 ") OR EXISTS (" + 5311 "SELECT " + RawContacts._ID + 5312 " FROM " + Tables.RAW_CONTACTS + 5313 " WHERE " + RawContacts.CONTACT_ID + "=?1" + 5314 " AND " + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + 5315 Clauses.LOCAL_ACCOUNT_ID + 5316 ")", 5317 new String[] { 5318 contactIdAsString, 5319 String.valueOf(mimetype) 5320 }) != 0; 5321 5322 if (onlyIfChanged) { 5323 boolean oldVisibility = isContactInDefaultDirectory(db, contactId); 5324 if (oldVisibility == newVisibility) { 5325 return false; 5326 } 5327 } 5328 5329 if (newVisibility) { 5330 db.execSQL("INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY + " VALUES(?)", 5331 new String[] {contactIdAsString}); 5332 txContext.invalidateSearchIndexForContact(contactId); 5333 } else { 5334 db.execSQL("DELETE FROM " + Tables.DEFAULT_DIRECTORY + 5335 " WHERE " + Contacts._ID + "=?", 5336 new String[] {contactIdAsString}); 5337 db.execSQL("DELETE FROM " + Tables.SEARCH_INDEX + 5338 " WHERE " + SearchIndexColumns.CONTACT_ID + "=CAST(? AS int)", 5339 new String[] {contactIdAsString}); 5340 } 5341 return true; 5342 } 5343 5344 public boolean isContactInDefaultDirectory(SQLiteDatabase db, long contactId) { 5345 if (mContactInDefaultDirectoryQuery == null) { 5346 mContactInDefaultDirectoryQuery = db.compileStatement( 5347 "SELECT EXISTS (" + 5348 "SELECT 1 FROM " + Tables.DEFAULT_DIRECTORY + 5349 " WHERE " + Contacts._ID + "=?)"); 5350 } 5351 mContactInDefaultDirectoryQuery.bindLong(1, contactId); 5352 return mContactInDefaultDirectoryQuery.simpleQueryForLong() != 0; 5353 } 5354 5355 /** 5356 * Update the visible_contacts table according to the current visibility of contacts, which 5357 * is defined by {@link Clauses#CONTACT_IS_VISIBLE}. 5358 * 5359 * If {@code optionalContactId} is non-negative, it'll update only for the specified contact. 5360 */ 5361 private void updateCustomContactVisibility(SQLiteDatabase db, long optionalContactId) { 5362 final long groupMembershipMimetypeId = getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE); 5363 String[] selectionArgs = new String[] {String.valueOf(groupMembershipMimetypeId)}; 5364 5365 final String contactIdSelect = (optionalContactId < 0) ? "" : 5366 (Contacts._ID + "=" + optionalContactId + " AND "); 5367 5368 // First delete what needs to be deleted, then insert what needs to be added. 5369 // Since flash writes are very expensive, this approach is much better than 5370 // delete-all-insert-all. 5371 db.execSQL( 5372 "DELETE FROM " + Tables.VISIBLE_CONTACTS + 5373 " WHERE " + Contacts._ID + " IN" + 5374 "(SELECT " + Contacts._ID + 5375 " FROM " + Tables.CONTACTS + 5376 " WHERE " + contactIdSelect + "(" + Clauses.CONTACT_IS_VISIBLE + ")=0) ", 5377 selectionArgs); 5378 5379 db.execSQL( 5380 "INSERT INTO " + Tables.VISIBLE_CONTACTS + 5381 " SELECT " + Contacts._ID + 5382 " FROM " + Tables.CONTACTS + 5383 " WHERE " + 5384 contactIdSelect + 5385 Contacts._ID + " NOT IN " + Tables.VISIBLE_CONTACTS + 5386 " AND (" + Clauses.CONTACT_IS_VISIBLE + ")=1 ", 5387 selectionArgs); 5388 } 5389 5390 /** 5391 * Returns contact ID for the given contact or zero if it is NULL. 5392 */ 5393 public long getContactId(long rawContactId) { 5394 if (mContactIdQuery == null) { 5395 mContactIdQuery = getWritableDatabase().compileStatement( 5396 "SELECT " + RawContacts.CONTACT_ID + 5397 " FROM " + Tables.RAW_CONTACTS + 5398 " WHERE " + RawContacts._ID + "=?"); 5399 } 5400 try { 5401 DatabaseUtils.bindObjectToProgram(mContactIdQuery, 1, rawContactId); 5402 return mContactIdQuery.simpleQueryForLong(); 5403 } catch (SQLiteDoneException e) { 5404 return 0; // No valid mapping found. 5405 } 5406 } 5407 5408 public int getAggregationMode(long rawContactId) { 5409 if (mAggregationModeQuery == null) { 5410 mAggregationModeQuery = getWritableDatabase().compileStatement( 5411 "SELECT " + RawContacts.AGGREGATION_MODE + 5412 " FROM " + Tables.RAW_CONTACTS + 5413 " WHERE " + RawContacts._ID + "=?"); 5414 } 5415 try { 5416 DatabaseUtils.bindObjectToProgram(mAggregationModeQuery, 1, rawContactId); 5417 return (int)mAggregationModeQuery.simpleQueryForLong(); 5418 } catch (SQLiteDoneException e) { 5419 return RawContacts.AGGREGATION_MODE_DISABLED; // No valid row found. 5420 } 5421 } 5422 5423 public void buildPhoneLookupAndContactQuery( 5424 SQLiteQueryBuilder qb, String normalizedNumber, String numberE164) { 5425 5426 String minMatch = PhoneNumberUtils.toCallerIDMinMatch(normalizedNumber); 5427 StringBuilder sb = new StringBuilder(); 5428 appendPhoneLookupTables(sb, minMatch, true); 5429 qb.setTables(sb.toString()); 5430 5431 sb = new StringBuilder(); 5432 appendPhoneLookupSelection(sb, normalizedNumber, numberE164); 5433 qb.appendWhere(sb.toString()); 5434 } 5435 5436 /** 5437 * Phone lookup method that uses the custom SQLite function phone_number_compare_loose 5438 * that serves as a fallback in case the regular lookup does not return any results. 5439 * @param qb The query builder. 5440 * @param number The phone number to search for. 5441 */ 5442 public void buildFallbackPhoneLookupAndContactQuery(SQLiteQueryBuilder qb, String number) { 5443 final String minMatch = PhoneNumberUtils.toCallerIDMinMatch(number); 5444 final StringBuilder sb = new StringBuilder(); 5445 // Append lookup tables. 5446 sb.append(Tables.RAW_CONTACTS); 5447 sb.append(" JOIN " + Views.CONTACTS + " as contacts_view" 5448 + " ON (contacts_view._id = " + Tables.RAW_CONTACTS 5449 + "." + RawContacts.CONTACT_ID + ")" + 5450 " JOIN (SELECT " + PhoneLookupColumns.DATA_ID + "," + 5451 PhoneLookupColumns.NORMALIZED_NUMBER + " FROM "+ Tables.PHONE_LOOKUP + " " 5452 + "WHERE (" + Tables.PHONE_LOOKUP + "." + PhoneLookupColumns.MIN_MATCH + " = '"); 5453 sb.append(minMatch); 5454 sb.append("')) AS lookup " + 5455 "ON lookup." + PhoneLookupColumns.DATA_ID + "=" + Tables.DATA + "." + Data._ID 5456 + " JOIN " + Tables.DATA + " " 5457 + "ON " + Tables.DATA + "." + Data.RAW_CONTACT_ID + "=" + Tables.RAW_CONTACTS + "." 5458 + RawContacts._ID); 5459 5460 qb.setTables(sb.toString()); 5461 5462 sb.setLength(0); 5463 sb.append("PHONE_NUMBERS_EQUAL(" + Tables.DATA + "." + Phone.NUMBER + ", "); 5464 DatabaseUtils.appendEscapedSQLString(sb, number); 5465 sb.append(mUseStrictPhoneNumberComparison ? ", 1)" : ", 0)"); 5466 qb.appendWhere(sb.toString()); 5467 } 5468 5469 /** 5470 * Adds query for selecting the contact with the given {@code sipAddress} to the given 5471 * {@link StringBuilder}. 5472 * 5473 * @return the query arguments to be passed in with the query 5474 */ 5475 public String[] buildSipContactQuery(StringBuilder sb, String sipAddress) { 5476 sb.append("upper("); 5477 sb.append(Data.DATA1); 5478 sb.append(")=upper(?) AND "); 5479 sb.append(DataColumns.MIMETYPE_ID); 5480 sb.append("="); 5481 sb.append(Long.toString(getMimeTypeIdForSip())); 5482 // Return the arguments to be passed to the query. 5483 return new String[] {sipAddress}; 5484 } 5485 5486 public String buildPhoneLookupAsNestedQuery(String number) { 5487 StringBuilder sb = new StringBuilder(); 5488 final String minMatch = PhoneNumberUtils.toCallerIDMinMatch(number); 5489 sb.append("(SELECT DISTINCT raw_contact_id" + " FROM "); 5490 appendPhoneLookupTables(sb, minMatch, false); 5491 sb.append(" WHERE "); 5492 appendPhoneLookupSelection(sb, number, null); 5493 sb.append(")"); 5494 return sb.toString(); 5495 } 5496 5497 private void appendPhoneLookupTables( 5498 StringBuilder sb, final String minMatch, boolean joinContacts) { 5499 5500 sb.append(Tables.RAW_CONTACTS); 5501 if (joinContacts) { 5502 sb.append(" JOIN " + Views.CONTACTS + " contacts_view" 5503 + " ON (contacts_view._id = raw_contacts.contact_id)"); 5504 } 5505 sb.append(", (SELECT data_id, normalized_number, length(normalized_number) as len " 5506 + " FROM phone_lookup " + " WHERE (" + Tables.PHONE_LOOKUP + "." 5507 + PhoneLookupColumns.MIN_MATCH + " = '"); 5508 sb.append(minMatch); 5509 sb.append("')) AS lookup, " + Tables.DATA); 5510 } 5511 5512 private void appendPhoneLookupSelection(StringBuilder sb, String number, String numberE164) { 5513 sb.append("lookup.data_id=data._id AND data.raw_contact_id=raw_contacts._id"); 5514 boolean hasNumberE164 = !TextUtils.isEmpty(numberE164); 5515 boolean hasNumber = !TextUtils.isEmpty(number); 5516 if (hasNumberE164 || hasNumber) { 5517 sb.append(" AND ( "); 5518 if (hasNumberE164) { 5519 sb.append(" lookup.normalized_number = "); 5520 DatabaseUtils.appendEscapedSQLString(sb, numberE164); 5521 } 5522 if (hasNumberE164 && hasNumber) { 5523 sb.append(" OR "); 5524 } 5525 if (hasNumber) { 5526 // Skip the suffix match entirely if we are using strict number comparison. 5527 if (!mUseStrictPhoneNumberComparison) { 5528 int numberLen = number.length(); 5529 sb.append(" lookup.len <= "); 5530 sb.append(numberLen); 5531 sb.append(" AND substr("); 5532 DatabaseUtils.appendEscapedSQLString(sb, number); 5533 sb.append(','); 5534 sb.append(numberLen); 5535 sb.append(" - lookup.len + 1) = lookup.normalized_number"); 5536 5537 // Some countries (e.g. Brazil) can have incoming calls which contain only 5538 // the local number (no country calling code and no area code). This case 5539 // is handled below, see b/5197612. 5540 // This also handles a Gingerbread -> ICS upgrade issue; see b/5638376. 5541 sb.append(" OR ("); 5542 sb.append(" lookup.len > "); 5543 sb.append(numberLen); 5544 sb.append(" AND substr(lookup.normalized_number,"); 5545 sb.append("lookup.len + 1 - "); 5546 sb.append(numberLen); 5547 sb.append(") = "); 5548 DatabaseUtils.appendEscapedSQLString(sb, number); 5549 sb.append(")"); 5550 } else { 5551 sb.append("0"); 5552 } 5553 } 5554 sb.append(')'); 5555 } 5556 } 5557 5558 public String getUseStrictPhoneNumberComparisonParameter() { 5559 return mUseStrictPhoneNumberComparison ? "1" : "0"; 5560 } 5561 5562 /** 5563 * Loads common nickname mappings into the database. 5564 */ 5565 private void loadNicknameLookupTable(SQLiteDatabase db) { 5566 db.execSQL("DELETE FROM " + Tables.NICKNAME_LOOKUP); 5567 5568 String[] strings = mContext.getResources().getStringArray( 5569 com.android.internal.R.array.common_nicknames); 5570 if (strings == null || strings.length == 0) { 5571 return; 5572 } 5573 5574 SQLiteStatement nicknameLookupInsert = db.compileStatement("INSERT INTO " 5575 + Tables.NICKNAME_LOOKUP + "(" + NicknameLookupColumns.NAME + "," 5576 + NicknameLookupColumns.CLUSTER + ") VALUES (?,?)"); 5577 5578 try { 5579 for (int clusterId = 0; clusterId < strings.length; clusterId++) { 5580 String[] names = strings[clusterId].split(","); 5581 for (String name : names) { 5582 String normalizedName = NameNormalizer.normalize(name); 5583 try { 5584 DatabaseUtils.bindObjectToProgram(nicknameLookupInsert, 1, normalizedName); 5585 DatabaseUtils.bindObjectToProgram( 5586 nicknameLookupInsert, 2, String.valueOf(clusterId)); 5587 nicknameLookupInsert.executeInsert(); 5588 } catch (SQLiteException e) { 5589 // Print the exception and keep going (this is not a fatal error). 5590 Log.e(TAG, "Cannot insert nickname: " + name, e); 5591 } 5592 } 5593 } 5594 } finally { 5595 nicknameLookupInsert.close(); 5596 } 5597 } 5598 5599 public static void copyStringValue( 5600 ContentValues toValues, String toKey, ContentValues fromValues, String fromKey) { 5601 5602 if (fromValues.containsKey(fromKey)) { 5603 toValues.put(toKey, fromValues.getAsString(fromKey)); 5604 } 5605 } 5606 5607 public static void copyLongValue( 5608 ContentValues toValues, String toKey, ContentValues fromValues, String fromKey) { 5609 5610 if (fromValues.containsKey(fromKey)) { 5611 long longValue; 5612 Object value = fromValues.get(fromKey); 5613 if (value instanceof Boolean) { 5614 longValue = (Boolean) value ? 1 : 0; 5615 } else if (value instanceof String) { 5616 longValue = Long.parseLong((String)value); 5617 } else { 5618 longValue = ((Number)value).longValue(); 5619 } 5620 toValues.put(toKey, longValue); 5621 } 5622 } 5623 5624 public SyncStateContentProviderHelper getSyncState() { 5625 return mSyncState; 5626 } 5627 5628 /** 5629 * Returns the value from the {@link Tables#PROPERTIES} table. 5630 */ 5631 public String getProperty(String key, String defaultValue) { 5632 return getProperty(getReadableDatabase(), key, defaultValue); 5633 } 5634 5635 public String getProperty(SQLiteDatabase db, String key, String defaultValue) { 5636 Cursor cursor = db.query(Tables.PROPERTIES, 5637 new String[] {PropertiesColumns.PROPERTY_VALUE}, 5638 PropertiesColumns.PROPERTY_KEY + "=?", 5639 new String[] {key}, null, null, null); 5640 String value = null; 5641 try { 5642 if (cursor.moveToFirst()) { 5643 value = cursor.getString(0); 5644 } 5645 } finally { 5646 cursor.close(); 5647 } 5648 5649 return value != null ? value : defaultValue; 5650 } 5651 5652 /** 5653 * Stores a key-value pair in the {@link Tables#PROPERTIES} table. 5654 */ 5655 public void setProperty(String key, String value) { 5656 setProperty(getWritableDatabase(), key, value); 5657 } 5658 5659 private void setProperty(SQLiteDatabase db, String key, String value) { 5660 ContentValues values = new ContentValues(); 5661 values.put(PropertiesColumns.PROPERTY_KEY, key); 5662 values.put(PropertiesColumns.PROPERTY_VALUE, value); 5663 db.replace(Tables.PROPERTIES, null, values); 5664 } 5665 5666 /** 5667 * Test if the given column appears in the given projection. 5668 */ 5669 public static boolean isInProjection(String[] projection, String column) { 5670 if (projection == null) { 5671 return true; // Null means "all columns". We can't really tell if it's in there. 5672 } 5673 for (String test : projection) { 5674 if (column.equals(test)) { 5675 return true; 5676 } 5677 } 5678 return false; 5679 } 5680 5681 /** 5682 * Tests if any of the columns appear in the given projection. 5683 */ 5684 public static boolean isInProjection(String[] projection, String... columns) { 5685 if (projection == null) { 5686 return true; 5687 } 5688 5689 // Optimized for a single-column test 5690 if (columns.length == 1) { 5691 return isInProjection(projection, columns[0]); 5692 } 5693 for (String test : projection) { 5694 for (String column : columns) { 5695 if (column.equals(test)) { 5696 return true; 5697 } 5698 } 5699 } 5700 return false; 5701 } 5702 5703 /** 5704 * Returns a detailed exception message for the supplied URI. It includes the calling 5705 * user and calling package(s). 5706 */ 5707 public String exceptionMessage(Uri uri) { 5708 return exceptionMessage(null, uri); 5709 } 5710 5711 /** 5712 * Returns a detailed exception message for the supplied URI. It includes the calling 5713 * user and calling package(s). 5714 */ 5715 public String exceptionMessage(String message, Uri uri) { 5716 StringBuilder sb = new StringBuilder(); 5717 if (message != null) { 5718 sb.append(message).append("; "); 5719 } 5720 sb.append("URI: ").append(uri); 5721 final PackageManager pm = mContext.getPackageManager(); 5722 int callingUid = Binder.getCallingUid(); 5723 sb.append(", calling user: "); 5724 String userName = pm.getNameForUid(callingUid); 5725 sb.append(userName == null ? callingUid : userName); 5726 5727 final String[] callerPackages = pm.getPackagesForUid(callingUid); 5728 if (callerPackages != null && callerPackages.length > 0) { 5729 if (callerPackages.length == 1) { 5730 sb.append(", calling package:"); 5731 sb.append(callerPackages[0]); 5732 } else { 5733 sb.append(", calling package is one of: ["); 5734 for (int i = 0; i < callerPackages.length; i++) { 5735 if (i != 0) { 5736 sb.append(", "); 5737 } 5738 sb.append(callerPackages[i]); 5739 } 5740 sb.append("]"); 5741 } 5742 } 5743 return sb.toString(); 5744 } 5745 5746 public void deleteStatusUpdate(long dataId) { 5747 if (mStatusUpdateDelete == null) { 5748 mStatusUpdateDelete = getWritableDatabase().compileStatement( 5749 "DELETE FROM " + Tables.STATUS_UPDATES + 5750 " WHERE " + StatusUpdatesColumns.DATA_ID + "=?"); 5751 } 5752 mStatusUpdateDelete.bindLong(1, dataId); 5753 mStatusUpdateDelete.execute(); 5754 } 5755 5756 public void replaceStatusUpdate(Long dataId, long timestamp, String status, String resPackage, 5757 Integer iconResource, Integer labelResource) { 5758 if (mStatusUpdateReplace == null) { 5759 mStatusUpdateReplace = getWritableDatabase().compileStatement( 5760 "INSERT OR REPLACE INTO " + Tables.STATUS_UPDATES + "(" 5761 + StatusUpdatesColumns.DATA_ID + ", " 5762 + StatusUpdates.STATUS_TIMESTAMP + "," 5763 + StatusUpdates.STATUS + "," 5764 + StatusUpdates.STATUS_RES_PACKAGE + "," 5765 + StatusUpdates.STATUS_ICON + "," 5766 + StatusUpdates.STATUS_LABEL + ")" + 5767 " VALUES (?,?,?,?,?,?)"); 5768 } 5769 mStatusUpdateReplace.bindLong(1, dataId); 5770 mStatusUpdateReplace.bindLong(2, timestamp); 5771 bindString(mStatusUpdateReplace, 3, status); 5772 bindString(mStatusUpdateReplace, 4, resPackage); 5773 bindLong(mStatusUpdateReplace, 5, iconResource); 5774 bindLong(mStatusUpdateReplace, 6, labelResource); 5775 mStatusUpdateReplace.execute(); 5776 } 5777 5778 public void insertStatusUpdate(Long dataId, String status, String resPackage, 5779 Integer iconResource, Integer labelResource) { 5780 if (mStatusUpdateInsert == null) { 5781 mStatusUpdateInsert = getWritableDatabase().compileStatement( 5782 "INSERT INTO " + Tables.STATUS_UPDATES + "(" 5783 + StatusUpdatesColumns.DATA_ID + ", " 5784 + StatusUpdates.STATUS + "," 5785 + StatusUpdates.STATUS_RES_PACKAGE + "," 5786 + StatusUpdates.STATUS_ICON + "," 5787 + StatusUpdates.STATUS_LABEL + ")" + 5788 " VALUES (?,?,?,?,?)"); 5789 } 5790 try { 5791 mStatusUpdateInsert.bindLong(1, dataId); 5792 bindString(mStatusUpdateInsert, 2, status); 5793 bindString(mStatusUpdateInsert, 3, resPackage); 5794 bindLong(mStatusUpdateInsert, 4, iconResource); 5795 bindLong(mStatusUpdateInsert, 5, labelResource); 5796 mStatusUpdateInsert.executeInsert(); 5797 } catch (SQLiteConstraintException e) { 5798 // The row already exists - update it 5799 if (mStatusUpdateAutoTimestamp == null) { 5800 mStatusUpdateAutoTimestamp = getWritableDatabase().compileStatement( 5801 "UPDATE " + Tables.STATUS_UPDATES + 5802 " SET " + StatusUpdates.STATUS_TIMESTAMP + "=?," 5803 + StatusUpdates.STATUS + "=?" + 5804 " WHERE " + StatusUpdatesColumns.DATA_ID + "=?" 5805 + " AND " + StatusUpdates.STATUS + "!=?"); 5806 } 5807 5808 long timestamp = System.currentTimeMillis(); 5809 mStatusUpdateAutoTimestamp.bindLong(1, timestamp); 5810 bindString(mStatusUpdateAutoTimestamp, 2, status); 5811 mStatusUpdateAutoTimestamp.bindLong(3, dataId); 5812 bindString(mStatusUpdateAutoTimestamp, 4, status); 5813 mStatusUpdateAutoTimestamp.execute(); 5814 5815 if (mStatusAttributionUpdate == null) { 5816 mStatusAttributionUpdate = getWritableDatabase().compileStatement( 5817 "UPDATE " + Tables.STATUS_UPDATES + 5818 " SET " + StatusUpdates.STATUS_RES_PACKAGE + "=?," 5819 + StatusUpdates.STATUS_ICON + "=?," 5820 + StatusUpdates.STATUS_LABEL + "=?" + 5821 " WHERE " + StatusUpdatesColumns.DATA_ID + "=?"); 5822 } 5823 bindString(mStatusAttributionUpdate, 1, resPackage); 5824 bindLong(mStatusAttributionUpdate, 2, iconResource); 5825 bindLong(mStatusAttributionUpdate, 3, labelResource); 5826 mStatusAttributionUpdate.bindLong(4, dataId); 5827 mStatusAttributionUpdate.execute(); 5828 } 5829 } 5830 5831 /** 5832 * Updates a raw contact display name based on data rows, e.g. structured name, 5833 * organization, email etc. 5834 */ 5835 public void updateRawContactDisplayName(SQLiteDatabase db, long rawContactId) { 5836 if (mNameSplitter == null) { 5837 createNameSplitter(); 5838 } 5839 5840 int bestDisplayNameSource = DisplayNameSources.UNDEFINED; 5841 NameSplitter.Name bestName = null; 5842 String bestDisplayName = null; 5843 String bestPhoneticName = null; 5844 int bestPhoneticNameStyle = PhoneticNameStyle.UNDEFINED; 5845 5846 mSelectionArgs1[0] = String.valueOf(rawContactId); 5847 Cursor c = db.rawQuery(RawContactNameQuery.RAW_SQL, mSelectionArgs1); 5848 try { 5849 while (c.moveToNext()) { 5850 int mimeType = c.getInt(RawContactNameQuery.MIMETYPE); 5851 int source = getDisplayNameSourceForMimeTypeId(mimeType); 5852 5853 if (source == DisplayNameSources.STRUCTURED_NAME) { 5854 final String given = c.getString(RawContactNameQuery.GIVEN_NAME); 5855 final String middle = c.getString(RawContactNameQuery.MIDDLE_NAME); 5856 final String family = c.getString(RawContactNameQuery.FAMILY_NAME); 5857 final String suffix = c.getString(RawContactNameQuery.SUFFIX); 5858 final String prefix = c.getString(RawContactNameQuery.PREFIX); 5859 if (TextUtils.isEmpty(given) && TextUtils.isEmpty(middle) 5860 && TextUtils.isEmpty(family) && TextUtils.isEmpty(suffix) 5861 && TextUtils.isEmpty(prefix)) { 5862 // Every non-phonetic name component is empty. Therefore, lets lower the 5863 // source score to STRUCTURED_PHONETIC_NAME. 5864 source = DisplayNameSources.STRUCTURED_PHONETIC_NAME; 5865 } 5866 } 5867 5868 if (source < bestDisplayNameSource || source == DisplayNameSources.UNDEFINED) { 5869 continue; 5870 } 5871 5872 if (source == bestDisplayNameSource 5873 && c.getInt(RawContactNameQuery.IS_PRIMARY) == 0) { 5874 continue; 5875 } 5876 5877 if (mimeType == getMimeTypeIdForStructuredName()) { 5878 NameSplitter.Name name; 5879 if (bestName != null) { 5880 name = new NameSplitter.Name(); 5881 } else { 5882 name = mName; 5883 name.clear(); 5884 } 5885 name.prefix = c.getString(RawContactNameQuery.PREFIX); 5886 name.givenNames = c.getString(RawContactNameQuery.GIVEN_NAME); 5887 name.middleName = c.getString(RawContactNameQuery.MIDDLE_NAME); 5888 name.familyName = c.getString(RawContactNameQuery.FAMILY_NAME); 5889 name.suffix = c.getString(RawContactNameQuery.SUFFIX); 5890 name.fullNameStyle = c.isNull(RawContactNameQuery.FULL_NAME_STYLE) 5891 ? FullNameStyle.UNDEFINED 5892 : c.getInt(RawContactNameQuery.FULL_NAME_STYLE); 5893 name.phoneticFamilyName = c.getString(RawContactNameQuery.PHONETIC_FAMILY_NAME); 5894 name.phoneticMiddleName = c.getString(RawContactNameQuery.PHONETIC_MIDDLE_NAME); 5895 name.phoneticGivenName = c.getString(RawContactNameQuery.PHONETIC_GIVEN_NAME); 5896 name.phoneticNameStyle = c.isNull(RawContactNameQuery.PHONETIC_NAME_STYLE) 5897 ? PhoneticNameStyle.UNDEFINED 5898 : c.getInt(RawContactNameQuery.PHONETIC_NAME_STYLE); 5899 if (!name.isEmpty()) { 5900 bestDisplayNameSource = source; 5901 bestName = name; 5902 } 5903 } else if (mimeType == getMimeTypeIdForOrganization()) { 5904 mCharArrayBuffer.sizeCopied = 0; 5905 c.copyStringToBuffer(RawContactNameQuery.DATA1, mCharArrayBuffer); 5906 if (mCharArrayBuffer.sizeCopied != 0) { 5907 bestDisplayNameSource = source; 5908 bestDisplayName = new String(mCharArrayBuffer.data, 0, 5909 mCharArrayBuffer.sizeCopied); 5910 bestPhoneticName = c.getString( 5911 RawContactNameQuery.ORGANIZATION_PHONETIC_NAME); 5912 bestPhoneticNameStyle = 5913 c.isNull(RawContactNameQuery.ORGANIZATION_PHONETIC_NAME_STYLE) 5914 ? PhoneticNameStyle.UNDEFINED 5915 : c.getInt(RawContactNameQuery.ORGANIZATION_PHONETIC_NAME_STYLE); 5916 } else { 5917 c.copyStringToBuffer(RawContactNameQuery.TITLE, mCharArrayBuffer); 5918 if (mCharArrayBuffer.sizeCopied != 0) { 5919 bestDisplayNameSource = source; 5920 bestDisplayName = new String(mCharArrayBuffer.data, 0, 5921 mCharArrayBuffer.sizeCopied); 5922 bestPhoneticName = null; 5923 bestPhoneticNameStyle = PhoneticNameStyle.UNDEFINED; 5924 } 5925 } 5926 } else { 5927 // Display name is at DATA1 in all other types. 5928 // This is ensured in the constructor. 5929 5930 mCharArrayBuffer.sizeCopied = 0; 5931 c.copyStringToBuffer(RawContactNameQuery.DATA1, mCharArrayBuffer); 5932 if (mCharArrayBuffer.sizeCopied != 0) { 5933 bestDisplayNameSource = source; 5934 bestDisplayName = new String(mCharArrayBuffer.data, 0, 5935 mCharArrayBuffer.sizeCopied); 5936 bestPhoneticName = null; 5937 bestPhoneticNameStyle = PhoneticNameStyle.UNDEFINED; 5938 } 5939 } 5940 } 5941 5942 } finally { 5943 c.close(); 5944 } 5945 5946 String displayNamePrimary; 5947 String displayNameAlternative; 5948 String sortNamePrimary; 5949 String sortNameAlternative; 5950 String sortKeyPrimary = null; 5951 String sortKeyAlternative = null; 5952 int displayNameStyle = FullNameStyle.UNDEFINED; 5953 5954 if (bestDisplayNameSource == DisplayNameSources.STRUCTURED_NAME 5955 || bestDisplayNameSource == DisplayNameSources.STRUCTURED_PHONETIC_NAME) { 5956 displayNameStyle = bestName.fullNameStyle; 5957 if (displayNameStyle == FullNameStyle.CJK 5958 || displayNameStyle == FullNameStyle.UNDEFINED) { 5959 displayNameStyle = mNameSplitter.getAdjustedFullNameStyle(displayNameStyle); 5960 bestName.fullNameStyle = displayNameStyle; 5961 } 5962 5963 displayNamePrimary = mNameSplitter.join(bestName, true, true); 5964 displayNameAlternative = mNameSplitter.join(bestName, false, true); 5965 5966 if (TextUtils.isEmpty(bestName.prefix)) { 5967 sortNamePrimary = displayNamePrimary; 5968 sortNameAlternative = displayNameAlternative; 5969 } else { 5970 sortNamePrimary = mNameSplitter.join(bestName, true, false); 5971 sortNameAlternative = mNameSplitter.join(bestName, false, false); 5972 } 5973 5974 bestPhoneticName = mNameSplitter.joinPhoneticName(bestName); 5975 bestPhoneticNameStyle = bestName.phoneticNameStyle; 5976 } else { 5977 displayNamePrimary = displayNameAlternative = bestDisplayName; 5978 sortNamePrimary = sortNameAlternative = bestDisplayName; 5979 } 5980 5981 if (bestPhoneticName != null) { 5982 if (displayNamePrimary == null) { 5983 displayNamePrimary = bestPhoneticName; 5984 } 5985 if (displayNameAlternative == null) { 5986 displayNameAlternative = bestPhoneticName; 5987 } 5988 // Phonetic names disregard name order so displayNamePrimary and displayNameAlternative 5989 // are the same. 5990 sortKeyPrimary = sortKeyAlternative = bestPhoneticName; 5991 if (bestPhoneticNameStyle == PhoneticNameStyle.UNDEFINED) { 5992 bestPhoneticNameStyle = mNameSplitter.guessPhoneticNameStyle(bestPhoneticName); 5993 } 5994 } else { 5995 bestPhoneticNameStyle = PhoneticNameStyle.UNDEFINED; 5996 if (displayNameStyle == FullNameStyle.UNDEFINED) { 5997 displayNameStyle = mNameSplitter.guessFullNameStyle(bestDisplayName); 5998 if (displayNameStyle == FullNameStyle.UNDEFINED 5999 || displayNameStyle == FullNameStyle.CJK) { 6000 displayNameStyle = mNameSplitter.getAdjustedNameStyleBasedOnPhoneticNameStyle( 6001 displayNameStyle, bestPhoneticNameStyle); 6002 } 6003 displayNameStyle = mNameSplitter.getAdjustedFullNameStyle(displayNameStyle); 6004 } 6005 if (displayNameStyle == FullNameStyle.CHINESE || 6006 displayNameStyle == FullNameStyle.CJK) { 6007 sortKeyPrimary = sortKeyAlternative = sortNamePrimary; 6008 } 6009 } 6010 6011 if (sortKeyPrimary == null) { 6012 sortKeyPrimary = sortNamePrimary; 6013 sortKeyAlternative = sortNameAlternative; 6014 } 6015 6016 String phonebookLabelPrimary = ""; 6017 String phonebookLabelAlternative = ""; 6018 int phonebookBucketPrimary = 0; 6019 int phonebookBucketAlternative = 0; 6020 ContactLocaleUtils localeUtils = ContactLocaleUtils.getInstance(); 6021 6022 if (sortKeyPrimary != null) { 6023 phonebookBucketPrimary = localeUtils.getBucketIndex(sortKeyPrimary); 6024 phonebookLabelPrimary = localeUtils.getBucketLabel(phonebookBucketPrimary); 6025 } 6026 if (sortKeyAlternative != null) { 6027 phonebookBucketAlternative = localeUtils.getBucketIndex(sortKeyAlternative); 6028 phonebookLabelAlternative = localeUtils.getBucketLabel(phonebookBucketAlternative); 6029 } 6030 6031 if (mRawContactDisplayNameUpdate == null) { 6032 mRawContactDisplayNameUpdate = db.compileStatement( 6033 "UPDATE " + Tables.RAW_CONTACTS + 6034 " SET " + 6035 RawContacts.DISPLAY_NAME_SOURCE + "=?," + 6036 RawContacts.DISPLAY_NAME_PRIMARY + "=?," + 6037 RawContacts.DISPLAY_NAME_ALTERNATIVE + "=?," + 6038 RawContacts.PHONETIC_NAME + "=?," + 6039 RawContacts.PHONETIC_NAME_STYLE + "=?," + 6040 RawContacts.SORT_KEY_PRIMARY + "=?," + 6041 RawContactsColumns.PHONEBOOK_LABEL_PRIMARY + "=?," + 6042 RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY + "=?," + 6043 RawContacts.SORT_KEY_ALTERNATIVE + "=?," + 6044 RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + "=?," + 6045 RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE + "=?" + 6046 " WHERE " + RawContacts._ID + "=?"); 6047 } 6048 6049 mRawContactDisplayNameUpdate.bindLong(1, bestDisplayNameSource); 6050 bindString(mRawContactDisplayNameUpdate, 2, displayNamePrimary); 6051 bindString(mRawContactDisplayNameUpdate, 3, displayNameAlternative); 6052 bindString(mRawContactDisplayNameUpdate, 4, bestPhoneticName); 6053 mRawContactDisplayNameUpdate.bindLong(5, bestPhoneticNameStyle); 6054 bindString(mRawContactDisplayNameUpdate, 6, sortKeyPrimary); 6055 bindString(mRawContactDisplayNameUpdate, 7, phonebookLabelPrimary); 6056 mRawContactDisplayNameUpdate.bindLong(8, phonebookBucketPrimary); 6057 bindString(mRawContactDisplayNameUpdate, 9, sortKeyAlternative); 6058 bindString(mRawContactDisplayNameUpdate, 10, phonebookLabelAlternative); 6059 mRawContactDisplayNameUpdate.bindLong(11, phonebookBucketAlternative); 6060 mRawContactDisplayNameUpdate.bindLong(12, rawContactId); 6061 mRawContactDisplayNameUpdate.execute(); 6062 } 6063 6064 /** 6065 * Sets the given dataId record in the "data" table to primary, and resets all data records of 6066 * the same mimetype and under the same contact to not be primary. 6067 * 6068 * @param dataId the id of the data record to be set to primary. Pass -1 to clear the primary 6069 * flag of all data items of this raw contacts 6070 */ 6071 public void setIsPrimary(long rawContactId, long dataId, long mimeTypeId) { 6072 if (mSetPrimaryStatement == null) { 6073 mSetPrimaryStatement = getWritableDatabase().compileStatement( 6074 "UPDATE " + Tables.DATA + 6075 " SET " + Data.IS_PRIMARY + "=(_id=?)" + 6076 " WHERE " + DataColumns.MIMETYPE_ID + "=?" + 6077 " AND " + Data.RAW_CONTACT_ID + "=?"); 6078 } 6079 mSetPrimaryStatement.bindLong(1, dataId); 6080 mSetPrimaryStatement.bindLong(2, mimeTypeId); 6081 mSetPrimaryStatement.bindLong(3, rawContactId); 6082 mSetPrimaryStatement.execute(); 6083 } 6084 6085 /** 6086 * Clears the super primary of all data items of the given raw contact. does not touch 6087 * other raw contacts of the same joined aggregate 6088 */ 6089 public void clearSuperPrimary(long rawContactId, long mimeTypeId) { 6090 if (mClearSuperPrimaryStatement == null) { 6091 mClearSuperPrimaryStatement = getWritableDatabase().compileStatement( 6092 "UPDATE " + Tables.DATA + 6093 " SET " + Data.IS_SUPER_PRIMARY + "=0" + 6094 " WHERE " + DataColumns.MIMETYPE_ID + "=?" + 6095 " AND " + Data.RAW_CONTACT_ID + "=?"); 6096 } 6097 mClearSuperPrimaryStatement.bindLong(1, mimeTypeId); 6098 mClearSuperPrimaryStatement.bindLong(2, rawContactId); 6099 mClearSuperPrimaryStatement.execute(); 6100 } 6101 6102 /** 6103 * Sets the given dataId record in the "data" table to "super primary", and resets all data 6104 * records of the same mimetype and under the same aggregate to not be "super primary". 6105 * 6106 * @param dataId the id of the data record to be set to primary. 6107 */ 6108 public void setIsSuperPrimary(long rawContactId, long dataId, long mimeTypeId) { 6109 if (mSetSuperPrimaryStatement == null) { 6110 mSetSuperPrimaryStatement = getWritableDatabase().compileStatement( 6111 "UPDATE " + Tables.DATA + 6112 " SET " + Data.IS_SUPER_PRIMARY + "=(" + Data._ID + "=?)" + 6113 " WHERE " + DataColumns.MIMETYPE_ID + "=?" + 6114 " AND " + Data.RAW_CONTACT_ID + " IN (" + 6115 "SELECT " + RawContacts._ID + 6116 " FROM " + Tables.RAW_CONTACTS + 6117 " WHERE " + RawContacts.CONTACT_ID + " =(" + 6118 "SELECT " + RawContacts.CONTACT_ID + 6119 " FROM " + Tables.RAW_CONTACTS + 6120 " WHERE " + RawContacts._ID + "=?))"); 6121 } 6122 mSetSuperPrimaryStatement.bindLong(1, dataId); 6123 mSetSuperPrimaryStatement.bindLong(2, mimeTypeId); 6124 mSetSuperPrimaryStatement.bindLong(3, rawContactId); 6125 mSetSuperPrimaryStatement.execute(); 6126 } 6127 6128 /** 6129 * Inserts a record in the {@link Tables#NAME_LOOKUP} table. 6130 */ 6131 public void insertNameLookup(long rawContactId, long dataId, int lookupType, String name) { 6132 if (TextUtils.isEmpty(name)) { 6133 return; 6134 } 6135 6136 if (mNameLookupInsert == null) { 6137 mNameLookupInsert = getWritableDatabase().compileStatement( 6138 "INSERT OR IGNORE INTO " + Tables.NAME_LOOKUP + "(" 6139 + NameLookupColumns.RAW_CONTACT_ID + "," 6140 + NameLookupColumns.DATA_ID + "," 6141 + NameLookupColumns.NAME_TYPE + "," 6142 + NameLookupColumns.NORMALIZED_NAME 6143 + ") VALUES (?,?,?,?)"); 6144 } 6145 mNameLookupInsert.bindLong(1, rawContactId); 6146 mNameLookupInsert.bindLong(2, dataId); 6147 mNameLookupInsert.bindLong(3, lookupType); 6148 bindString(mNameLookupInsert, 4, name); 6149 mNameLookupInsert.executeInsert(); 6150 } 6151 6152 /** 6153 * Deletes all {@link Tables#NAME_LOOKUP} table rows associated with the specified data element. 6154 */ 6155 public void deleteNameLookup(long dataId) { 6156 if (mNameLookupDelete == null) { 6157 mNameLookupDelete = getWritableDatabase().compileStatement( 6158 "DELETE FROM " + Tables.NAME_LOOKUP + 6159 " WHERE " + NameLookupColumns.DATA_ID + "=?"); 6160 } 6161 mNameLookupDelete.bindLong(1, dataId); 6162 mNameLookupDelete.execute(); 6163 } 6164 6165 public String insertNameLookupForEmail(long rawContactId, long dataId, String email) { 6166 if (TextUtils.isEmpty(email)) { 6167 return null; 6168 } 6169 6170 String address = extractHandleFromEmailAddress(email); 6171 if (address == null) { 6172 return null; 6173 } 6174 6175 insertNameLookup(rawContactId, dataId, 6176 NameLookupType.EMAIL_BASED_NICKNAME, NameNormalizer.normalize(address)); 6177 return address; 6178 } 6179 6180 /** 6181 * Normalizes the nickname and inserts it in the name lookup table. 6182 */ 6183 public void insertNameLookupForNickname(long rawContactId, long dataId, String nickname) { 6184 if (!TextUtils.isEmpty(nickname)) { 6185 insertNameLookup(rawContactId, dataId, 6186 NameLookupType.NICKNAME, NameNormalizer.normalize(nickname)); 6187 } 6188 } 6189 6190 /** 6191 * Performs a query and returns true if any Data item of the raw contact with the given 6192 * id and mimetype is marked as super-primary 6193 */ 6194 public boolean rawContactHasSuperPrimary(long rawContactId, long mimeTypeId) { 6195 final Cursor existsCursor = getReadableDatabase().rawQuery( 6196 "SELECT EXISTS(SELECT 1 FROM " + Tables.DATA + 6197 " WHERE " + Data.RAW_CONTACT_ID + "=?" + 6198 " AND " + DataColumns.MIMETYPE_ID + "=?" + 6199 " AND " + Data.IS_SUPER_PRIMARY + "<>0)", 6200 new String[] {String.valueOf(rawContactId), String.valueOf(mimeTypeId)}); 6201 try { 6202 if (!existsCursor.moveToFirst()) throw new IllegalStateException(); 6203 return existsCursor.getInt(0) != 0; 6204 } finally { 6205 existsCursor.close(); 6206 } 6207 } 6208 6209 public String getCurrentCountryIso() { 6210 return mCountryMonitor.getCountryIso(); 6211 } 6212 6213 @NeededForTesting 6214 /* package */ void setUseStrictPhoneNumberComparisonForTest(boolean useStrict) { 6215 mUseStrictPhoneNumberComparison = useStrict; 6216 } 6217 6218 @NeededForTesting 6219 /* package */ boolean getUseStrictPhoneNumberComparisonForTest() { 6220 return mUseStrictPhoneNumberComparison; 6221 } 6222 6223 @NeededForTesting 6224 /* package */ String querySearchIndexContentForTest(long contactId) { 6225 return DatabaseUtils.stringForQuery(getReadableDatabase(), 6226 "SELECT " + SearchIndexColumns.CONTENT + 6227 " FROM " + Tables.SEARCH_INDEX + 6228 " WHERE " + SearchIndexColumns.CONTACT_ID + "=CAST(? AS int)", 6229 new String[] {String.valueOf(contactId)}); 6230 } 6231 6232 @NeededForTesting 6233 /* package */ String querySearchIndexTokensForTest(long contactId) { 6234 return DatabaseUtils.stringForQuery(getReadableDatabase(), 6235 "SELECT " + SearchIndexColumns.TOKENS + 6236 " FROM " + Tables.SEARCH_INDEX + 6237 " WHERE " + SearchIndexColumns.CONTACT_ID + "=CAST(? AS int)", 6238 new String[] {String.valueOf(contactId)}); 6239 } 6240 6241 public long upsertMetadataSync(String backupId, Long accountId, String data, Integer deleted) { 6242 if (mMetadataSyncInsert == null) { 6243 mMetadataSyncInsert = getWritableDatabase().compileStatement( 6244 "INSERT OR REPLACE INTO " + Tables.METADATA_SYNC + "(" 6245 + MetadataSync.RAW_CONTACT_BACKUP_ID + ", " 6246 + MetadataSyncColumns.ACCOUNT_ID + ", " 6247 + MetadataSync.DATA + "," 6248 + MetadataSync.DELETED + ")" + 6249 " VALUES (?,?,?,?)"); 6250 } 6251 mMetadataSyncInsert.bindString(1, backupId); 6252 mMetadataSyncInsert.bindLong(2, accountId); 6253 data = (data == null) ? "" : data; 6254 mMetadataSyncInsert.bindString(3, data); 6255 mMetadataSyncInsert.bindLong(4, deleted); 6256 return mMetadataSyncInsert.executeInsert(); 6257 } 6258} 6259