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