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