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