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