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