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