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