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