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