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