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