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