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