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