1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.providers.contacts; 18 19 import com.android.internal.content.SyncStateContentProviderHelper; 20 21 import android.content.ContentResolver; 22 import android.content.ContentValues; 23 import android.content.Context; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.PackageManager; 26 import android.content.pm.PackageManager.NameNotFoundException; 27 import android.content.res.Resources; 28 import android.database.DatabaseUtils; 29 import android.database.SQLException; 30 import android.database.sqlite.SQLiteDatabase; 31 import android.database.sqlite.SQLiteDoneException; 32 import android.database.sqlite.SQLiteException; 33 import android.database.sqlite.SQLiteOpenHelper; 34 import android.database.sqlite.SQLiteQueryBuilder; 35 import android.database.sqlite.SQLiteStatement; 36 import android.os.Binder; 37 import android.os.Bundle; 38 import android.provider.BaseColumns; 39 import android.provider.ContactsContract; 40 import android.provider.CallLog.Calls; 41 import android.provider.ContactsContract.AggregationExceptions; 42 import android.provider.ContactsContract.Contacts; 43 import android.provider.ContactsContract.Data; 44 import android.provider.ContactsContract.Groups; 45 import android.provider.ContactsContract.RawContacts; 46 import android.provider.ContactsContract.Settings; 47 import android.provider.ContactsContract.StatusUpdates; 48 import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 49 import android.provider.ContactsContract.CommonDataKinds.Phone; 50 import android.provider.SocialContract.Activities; 51 import android.telephony.PhoneNumberUtils; 52 import android.util.Log; 53 54 import java.util.HashMap; 55 56 /** 57 * Database helper for contacts. Designed as a singleton to make sure that all 58 * {@link android.content.ContentProvider} users get the same reference. 59 * Provides handy methods for maintaining package and mime-type lookup tables. 60 */ 61 /* package */ class ContactsDatabaseHelper extends SQLiteOpenHelper { 62 private static final String TAG = "ContactsDatabaseHelper"; 63 64 private static final int DATABASE_VERSION = 105; 65 66 private static final String DATABASE_NAME = "contacts2.db"; 67 private static final String DATABASE_PRESENCE = "presence_db"; 68 69 public interface Tables { 70 public static final String CONTACTS = "contacts"; 71 public static final String RAW_CONTACTS = "raw_contacts"; 72 public static final String PACKAGES = "packages"; 73 public static final String MIMETYPES = "mimetypes"; 74 public static final String PHONE_LOOKUP = "phone_lookup"; 75 public static final String NAME_LOOKUP = "name_lookup"; 76 public static final String AGGREGATION_EXCEPTIONS = "agg_exceptions"; 77 public static final String SETTINGS = "settings"; 78 public static final String DATA = "data"; 79 public static final String GROUPS = "groups"; 80 public static final String PRESENCE = "presence"; 81 public static final String AGGREGATED_PRESENCE = "agg_presence"; 82 public static final String NICKNAME_LOOKUP = "nickname_lookup"; 83 public static final String CALLS = "calls"; 84 public static final String CONTACT_ENTITIES = "contact_entities_view"; 85 public static final String CONTACT_ENTITIES_RESTRICTED = "contact_entities_view_restricted"; 86 public static final String STATUS_UPDATES = "status_updates"; 87 88 public static final String DATA_JOIN_MIMETYPES = "data " 89 + "JOIN mimetypes ON (data.mimetype_id = mimetypes._id)"; 90 91 public static final String DATA_JOIN_RAW_CONTACTS = "data " 92 + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id)"; 93 94 public static final String DATA_JOIN_MIMETYPE_RAW_CONTACTS = "data " 95 + "JOIN mimetypes ON (data.mimetype_id = mimetypes._id) " 96 + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id)"; 97 98 public static final String DATA_JOIN_RAW_CONTACTS_GROUPS = "data " 99 + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id)" 100 + "LEFT OUTER JOIN groups ON (groups._id = data." + GroupMembership.GROUP_ROW_ID 101 + ")"; 102 103 public static final String DATA_JOIN_PACKAGES_MIMETYPES_RAW_CONTACTS = "data " 104 + "JOIN mimetypes ON (data.mimetype_id = mimetypes._id) " 105 + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) " 106 + "LEFT OUTER JOIN packages ON (data.package_id = packages._id)"; 107 108 public static final String DATA_JOIN_PACKAGES_MIMETYPES_RAW_CONTACTS_CONTACTS = "data " 109 + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) " 110 + "JOIN mimetypes ON (data.mimetype_id = mimetypes._id) " 111 + "LEFT OUTER JOIN packages ON (data.package_id = packages._id) " 112 + "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)"; 113 114 public static final String RAW_CONTACTS_JOIN_CONTACTS = "raw_contacts " 115 + "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)"; 116 117 // NOTE: This requires late binding of GroupMembership MIME-type 118 public static final String RAW_CONTACTS_JOIN_SETTINGS_DATA_GROUPS = "raw_contacts " 119 + "LEFT OUTER JOIN settings ON (" 120 + "raw_contacts.account_name = settings.account_name AND " 121 + "raw_contacts.account_type = settings.account_type) " 122 + "LEFT OUTER JOIN data ON (data.mimetype_id=? AND " 123 + "data.raw_contact_id = raw_contacts._id) " 124 + "LEFT OUTER JOIN groups ON (groups._id = data." + GroupMembership.GROUP_ROW_ID 125 + ")"; 126 127 // NOTE: This requires late binding of GroupMembership MIME-type 128 public static final String SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS = "settings " 129 + "LEFT OUTER JOIN raw_contacts ON (" 130 + "raw_contacts.account_name = settings.account_name AND " 131 + "raw_contacts.account_type = settings.account_type) " 132 + "LEFT OUTER JOIN data ON (data.mimetype_id=? AND " 133 + "data.raw_contact_id = raw_contacts._id) " 134 + "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)"; 135 136 public static final String DATA_JOIN_MIMETYPES_RAW_CONTACTS_CONTACTS = "data " 137 + "JOIN mimetypes ON (data.mimetype_id = mimetypes._id) " 138 + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) " 139 + "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)"; 140 141 public static final String DATA_INNER_JOIN_MIMETYPES_RAW_CONTACTS_CONTACTS = "data " 142 + "JOIN mimetypes ON (data.mimetype_id = mimetypes._id) " 143 + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) " 144 + "JOIN contacts ON (raw_contacts.contact_id = contacts._id)"; 145 146 public static final String DATA_JOIN_PACKAGES_MIMETYPES_RAW_CONTACTS_CONTACTS_GROUPS = 147 "data " 148 + "JOIN mimetypes ON (data.mimetype_id = mimetypes._id) " 149 + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) " 150 + "LEFT OUTER JOIN packages ON (data.package_id = packages._id) " 151 + "LEFT OUTER JOIN groups " 152 + " ON (mimetypes.mimetype='" + GroupMembership.CONTENT_ITEM_TYPE + "' " 153 + " AND groups._id = data." + GroupMembership.GROUP_ROW_ID + ") " 154 + "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)"; 155 156 public static final String DATA_JOIN_PACKAGES_MIMETYPES_RAW_CONTACTS_GROUPS = "data " 157 + "JOIN mimetypes ON (data.mimetype_id = mimetypes._id) " 158 + "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) " 159 + "LEFT OUTER JOIN packages ON (data.package_id = packages._id) " 160 + "LEFT OUTER JOIN groups " 161 + " ON (mimetypes.mimetype='" + GroupMembership.CONTENT_ITEM_TYPE + "' " 162 + " AND groups._id = data." + GroupMembership.GROUP_ROW_ID + ") "; 163 164 public static final String GROUPS_JOIN_PACKAGES = "groups " 165 + "LEFT OUTER JOIN packages ON (groups.package_id = packages._id)"; 166 167 public static final String GROUPS_JOIN_PACKAGES_DATA_RAW_CONTACTS_CONTACTS = "groups " 168 + "LEFT OUTER JOIN packages ON (groups.package_id = packages._id) " 169 + "LEFT OUTER JOIN data " 170 + " ON (groups._id = data." + GroupMembership.GROUP_ROW_ID + ") " 171 + "LEFT OUTER JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) " 172 + "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)"; 173 174 public static final String ACTIVITIES = "activities"; 175 176 public static final String ACTIVITIES_JOIN_MIMETYPES = "activities " 177 + "LEFT OUTER JOIN mimetypes ON (activities.mimetype_id = mimetypes._id)"; 178 179 public static final String ACTIVITIES_JOIN_PACKAGES_MIMETYPES_RAW_CONTACTS_CONTACTS = 180 "activities " 181 + "LEFT OUTER JOIN packages ON (activities.package_id = packages._id) " 182 + "LEFT OUTER JOIN mimetypes ON (activities.mimetype_id = mimetypes._id) " 183 + "LEFT OUTER JOIN raw_contacts ON (activities.author_contact_id = " + 184 "raw_contacts._id) " 185 + "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)"; 186 187 public static final String NAME_LOOKUP_JOIN_RAW_CONTACTS = "name_lookup " 188 + "INNER JOIN raw_contacts ON (name_lookup.raw_contact_id = raw_contacts._id)"; 189 } 190 191 public interface Views { 192 public static final String DATA_ALL = "view_data"; 193 public static final String DATA_RESTRICTED = "view_data_restricted"; 194 195 public static final String RAW_CONTACTS_ALL = "view_raw_contacts"; 196 public static final String RAW_CONTACTS_RESTRICTED = "view_raw_contacts_restricted"; 197 198 public static final String CONTACTS_ALL = "view_contacts"; 199 public static final String CONTACTS_RESTRICTED = "view_contacts_restricted"; 200 201 public static final String GROUPS_ALL = "view_groups"; 202 } 203 204 public interface Clauses { 205 final String MIMETYPE_IS_GROUP_MEMBERSHIP = MimetypesColumns.CONCRETE_MIMETYPE + "='" 206 + GroupMembership.CONTENT_ITEM_TYPE + "'"; 207 208 final String BELONGS_TO_GROUP = DataColumns.CONCRETE_GROUP_ID + "=" 209 + GroupsColumns.CONCRETE_ID; 210 211 final String HAVING_NO_GROUPS = "COUNT(" + DataColumns.CONCRETE_GROUP_ID + ") == 0"; 212 213 final String GROUP_BY_ACCOUNT_CONTACT_ID = SettingsColumns.CONCRETE_ACCOUNT_NAME + "," 214 + SettingsColumns.CONCRETE_ACCOUNT_TYPE + "," + RawContacts.CONTACT_ID; 215 216 final String RAW_CONTACT_IS_LOCAL = RawContactsColumns.CONCRETE_ACCOUNT_NAME 217 + " IS NULL AND " + RawContactsColumns.CONCRETE_ACCOUNT_TYPE + " IS NULL"; 218 219 final String ZERO_GROUP_MEMBERSHIPS = "COUNT(" + GroupsColumns.CONCRETE_ID + ")=0"; 220 221 final String OUTER_RAW_CONTACTS = "outer_raw_contacts"; 222 final String OUTER_RAW_CONTACTS_ID = OUTER_RAW_CONTACTS + "." + RawContacts._ID; 223 224 final String CONTACT_IS_VISIBLE = 225 "SELECT " + 226 "MAX((SELECT (CASE WHEN " + 227 "(CASE" + 228 " WHEN " + RAW_CONTACT_IS_LOCAL + 229 " THEN 1 " + 230 " WHEN " + ZERO_GROUP_MEMBERSHIPS + 231 " THEN " + Settings.UNGROUPED_VISIBLE + 232 " ELSE MAX(" + Groups.GROUP_VISIBLE + ")" + 233 "END)=1 THEN 1 ELSE 0 END)" + 234 " FROM " + Tables.RAW_CONTACTS_JOIN_SETTINGS_DATA_GROUPS + 235 " WHERE " + RawContactsColumns.CONCRETE_ID + "=" + OUTER_RAW_CONTACTS_ID + "))" + 236 " FROM " + Tables.RAW_CONTACTS + " AS " + OUTER_RAW_CONTACTS + 237 " WHERE " + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + 238 " GROUP BY " + RawContacts.CONTACT_ID; 239 240 final String GROUP_HAS_ACCOUNT_AND_SOURCE_ID = Groups.SOURCE_ID + "=? AND " 241 + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?"; 242 } 243 244 public interface ContactsColumns { 245 /** 246 * This flag is set for a contact if it has only one constituent raw contact and 247 * it is restricted. 248 */ 249 public static final String SINGLE_IS_RESTRICTED = "single_is_restricted"; 250 251 public static final String LAST_STATUS_UPDATE_ID = "status_update_id"; 252 253 public static final String CONCRETE_ID = Tables.CONTACTS + "." + BaseColumns._ID; 254 public static final String CONCRETE_DISPLAY_NAME = Tables.CONTACTS + "." 255 + Contacts.DISPLAY_NAME; 256 257 public static final String CONCRETE_TIMES_CONTACTED = Tables.CONTACTS + "." 258 + Contacts.TIMES_CONTACTED; 259 public static final String CONCRETE_LAST_TIME_CONTACTED = Tables.CONTACTS + "." 260 + Contacts.LAST_TIME_CONTACTED; 261 public static final String CONCRETE_STARRED = Tables.CONTACTS + "." + Contacts.STARRED; 262 public static final String CONCRETE_CUSTOM_RINGTONE = Tables.CONTACTS + "." 263 + Contacts.CUSTOM_RINGTONE; 264 public static final String CONCRETE_SEND_TO_VOICEMAIL = Tables.CONTACTS + "." 265 + Contacts.SEND_TO_VOICEMAIL; 266 } 267 268 public interface RawContactsColumns { 269 public static final String CONCRETE_ID = 270 Tables.RAW_CONTACTS + "." + BaseColumns._ID; 271 public static final String CONCRETE_ACCOUNT_NAME = 272 Tables.RAW_CONTACTS + "." + RawContacts.ACCOUNT_NAME; 273 public static final String CONCRETE_ACCOUNT_TYPE = 274 Tables.RAW_CONTACTS + "." + RawContacts.ACCOUNT_TYPE; 275 public static final String CONCRETE_SOURCE_ID = 276 Tables.RAW_CONTACTS + "." + RawContacts.SOURCE_ID; 277 public static final String CONCRETE_VERSION = 278 Tables.RAW_CONTACTS + "." + RawContacts.VERSION; 279 public static final String CONCRETE_DIRTY = 280 Tables.RAW_CONTACTS + "." + RawContacts.DIRTY; 281 public static final String CONCRETE_DELETED = 282 Tables.RAW_CONTACTS + "." + RawContacts.DELETED; 283 public static final String CONCRETE_SYNC1 = 284 Tables.RAW_CONTACTS + "." + RawContacts.SYNC1; 285 public static final String CONCRETE_SYNC2 = 286 Tables.RAW_CONTACTS + "." + RawContacts.SYNC2; 287 public static final String CONCRETE_SYNC3 = 288 Tables.RAW_CONTACTS + "." + RawContacts.SYNC3; 289 public static final String CONCRETE_SYNC4 = 290 Tables.RAW_CONTACTS + "." + RawContacts.SYNC4; 291 public static final String CONCRETE_STARRED = 292 Tables.RAW_CONTACTS + "." + RawContacts.STARRED; 293 public static final String CONCRETE_IS_RESTRICTED = 294 Tables.RAW_CONTACTS + "." + RawContacts.IS_RESTRICTED; 295 296 public static final String DISPLAY_NAME = "display_name"; 297 public static final String DISPLAY_NAME_SOURCE = "display_name_source"; 298 public static final String AGGREGATION_NEEDED = "aggregation_needed"; 299 } 300 301 /** 302 * Types of data used to produce the display name for a contact. Listed in the order 303 * of increasing priority. 304 */ 305 public interface DisplayNameSources { 306 int UNDEFINED = 0; 307 int EMAIL = 10; 308 int PHONE = 20; 309 int ORGANIZATION = 30; 310 int NICKNAME = 35; 311 int STRUCTURED_NAME = 40; 312 } 313 314 public interface DataColumns { 315 public static final String PACKAGE_ID = "package_id"; 316 public static final String MIMETYPE_ID = "mimetype_id"; 317 318 public static final String CONCRETE_ID = Tables.DATA + "." + BaseColumns._ID; 319 public static final String CONCRETE_MIMETYPE_ID = Tables.DATA + "." + MIMETYPE_ID; 320 public static final String CONCRETE_RAW_CONTACT_ID = Tables.DATA + "." 321 + Data.RAW_CONTACT_ID; 322 public static final String CONCRETE_GROUP_ID = Tables.DATA + "." 323 + GroupMembership.GROUP_ROW_ID; 324 325 public static final String CONCRETE_DATA1 = Tables.DATA + "." + Data.DATA1; 326 public static final String CONCRETE_DATA2 = Tables.DATA + "." + Data.DATA2; 327 public static final String CONCRETE_DATA3 = Tables.DATA + "." + Data.DATA3; 328 public static final String CONCRETE_DATA4 = Tables.DATA + "." + Data.DATA4; 329 public static final String CONCRETE_DATA5 = Tables.DATA + "." + Data.DATA5; 330 public static final String CONCRETE_DATA6 = Tables.DATA + "." + Data.DATA6; 331 public static final String CONCRETE_DATA7 = Tables.DATA + "." + Data.DATA7; 332 public static final String CONCRETE_DATA8 = Tables.DATA + "." + Data.DATA8; 333 public static final String CONCRETE_DATA9 = Tables.DATA + "." + Data.DATA9; 334 public static final String CONCRETE_DATA10 = Tables.DATA + "." + Data.DATA10; 335 public static final String CONCRETE_DATA11 = Tables.DATA + "." + Data.DATA11; 336 public static final String CONCRETE_DATA12 = Tables.DATA + "." + Data.DATA12; 337 public static final String CONCRETE_DATA13 = Tables.DATA + "." + Data.DATA13; 338 public static final String CONCRETE_DATA14 = Tables.DATA + "." + Data.DATA14; 339 public static final String CONCRETE_DATA15 = Tables.DATA + "." + Data.DATA15; 340 public static final String CONCRETE_IS_PRIMARY = Tables.DATA + "." + Data.IS_PRIMARY; 341 public static final String CONCRETE_PACKAGE_ID = Tables.DATA + "." + PACKAGE_ID; 342 } 343 344 // Used only for legacy API support 345 public interface ExtensionsColumns { 346 public static final String NAME = Data.DATA1; 347 public static final String VALUE = Data.DATA2; 348 } 349 350 public interface GroupMembershipColumns { 351 public static final String RAW_CONTACT_ID = Data.RAW_CONTACT_ID; 352 public static final String GROUP_ROW_ID = GroupMembership.GROUP_ROW_ID; 353 } 354 355 public interface PhoneColumns { 356 public static final String NORMALIZED_NUMBER = Data.DATA4; 357 public static final String CONCRETE_NORMALIZED_NUMBER = DataColumns.CONCRETE_DATA4; 358 } 359 360 public interface GroupsColumns { 361 public static final String PACKAGE_ID = "package_id"; 362 363 public static final String CONCRETE_ID = Tables.GROUPS + "." + BaseColumns._ID; 364 public static final String CONCRETE_SOURCE_ID = Tables.GROUPS + "." + Groups.SOURCE_ID; 365 public static final String CONCRETE_ACCOUNT_NAME = Tables.GROUPS + "." + Groups.ACCOUNT_NAME; 366 public static final String CONCRETE_ACCOUNT_TYPE = Tables.GROUPS + "." + Groups.ACCOUNT_TYPE; 367 } 368 369 public interface ActivitiesColumns { 370 public static final String PACKAGE_ID = "package_id"; 371 public static final String MIMETYPE_ID = "mimetype_id"; 372 } 373 374 public interface PhoneLookupColumns { 375 public static final String _ID = BaseColumns._ID; 376 public static final String DATA_ID = "data_id"; 377 public static final String RAW_CONTACT_ID = "raw_contact_id"; 378 public static final String NORMALIZED_NUMBER = "normalized_number"; 379 } 380 381 public interface NameLookupColumns { 382 public static final String RAW_CONTACT_ID = "raw_contact_id"; 383 public static final String DATA_ID = "data_id"; 384 public static final String NORMALIZED_NAME = "normalized_name"; 385 public static final String NAME_TYPE = "name_type"; 386 } 387 388 public final static class NameLookupType { 389 public static final int NAME_EXACT = 0; 390 public static final int NAME_VARIANT = 1; 391 public static final int NAME_COLLATION_KEY = 2; 392 public static final int NICKNAME = 3; 393 public static final int EMAIL_BASED_NICKNAME = 4; 394 public static final int ORGANIZATION = 5; 395 396 // This is the highest name lookup type code plus one 397 public static final int TYPE_COUNT = 6; 398 isBasedOnStructuredName(int nameLookupType)399 public static boolean isBasedOnStructuredName(int nameLookupType) { 400 return nameLookupType == NameLookupType.NAME_EXACT 401 || nameLookupType == NameLookupType.NAME_VARIANT 402 || nameLookupType == NameLookupType.NAME_COLLATION_KEY; 403 } 404 } 405 406 public interface PackagesColumns { 407 public static final String _ID = BaseColumns._ID; 408 public static final String PACKAGE = "package"; 409 410 public static final String CONCRETE_ID = Tables.PACKAGES + "." + _ID; 411 } 412 413 public interface MimetypesColumns { 414 public static final String _ID = BaseColumns._ID; 415 public static final String MIMETYPE = "mimetype"; 416 417 public static final String CONCRETE_ID = Tables.MIMETYPES + "." + BaseColumns._ID; 418 public static final String CONCRETE_MIMETYPE = Tables.MIMETYPES + "." + MIMETYPE; 419 } 420 421 public interface AggregationExceptionColumns { 422 public static final String _ID = BaseColumns._ID; 423 } 424 425 public interface NicknameLookupColumns { 426 public static final String NAME = "name"; 427 public static final String CLUSTER = "cluster"; 428 } 429 430 public interface SettingsColumns { 431 public static final String CONCRETE_ACCOUNT_NAME = Tables.SETTINGS + "." 432 + Settings.ACCOUNT_NAME; 433 public static final String CONCRETE_ACCOUNT_TYPE = Tables.SETTINGS + "." 434 + Settings.ACCOUNT_TYPE; 435 } 436 437 public interface PresenceColumns { 438 String RAW_CONTACT_ID = "presence_raw_contact_id"; 439 String CONTACT_ID = "presence_contact_id"; 440 } 441 442 public interface AggregatedPresenceColumns { 443 String CONTACT_ID = "presence_contact_id"; 444 445 String CONCRETE_CONTACT_ID = Tables.AGGREGATED_PRESENCE + "." + CONTACT_ID; 446 } 447 448 public interface StatusUpdatesColumns { 449 String DATA_ID = "status_update_data_id"; 450 451 String CONCRETE_DATA_ID = Tables.STATUS_UPDATES + "." + DATA_ID; 452 453 String CONCRETE_PRESENCE = Tables.STATUS_UPDATES + "." + StatusUpdates.PRESENCE; 454 String CONCRETE_STATUS = Tables.STATUS_UPDATES + "." + StatusUpdates.STATUS; 455 String CONCRETE_STATUS_TIMESTAMP = Tables.STATUS_UPDATES + "." 456 + StatusUpdates.STATUS_TIMESTAMP; 457 String CONCRETE_STATUS_RES_PACKAGE = Tables.STATUS_UPDATES + "." 458 + StatusUpdates.STATUS_RES_PACKAGE; 459 String CONCRETE_STATUS_LABEL = Tables.STATUS_UPDATES + "." + StatusUpdates.STATUS_LABEL; 460 String CONCRETE_STATUS_ICON = Tables.STATUS_UPDATES + "." + StatusUpdates.STATUS_ICON; 461 } 462 463 public interface ContactsStatusUpdatesColumns { 464 String ALIAS = "contacts_" + Tables.STATUS_UPDATES; 465 466 String CONCRETE_DATA_ID = ALIAS + "." + StatusUpdatesColumns.DATA_ID; 467 468 String CONCRETE_PRESENCE = ALIAS + "." + StatusUpdates.PRESENCE; 469 String CONCRETE_STATUS = ALIAS + "." + StatusUpdates.STATUS; 470 String CONCRETE_STATUS_TIMESTAMP = ALIAS + "." + StatusUpdates.STATUS_TIMESTAMP; 471 String CONCRETE_STATUS_RES_PACKAGE = ALIAS + "." + StatusUpdates.STATUS_RES_PACKAGE; 472 String CONCRETE_STATUS_LABEL = ALIAS + "." + StatusUpdates.STATUS_LABEL; 473 String CONCRETE_STATUS_ICON = ALIAS + "." + StatusUpdates.STATUS_ICON; 474 } 475 476 /** In-memory cache of previously found MIME-type mappings */ 477 private final HashMap<String, Long> mMimetypeCache = new HashMap<String, Long>(); 478 /** In-memory cache of previously found package name mappings */ 479 private final HashMap<String, Long> mPackageCache = new HashMap<String, Long>(); 480 481 482 /** Compiled statements for querying and inserting mappings */ 483 private SQLiteStatement mMimetypeQuery; 484 private SQLiteStatement mPackageQuery; 485 private SQLiteStatement mContactIdQuery; 486 private SQLiteStatement mAggregationModeQuery; 487 private SQLiteStatement mMimetypeInsert; 488 private SQLiteStatement mPackageInsert; 489 private SQLiteStatement mDataMimetypeQuery; 490 private SQLiteStatement mActivitiesMimetypeQuery; 491 492 private final Context mContext; 493 private final SyncStateContentProviderHelper mSyncState; 494 495 496 /** Compiled statements for updating {@link Contacts#IN_VISIBLE_GROUP}. */ 497 private SQLiteStatement mVisibleUpdate; 498 private SQLiteStatement mVisibleSpecificUpdate; 499 500 private boolean mReopenDatabase = false; 501 502 private static ContactsDatabaseHelper sSingleton = null; 503 504 private boolean mUseStrictPhoneNumberComparation; 505 506 /** 507 * List of package names with access to {@link RawContacts#IS_RESTRICTED} data. 508 */ 509 private String[] mUnrestrictedPackages; 510 getInstance(Context context)511 public static synchronized ContactsDatabaseHelper getInstance(Context context) { 512 if (sSingleton == null) { 513 sSingleton = new ContactsDatabaseHelper(context); 514 } 515 return sSingleton; 516 } 517 518 /** 519 * Private constructor, callers except unit tests should obtain an instance through 520 * {@link #getInstance(android.content.Context)} instead. 521 */ ContactsDatabaseHelper(Context context)522 ContactsDatabaseHelper(Context context) { 523 super(context, DATABASE_NAME, null, DATABASE_VERSION); 524 if (false) Log.i(TAG, "Creating OpenHelper"); 525 Resources resources = context.getResources(); 526 527 mContext = context; 528 mSyncState = new SyncStateContentProviderHelper(); 529 mUseStrictPhoneNumberComparation = 530 resources.getBoolean( 531 com.android.internal.R.bool.config_use_strict_phone_number_comparation); 532 int resourceId = resources.getIdentifier("unrestricted_packages", "array", 533 context.getPackageName()); 534 if (resourceId != 0) { 535 mUnrestrictedPackages = resources.getStringArray(resourceId); 536 } else { 537 mUnrestrictedPackages = new String[0]; 538 } 539 } 540 541 @Override onOpen(SQLiteDatabase db)542 public void onOpen(SQLiteDatabase db) { 543 mSyncState.onDatabaseOpened(db); 544 545 // Create compiled statements for package and mimetype lookups 546 mMimetypeQuery = db.compileStatement("SELECT " + MimetypesColumns._ID + " FROM " 547 + Tables.MIMETYPES + " WHERE " + MimetypesColumns.MIMETYPE + "=?"); 548 mPackageQuery = db.compileStatement("SELECT " + PackagesColumns._ID + " FROM " 549 + Tables.PACKAGES + " WHERE " + PackagesColumns.PACKAGE + "=?"); 550 mContactIdQuery = db.compileStatement("SELECT " + RawContacts.CONTACT_ID + " FROM " 551 + Tables.RAW_CONTACTS + " WHERE " + RawContacts._ID + "=?"); 552 mAggregationModeQuery = db.compileStatement("SELECT " + RawContacts.AGGREGATION_MODE 553 + " FROM " + Tables.RAW_CONTACTS + " WHERE " + RawContacts._ID + "=?"); 554 mMimetypeInsert = db.compileStatement("INSERT INTO " + Tables.MIMETYPES + "(" 555 + MimetypesColumns.MIMETYPE + ") VALUES (?)"); 556 mPackageInsert = db.compileStatement("INSERT INTO " + Tables.PACKAGES + "(" 557 + PackagesColumns.PACKAGE + ") VALUES (?)"); 558 559 mDataMimetypeQuery = db.compileStatement("SELECT " + MimetypesColumns.MIMETYPE + " FROM " 560 + Tables.DATA_JOIN_MIMETYPES + " WHERE " + Tables.DATA + "." + Data._ID + "=?"); 561 mActivitiesMimetypeQuery = db.compileStatement("SELECT " + MimetypesColumns.MIMETYPE 562 + " FROM " + Tables.ACTIVITIES_JOIN_MIMETYPES + " WHERE " + Tables.ACTIVITIES + "." 563 + Activities._ID + "=?"); 564 565 // Compile statements for updating visibility 566 final String visibleUpdate = "UPDATE " + Tables.CONTACTS + " SET " 567 + Contacts.IN_VISIBLE_GROUP + "=(" + Clauses.CONTACT_IS_VISIBLE + ")"; 568 569 mVisibleUpdate = db.compileStatement(visibleUpdate); 570 mVisibleSpecificUpdate = db.compileStatement(visibleUpdate + " WHERE " 571 + ContactsColumns.CONCRETE_ID + "=?"); 572 573 db.execSQL("ATTACH DATABASE ':memory:' AS " + DATABASE_PRESENCE + ";"); 574 db.execSQL("CREATE TABLE IF NOT EXISTS " + DATABASE_PRESENCE + "." + Tables.PRESENCE + " ("+ 575 StatusUpdates.DATA_ID + " INTEGER PRIMARY KEY REFERENCES data(_id)," + 576 StatusUpdates.PROTOCOL + " INTEGER NOT NULL," + 577 StatusUpdates.CUSTOM_PROTOCOL + " TEXT," + 578 StatusUpdates.IM_HANDLE + " TEXT," + 579 StatusUpdates.IM_ACCOUNT + " TEXT," + 580 PresenceColumns.CONTACT_ID + " INTEGER REFERENCES contacts(_id)," + 581 PresenceColumns.RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)," + 582 StatusUpdates.PRESENCE + " INTEGER," + 583 "UNIQUE(" + StatusUpdates.PROTOCOL + ", " + StatusUpdates.CUSTOM_PROTOCOL 584 + ", " + StatusUpdates.IM_HANDLE + ", " + StatusUpdates.IM_ACCOUNT + ")" + 585 ");"); 586 587 db.execSQL("CREATE INDEX IF NOT EXISTS " + DATABASE_PRESENCE + ".presenceIndex" + " ON " 588 + Tables.PRESENCE + " (" + PresenceColumns.RAW_CONTACT_ID + ");"); 589 590 db.execSQL("CREATE TABLE IF NOT EXISTS " 591 + DATABASE_PRESENCE + "." + Tables.AGGREGATED_PRESENCE + " ("+ 592 AggregatedPresenceColumns.CONTACT_ID 593 + " INTEGER PRIMARY KEY REFERENCES contacts(_id)," + 594 StatusUpdates.PRESENCE_STATUS + " INTEGER" + 595 ");"); 596 597 598 db.execSQL("CREATE TRIGGER " + DATABASE_PRESENCE + "." + Tables.PRESENCE + "_deleted" 599 + " BEFORE DELETE ON " + DATABASE_PRESENCE + "." + Tables.PRESENCE 600 + " BEGIN " 601 + " DELETE FROM " + Tables.AGGREGATED_PRESENCE 602 + " WHERE " + AggregatedPresenceColumns.CONTACT_ID + " = " + 603 "(SELECT " + PresenceColumns.CONTACT_ID + 604 " FROM " + Tables.PRESENCE + 605 " WHERE " + PresenceColumns.RAW_CONTACT_ID 606 + "=OLD." + PresenceColumns.RAW_CONTACT_ID + 607 " AND NOT EXISTS" + 608 "(SELECT " + PresenceColumns.RAW_CONTACT_ID + 609 " FROM " + Tables.PRESENCE + 610 " WHERE " + PresenceColumns.CONTACT_ID 611 + "=OLD." + PresenceColumns.CONTACT_ID + 612 " AND " + PresenceColumns.RAW_CONTACT_ID 613 + "!=OLD." + PresenceColumns.RAW_CONTACT_ID + "));" 614 + " END"); 615 616 String replaceAggregatePresenceSql = 617 "INSERT OR REPLACE INTO " + Tables.AGGREGATED_PRESENCE + "(" 618 + AggregatedPresenceColumns.CONTACT_ID + ", " 619 + StatusUpdates.PRESENCE_STATUS + ")" + 620 " SELECT " + PresenceColumns.CONTACT_ID + "," 621 + "MAX(" + StatusUpdates.PRESENCE_STATUS + ")" + 622 " FROM " + Tables.PRESENCE + 623 " WHERE " + PresenceColumns.CONTACT_ID 624 + "=NEW." + PresenceColumns.CONTACT_ID + ";"; 625 626 db.execSQL("CREATE TRIGGER " + DATABASE_PRESENCE + "." + Tables.PRESENCE + "_inserted" 627 + " AFTER INSERT ON " + DATABASE_PRESENCE + "." + Tables.PRESENCE 628 + " BEGIN " 629 + replaceAggregatePresenceSql 630 + " END"); 631 632 db.execSQL("CREATE TRIGGER " + DATABASE_PRESENCE + "." + Tables.PRESENCE + "_updated" 633 + " AFTER UPDATE ON " + DATABASE_PRESENCE + "." + Tables.PRESENCE 634 + " BEGIN " 635 + replaceAggregatePresenceSql 636 + " END"); 637 } 638 639 @Override onCreate(SQLiteDatabase db)640 public void onCreate(SQLiteDatabase db) { 641 Log.i(TAG, "Bootstrapping database"); 642 643 mSyncState.createDatabase(db); 644 645 // One row per group of contacts corresponding to the same person 646 db.execSQL("CREATE TABLE " + Tables.CONTACTS + " (" + 647 BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 648 Contacts.DISPLAY_NAME + " TEXT," + 649 Contacts.PHOTO_ID + " INTEGER REFERENCES data(_id)," + 650 Contacts.CUSTOM_RINGTONE + " TEXT," + 651 Contacts.SEND_TO_VOICEMAIL + " INTEGER NOT NULL DEFAULT 0," + 652 Contacts.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," + 653 Contacts.LAST_TIME_CONTACTED + " INTEGER," + 654 Contacts.STARRED + " INTEGER NOT NULL DEFAULT 0," + 655 Contacts.IN_VISIBLE_GROUP + " INTEGER NOT NULL DEFAULT 1," + 656 Contacts.HAS_PHONE_NUMBER + " INTEGER NOT NULL DEFAULT 0," + 657 Contacts.LOOKUP_KEY + " TEXT," + 658 ContactsColumns.LAST_STATUS_UPDATE_ID + " INTEGER REFERENCES data(_id)," + 659 ContactsColumns.SINGLE_IS_RESTRICTED + " INTEGER NOT NULL DEFAULT 0" + 660 ");"); 661 662 db.execSQL("CREATE INDEX contacts_visible_index ON " + Tables.CONTACTS + " (" + 663 Contacts.IN_VISIBLE_GROUP + "," + 664 Contacts.DISPLAY_NAME + " COLLATE LOCALIZED" + 665 ");"); 666 667 db.execSQL("CREATE INDEX contacts_has_phone_index ON " + Tables.CONTACTS + " (" + 668 Contacts.HAS_PHONE_NUMBER + 669 ");"); 670 671 db.execSQL("CREATE INDEX contacts_restricted_index ON " + Tables.CONTACTS + " (" + 672 ContactsColumns.SINGLE_IS_RESTRICTED + 673 ");"); 674 675 // Contacts table 676 db.execSQL("CREATE TABLE " + Tables.RAW_CONTACTS + " (" + 677 RawContacts._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 678 RawContacts.IS_RESTRICTED + " INTEGER DEFAULT 0," + 679 RawContacts.ACCOUNT_NAME + " STRING DEFAULT NULL, " + 680 RawContacts.ACCOUNT_TYPE + " STRING DEFAULT NULL, " + 681 RawContacts.SOURCE_ID + " TEXT," + 682 RawContacts.VERSION + " INTEGER NOT NULL DEFAULT 1," + 683 RawContacts.DIRTY + " INTEGER NOT NULL DEFAULT 0," + 684 RawContacts.DELETED + " INTEGER NOT NULL DEFAULT 0," + 685 RawContacts.CONTACT_ID + " INTEGER REFERENCES contacts(_id)," + 686 RawContacts.AGGREGATION_MODE + " INTEGER NOT NULL DEFAULT " + 687 RawContacts.AGGREGATION_MODE_DEFAULT + "," + 688 RawContactsColumns.AGGREGATION_NEEDED + " INTEGER NOT NULL DEFAULT 1," + 689 RawContacts.CUSTOM_RINGTONE + " TEXT," + 690 RawContacts.SEND_TO_VOICEMAIL + " INTEGER NOT NULL DEFAULT 0," + 691 RawContacts.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," + 692 RawContacts.LAST_TIME_CONTACTED + " INTEGER," + 693 RawContacts.STARRED + " INTEGER NOT NULL DEFAULT 0," + 694 RawContactsColumns.DISPLAY_NAME + " TEXT," + 695 RawContactsColumns.DISPLAY_NAME_SOURCE + " INTEGER NOT NULL DEFAULT " + 696 DisplayNameSources.UNDEFINED + "," + 697 RawContacts.SYNC1 + " TEXT, " + 698 RawContacts.SYNC2 + " TEXT, " + 699 RawContacts.SYNC3 + " TEXT, " + 700 RawContacts.SYNC4 + " TEXT " + 701 ");"); 702 703 db.execSQL("CREATE TRIGGER raw_contacts_times_contacted UPDATE OF " + 704 RawContacts.LAST_TIME_CONTACTED + " ON " + Tables.RAW_CONTACTS + " " + 705 "BEGIN " + 706 "UPDATE " + Tables.RAW_CONTACTS + " SET " 707 + RawContacts.TIMES_CONTACTED + " = " + "" + 708 "(new." + RawContacts.TIMES_CONTACTED + " + 1)" 709 + " WHERE _id = new._id;" + 710 "END"); 711 712 db.execSQL("CREATE INDEX raw_contacts_contact_id_index ON " + Tables.RAW_CONTACTS + " (" + 713 RawContacts.CONTACT_ID + 714 ");"); 715 716 db.execSQL("CREATE INDEX raw_contacts_source_id_index ON " + Tables.RAW_CONTACTS + " (" + 717 RawContacts.SOURCE_ID + ", " + 718 RawContacts.ACCOUNT_TYPE + ", " + 719 RawContacts.ACCOUNT_NAME + 720 ");"); 721 722 // TODO readd the index and investigate a controlled use of it 723 // db.execSQL("CREATE INDEX raw_contacts_agg_index ON " + Tables.RAW_CONTACTS + " (" + 724 // RawContactsColumns.AGGREGATION_NEEDED + 725 // ");"); 726 727 // Package name mapping table 728 db.execSQL("CREATE TABLE " + Tables.PACKAGES + " (" + 729 PackagesColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 730 PackagesColumns.PACKAGE + " TEXT NOT NULL" + 731 ");"); 732 733 // Mimetype mapping table 734 db.execSQL("CREATE TABLE " + Tables.MIMETYPES + " (" + 735 MimetypesColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 736 MimetypesColumns.MIMETYPE + " TEXT NOT NULL" + 737 ");"); 738 739 // Public generic data table 740 db.execSQL("CREATE TABLE " + Tables.DATA + " (" + 741 Data._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 742 DataColumns.PACKAGE_ID + " INTEGER REFERENCES package(_id)," + 743 DataColumns.MIMETYPE_ID + " INTEGER REFERENCES mimetype(_id) NOT NULL," + 744 Data.RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," + 745 Data.IS_PRIMARY + " INTEGER NOT NULL DEFAULT 0," + 746 Data.IS_SUPER_PRIMARY + " INTEGER NOT NULL DEFAULT 0," + 747 Data.DATA_VERSION + " INTEGER NOT NULL DEFAULT 0," + 748 Data.DATA1 + " TEXT," + 749 Data.DATA2 + " TEXT," + 750 Data.DATA3 + " TEXT," + 751 Data.DATA4 + " TEXT," + 752 Data.DATA5 + " TEXT," + 753 Data.DATA6 + " TEXT," + 754 Data.DATA7 + " TEXT," + 755 Data.DATA8 + " TEXT," + 756 Data.DATA9 + " TEXT," + 757 Data.DATA10 + " TEXT," + 758 Data.DATA11 + " TEXT," + 759 Data.DATA12 + " TEXT," + 760 Data.DATA13 + " TEXT," + 761 Data.DATA14 + " TEXT," + 762 Data.DATA15 + " TEXT," + 763 Data.SYNC1 + " TEXT, " + 764 Data.SYNC2 + " TEXT, " + 765 Data.SYNC3 + " TEXT, " + 766 Data.SYNC4 + " TEXT " + 767 ");"); 768 769 db.execSQL("CREATE INDEX data_raw_contact_id ON " + Tables.DATA + " (" + 770 Data.RAW_CONTACT_ID + 771 ");"); 772 773 /** 774 * For email lookup and similar queries. 775 */ 776 db.execSQL("CREATE INDEX data_mimetype_data1_index ON " + Tables.DATA + " (" + 777 DataColumns.MIMETYPE_ID + "," + 778 Data.DATA1 + 779 ");"); 780 781 // Private phone numbers table used for lookup 782 db.execSQL("CREATE TABLE " + Tables.PHONE_LOOKUP + " (" + 783 PhoneLookupColumns.DATA_ID 784 + " INTEGER PRIMARY KEY REFERENCES data(_id) NOT NULL," + 785 PhoneLookupColumns.RAW_CONTACT_ID 786 + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," + 787 PhoneLookupColumns.NORMALIZED_NUMBER + " TEXT NOT NULL" + 788 ");"); 789 790 db.execSQL("CREATE INDEX phone_lookup_index ON " + Tables.PHONE_LOOKUP + " (" + 791 PhoneLookupColumns.NORMALIZED_NUMBER + "," + 792 PhoneLookupColumns.RAW_CONTACT_ID + "," + 793 PhoneLookupColumns.DATA_ID + 794 ");"); 795 796 // Private name/nickname table used for lookup 797 db.execSQL("CREATE TABLE " + Tables.NAME_LOOKUP + " (" + 798 NameLookupColumns.DATA_ID 799 + " INTEGER REFERENCES data(_id) NOT NULL," + 800 NameLookupColumns.RAW_CONTACT_ID 801 + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," + 802 NameLookupColumns.NORMALIZED_NAME + " TEXT NOT NULL," + 803 NameLookupColumns.NAME_TYPE + " INTEGER NOT NULL," + 804 "PRIMARY KEY (" 805 + NameLookupColumns.DATA_ID + ", " 806 + NameLookupColumns.NORMALIZED_NAME + ", " 807 + NameLookupColumns.NAME_TYPE + ")" + 808 ");"); 809 810 db.execSQL("CREATE INDEX name_lookup_index ON " + Tables.NAME_LOOKUP + " (" + 811 NameLookupColumns.NORMALIZED_NAME + "," + 812 NameLookupColumns.NAME_TYPE + ", " + 813 NameLookupColumns.RAW_CONTACT_ID + 814 ");"); 815 816 db.execSQL("CREATE INDEX name_lookup_raw_contact_id_index ON " + Tables.NAME_LOOKUP + " (" + 817 NameLookupColumns.RAW_CONTACT_ID + 818 ");"); 819 820 db.execSQL("CREATE TABLE " + Tables.NICKNAME_LOOKUP + " (" + 821 NicknameLookupColumns.NAME + " TEXT," + 822 NicknameLookupColumns.CLUSTER + " TEXT" + 823 ");"); 824 825 db.execSQL("CREATE UNIQUE INDEX nickname_lookup_index ON " + Tables.NICKNAME_LOOKUP + " (" + 826 NicknameLookupColumns.NAME + ", " + 827 NicknameLookupColumns.CLUSTER + 828 ");"); 829 830 // Groups table 831 db.execSQL("CREATE TABLE " + Tables.GROUPS + " (" + 832 Groups._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 833 GroupsColumns.PACKAGE_ID + " INTEGER REFERENCES package(_id)," + 834 Groups.ACCOUNT_NAME + " STRING DEFAULT NULL, " + 835 Groups.ACCOUNT_TYPE + " STRING DEFAULT NULL, " + 836 Groups.SOURCE_ID + " TEXT," + 837 Groups.VERSION + " INTEGER NOT NULL DEFAULT 1," + 838 Groups.DIRTY + " INTEGER NOT NULL DEFAULT 0," + 839 Groups.TITLE + " TEXT," + 840 Groups.TITLE_RES + " INTEGER," + 841 Groups.NOTES + " TEXT," + 842 Groups.SYSTEM_ID + " TEXT," + 843 Groups.DELETED + " INTEGER NOT NULL DEFAULT 0," + 844 Groups.GROUP_VISIBLE + " INTEGER NOT NULL DEFAULT 0," + 845 Groups.SHOULD_SYNC + " INTEGER NOT NULL DEFAULT 1," + 846 Groups.SYNC1 + " TEXT, " + 847 Groups.SYNC2 + " TEXT, " + 848 Groups.SYNC3 + " TEXT, " + 849 Groups.SYNC4 + " TEXT " + 850 ");"); 851 852 db.execSQL("CREATE INDEX groups_source_id_index ON " + Tables.GROUPS + " (" + 853 Groups.SOURCE_ID + ", " + 854 Groups.ACCOUNT_TYPE + ", " + 855 Groups.ACCOUNT_NAME + 856 ");"); 857 858 db.execSQL("CREATE TABLE IF NOT EXISTS " + Tables.AGGREGATION_EXCEPTIONS + " (" + 859 AggregationExceptionColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 860 AggregationExceptions.TYPE + " INTEGER NOT NULL, " + 861 AggregationExceptions.RAW_CONTACT_ID1 862 + " INTEGER REFERENCES raw_contacts(_id), " + 863 AggregationExceptions.RAW_CONTACT_ID2 864 + " INTEGER REFERENCES raw_contacts(_id)" + 865 ");"); 866 867 db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS aggregation_exception_index1 ON " + 868 Tables.AGGREGATION_EXCEPTIONS + " (" + 869 AggregationExceptions.RAW_CONTACT_ID1 + ", " + 870 AggregationExceptions.RAW_CONTACT_ID2 + 871 ");"); 872 873 db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS aggregation_exception_index2 ON " + 874 Tables.AGGREGATION_EXCEPTIONS + " (" + 875 AggregationExceptions.RAW_CONTACT_ID2 + ", " + 876 AggregationExceptions.RAW_CONTACT_ID1 + 877 ");"); 878 879 db.execSQL("CREATE TABLE IF NOT EXISTS " + Tables.SETTINGS + " (" + 880 Settings.ACCOUNT_NAME + " STRING NOT NULL," + 881 Settings.ACCOUNT_TYPE + " STRING NOT NULL," + 882 Settings.UNGROUPED_VISIBLE + " INTEGER NOT NULL DEFAULT 0," + 883 Settings.SHOULD_SYNC + " INTEGER NOT NULL DEFAULT 1, " + 884 "PRIMARY KEY (" + Settings.ACCOUNT_NAME + ", " + 885 Settings.ACCOUNT_TYPE + ") ON CONFLICT REPLACE" + 886 ");"); 887 888 // The table for recent calls is here so we can do table joins 889 // on people, phones, and calls all in one place. 890 db.execSQL("CREATE TABLE " + Tables.CALLS + " (" + 891 Calls._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 892 Calls.NUMBER + " TEXT," + 893 Calls.DATE + " INTEGER," + 894 Calls.DURATION + " INTEGER," + 895 Calls.TYPE + " INTEGER," + 896 Calls.NEW + " INTEGER," + 897 Calls.CACHED_NAME + " TEXT," + 898 Calls.CACHED_NUMBER_TYPE + " INTEGER," + 899 Calls.CACHED_NUMBER_LABEL + " TEXT" + 900 ");"); 901 902 // Activities table 903 db.execSQL("CREATE TABLE " + Tables.ACTIVITIES + " (" + 904 Activities._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 905 ActivitiesColumns.PACKAGE_ID + " INTEGER REFERENCES package(_id)," + 906 ActivitiesColumns.MIMETYPE_ID + " INTEGER REFERENCES mimetype(_id) NOT NULL," + 907 Activities.RAW_ID + " TEXT," + 908 Activities.IN_REPLY_TO + " TEXT," + 909 Activities.AUTHOR_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)," + 910 Activities.TARGET_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)," + 911 Activities.PUBLISHED + " INTEGER NOT NULL," + 912 Activities.THREAD_PUBLISHED + " INTEGER NOT NULL," + 913 Activities.TITLE + " TEXT NOT NULL," + 914 Activities.SUMMARY + " TEXT," + 915 Activities.LINK + " TEXT, " + 916 Activities.THUMBNAIL + " BLOB" + 917 ");"); 918 919 db.execSQL("CREATE TABLE " + Tables.STATUS_UPDATES + " (" + 920 StatusUpdatesColumns.DATA_ID + " INTEGER PRIMARY KEY REFERENCES data(_id)," + 921 StatusUpdates.STATUS + " TEXT," + 922 StatusUpdates.STATUS_TIMESTAMP + " INTEGER," + 923 StatusUpdates.STATUS_RES_PACKAGE + " TEXT, " + 924 StatusUpdates.STATUS_LABEL + " INTEGER, " + 925 StatusUpdates.STATUS_ICON + " INTEGER" + 926 ");"); 927 928 createContactsViews(db); 929 createGroupsView(db); 930 createContactEntitiesView(db); 931 createContactsTriggers(db); 932 933 loadNicknameLookupTable(db); 934 935 // Add the legacy API support views, etc 936 LegacyApiSupport.createDatabase(db); 937 938 // This will create a sqlite_stat1 table that is used for query optimization 939 db.execSQL("ANALYZE;"); 940 941 updateSqliteStats(db); 942 943 // We need to close and reopen the database connection so that the stats are 944 // taken into account. Make a note of it and do the actual reopening in the 945 // getWritableDatabase method. 946 mReopenDatabase = true; 947 948 ContentResolver.requestSync(null /* all accounts */, 949 ContactsContract.AUTHORITY, new Bundle()); 950 } 951 createContactsTriggers(SQLiteDatabase db)952 private void createContactsTriggers(SQLiteDatabase db) { 953 954 /* 955 * Automatically delete Data rows when a raw contact is deleted. 956 */ 957 db.execSQL("DROP TRIGGER IF EXISTS " + Tables.RAW_CONTACTS + "_deleted;"); 958 db.execSQL("CREATE TRIGGER " + Tables.RAW_CONTACTS + "_deleted " 959 + " BEFORE DELETE ON " + Tables.RAW_CONTACTS 960 + " BEGIN " 961 + " DELETE FROM " + Tables.DATA 962 + " WHERE " + Data.RAW_CONTACT_ID 963 + "=OLD." + RawContacts._ID + ";" 964 + " DELETE FROM " + Tables.AGGREGATION_EXCEPTIONS 965 + " WHERE " + AggregationExceptions.RAW_CONTACT_ID1 966 + "=OLD." + RawContacts._ID 967 + " OR " + AggregationExceptions.RAW_CONTACT_ID2 968 + "=OLD." + RawContacts._ID + ";" 969 + " DELETE FROM " + Tables.CONTACTS 970 + " WHERE " + Contacts._ID + "=OLD." + RawContacts.CONTACT_ID 971 + " AND (SELECT COUNT(*) FROM " + Tables.RAW_CONTACTS 972 + " WHERE " + RawContacts.CONTACT_ID + "=OLD." + RawContacts.CONTACT_ID 973 + " )=1;" 974 + " END"); 975 976 977 db.execSQL("DROP TRIGGER IF EXISTS contacts_times_contacted;"); 978 db.execSQL("CREATE TRIGGER contacts_times_contacted UPDATE OF " + 979 Contacts.LAST_TIME_CONTACTED + " ON " + Tables.CONTACTS + " " + 980 "BEGIN " + 981 "UPDATE " + Tables.CONTACTS + " SET " 982 + Contacts.TIMES_CONTACTED + " = " + "" + 983 "(new." + Contacts.TIMES_CONTACTED + " + 1)" 984 + " WHERE _id = new._id;" + 985 "END"); 986 987 /* 988 * Triggers that update {@link RawContacts#VERSION} when the contact is 989 * marked for deletion or any time a data row is inserted, updated or 990 * deleted. 991 */ 992 db.execSQL("DROP TRIGGER IF EXISTS " + Tables.RAW_CONTACTS + "_marked_deleted;"); 993 db.execSQL("CREATE TRIGGER " + Tables.RAW_CONTACTS + "_marked_deleted " 994 + " BEFORE UPDATE ON " + Tables.RAW_CONTACTS 995 + " BEGIN " 996 + " UPDATE " + Tables.RAW_CONTACTS 997 + " SET " 998 + RawContacts.VERSION + "=OLD." + RawContacts.VERSION + "+1 " 999 + " WHERE " + RawContacts._ID + "=OLD." + RawContacts._ID 1000 + " AND NEW." + RawContacts.DELETED + "!= OLD." + RawContacts.DELETED + ";" 1001 + " END"); 1002 1003 db.execSQL("DROP TRIGGER IF EXISTS " + Tables.DATA + "_updated;"); 1004 db.execSQL("CREATE TRIGGER " + Tables.DATA + "_updated BEFORE UPDATE ON " + Tables.DATA 1005 + " BEGIN " 1006 + " UPDATE " + Tables.DATA 1007 + " SET " + Data.DATA_VERSION + "=OLD." + Data.DATA_VERSION + "+1 " 1008 + " WHERE " + Data._ID + "=OLD." + Data._ID + ";" 1009 + " UPDATE " + Tables.RAW_CONTACTS 1010 + " SET " + RawContacts.VERSION + "=" + RawContacts.VERSION + "+1 " 1011 + " WHERE " + RawContacts._ID + "=OLD." + Data.RAW_CONTACT_ID + ";" 1012 + " END"); 1013 1014 db.execSQL("DROP TRIGGER IF EXISTS " + Tables.DATA + "_deleted;"); 1015 db.execSQL("CREATE TRIGGER " + Tables.DATA + "_deleted BEFORE DELETE ON " + Tables.DATA 1016 + " BEGIN " 1017 + " UPDATE " + Tables.RAW_CONTACTS 1018 + " SET " + RawContacts.VERSION + "=" + RawContacts.VERSION + "+1 " 1019 + " WHERE " + RawContacts._ID + "=OLD." + Data.RAW_CONTACT_ID + ";" 1020 + " DELETE FROM " + Tables.PHONE_LOOKUP 1021 + " WHERE " + PhoneLookupColumns.DATA_ID + "=OLD." + Data._ID + ";" 1022 + " DELETE FROM " + Tables.STATUS_UPDATES 1023 + " WHERE " + StatusUpdatesColumns.DATA_ID + "=OLD." + Data._ID + ";" 1024 + " DELETE FROM " + Tables.NAME_LOOKUP 1025 + " WHERE " + NameLookupColumns.DATA_ID + "=OLD." + Data._ID + ";" 1026 + " END"); 1027 1028 1029 db.execSQL("DROP TRIGGER IF EXISTS " + Tables.GROUPS + "_updated1;"); 1030 db.execSQL("CREATE TRIGGER " + Tables.GROUPS + "_updated1 " 1031 + " BEFORE UPDATE ON " + Tables.GROUPS 1032 + " BEGIN " 1033 + " UPDATE " + Tables.GROUPS 1034 + " SET " 1035 + Groups.VERSION + "=OLD." + Groups.VERSION + "+1" 1036 + " WHERE " + Groups._ID + "=OLD." + Groups._ID + ";" 1037 + " END"); 1038 } 1039 createContactsViews(SQLiteDatabase db)1040 private static void createContactsViews(SQLiteDatabase db) { 1041 db.execSQL("DROP VIEW IF EXISTS " + Views.CONTACTS_ALL + ";"); 1042 db.execSQL("DROP VIEW IF EXISTS " + Views.CONTACTS_RESTRICTED + ";"); 1043 db.execSQL("DROP VIEW IF EXISTS " + Views.DATA_ALL + ";"); 1044 db.execSQL("DROP VIEW IF EXISTS " + Views.DATA_RESTRICTED + ";"); 1045 db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_CONTACTS_ALL + ";"); 1046 db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_CONTACTS_RESTRICTED + ";"); 1047 1048 String dataColumns = 1049 Data.IS_PRIMARY + ", " 1050 + Data.IS_SUPER_PRIMARY + ", " 1051 + Data.DATA_VERSION + ", " 1052 + PackagesColumns.PACKAGE + " AS " + Data.RES_PACKAGE + "," 1053 + MimetypesColumns.MIMETYPE + " AS " + Data.MIMETYPE + ", " 1054 + Data.DATA1 + ", " 1055 + Data.DATA2 + ", " 1056 + Data.DATA3 + ", " 1057 + Data.DATA4 + ", " 1058 + Data.DATA5 + ", " 1059 + Data.DATA6 + ", " 1060 + Data.DATA7 + ", " 1061 + Data.DATA8 + ", " 1062 + Data.DATA9 + ", " 1063 + Data.DATA10 + ", " 1064 + Data.DATA11 + ", " 1065 + Data.DATA12 + ", " 1066 + Data.DATA13 + ", " 1067 + Data.DATA14 + ", " 1068 + Data.DATA15 + ", " 1069 + Data.SYNC1 + ", " 1070 + Data.SYNC2 + ", " 1071 + Data.SYNC3 + ", " 1072 + Data.SYNC4; 1073 1074 String syncColumns = 1075 RawContactsColumns.CONCRETE_ACCOUNT_NAME + " AS " + RawContacts.ACCOUNT_NAME + "," 1076 + RawContactsColumns.CONCRETE_ACCOUNT_TYPE + " AS " + RawContacts.ACCOUNT_TYPE + "," 1077 + RawContactsColumns.CONCRETE_SOURCE_ID + " AS " + RawContacts.SOURCE_ID + "," 1078 + RawContactsColumns.CONCRETE_VERSION + " AS " + RawContacts.VERSION + "," 1079 + RawContactsColumns.CONCRETE_DIRTY + " AS " + RawContacts.DIRTY + "," 1080 + RawContactsColumns.CONCRETE_SYNC1 + " AS " + RawContacts.SYNC1 + "," 1081 + RawContactsColumns.CONCRETE_SYNC2 + " AS " + RawContacts.SYNC2 + "," 1082 + RawContactsColumns.CONCRETE_SYNC3 + " AS " + RawContacts.SYNC3 + "," 1083 + RawContactsColumns.CONCRETE_SYNC4 + " AS " + RawContacts.SYNC4; 1084 1085 String contactOptionColumns = 1086 ContactsColumns.CONCRETE_CUSTOM_RINGTONE 1087 + " AS " + RawContacts.CUSTOM_RINGTONE + "," 1088 + ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL 1089 + " AS " + RawContacts.SEND_TO_VOICEMAIL + "," 1090 + ContactsColumns.CONCRETE_LAST_TIME_CONTACTED 1091 + " AS " + RawContacts.LAST_TIME_CONTACTED + "," 1092 + ContactsColumns.CONCRETE_TIMES_CONTACTED 1093 + " AS " + RawContacts.TIMES_CONTACTED + "," 1094 + ContactsColumns.CONCRETE_STARRED 1095 + " AS " + RawContacts.STARRED; 1096 1097 String dataSelect = "SELECT " 1098 + DataColumns.CONCRETE_ID + " AS " + Data._ID + "," 1099 + Data.RAW_CONTACT_ID + ", " 1100 + RawContacts.CONTACT_ID + ", " 1101 + syncColumns + ", " 1102 + dataColumns + ", " 1103 + contactOptionColumns + ", " 1104 + ContactsColumns.CONCRETE_DISPLAY_NAME + " AS " + Contacts.DISPLAY_NAME + ", " 1105 + Contacts.LOOKUP_KEY + ", " 1106 + Contacts.PHOTO_ID + ", " 1107 + Contacts.IN_VISIBLE_GROUP + ", " 1108 + ContactsColumns.LAST_STATUS_UPDATE_ID + ", " 1109 + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID 1110 + " FROM " + Tables.DATA 1111 + " JOIN " + Tables.MIMETYPES + " ON (" 1112 + DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")" 1113 + " JOIN " + Tables.RAW_CONTACTS + " ON (" 1114 + DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")" 1115 + " JOIN " + Tables.CONTACTS + " ON (" 1116 + RawContacts.CONTACT_ID + "=" + Tables.CONTACTS + "." + Contacts._ID + ")" 1117 + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON (" 1118 + DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")" 1119 + " LEFT OUTER JOIN " + Tables.GROUPS + " ON (" 1120 + MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE 1121 + "' AND " + GroupsColumns.CONCRETE_ID + "=" 1122 + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")"; 1123 1124 db.execSQL("CREATE VIEW " + Views.DATA_ALL + " AS " + dataSelect); 1125 db.execSQL("CREATE VIEW " + Views.DATA_RESTRICTED + " AS " + dataSelect + " WHERE " 1126 + RawContacts.IS_RESTRICTED + "=0"); 1127 1128 String rawContactOptionColumns = 1129 RawContacts.CUSTOM_RINGTONE + "," 1130 + RawContacts.SEND_TO_VOICEMAIL + "," 1131 + RawContacts.LAST_TIME_CONTACTED + "," 1132 + RawContacts.TIMES_CONTACTED + "," 1133 + RawContacts.STARRED; 1134 1135 String rawContactsSelect = "SELECT " 1136 + RawContactsColumns.CONCRETE_ID + " AS " + RawContacts._ID + "," 1137 + RawContacts.CONTACT_ID + ", " 1138 + RawContacts.AGGREGATION_MODE + ", " 1139 + RawContacts.DELETED + ", " 1140 + rawContactOptionColumns + ", " 1141 + syncColumns 1142 + " FROM " + Tables.RAW_CONTACTS; 1143 1144 db.execSQL("CREATE VIEW " + Views.RAW_CONTACTS_ALL + " AS " + rawContactsSelect); 1145 db.execSQL("CREATE VIEW " + Views.RAW_CONTACTS_RESTRICTED + " AS " + rawContactsSelect 1146 + " WHERE " + RawContacts.IS_RESTRICTED + "=0"); 1147 1148 String contactsColumns = 1149 ContactsColumns.CONCRETE_CUSTOM_RINGTONE 1150 + " AS " + Contacts.CUSTOM_RINGTONE + ", " 1151 + ContactsColumns.CONCRETE_DISPLAY_NAME 1152 + " AS " + Contacts.DISPLAY_NAME + ", " 1153 + Contacts.IN_VISIBLE_GROUP + ", " 1154 + Contacts.HAS_PHONE_NUMBER + ", " 1155 + Contacts.LOOKUP_KEY + ", " 1156 + Contacts.PHOTO_ID + ", " 1157 + Contacts.IN_VISIBLE_GROUP + ", " 1158 + ContactsColumns.CONCRETE_LAST_TIME_CONTACTED 1159 + " AS " + Contacts.LAST_TIME_CONTACTED + ", " 1160 + ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL 1161 + " AS " + Contacts.SEND_TO_VOICEMAIL + ", " 1162 + ContactsColumns.CONCRETE_STARRED 1163 + " AS " + Contacts.STARRED + ", " 1164 + ContactsColumns.CONCRETE_TIMES_CONTACTED 1165 + " AS " + Contacts.TIMES_CONTACTED + ", " 1166 + ContactsColumns.LAST_STATUS_UPDATE_ID; 1167 1168 String contactsSelect = "SELECT " 1169 + ContactsColumns.CONCRETE_ID + " AS " + Contacts._ID + "," 1170 + contactsColumns 1171 + " FROM " + Tables.CONTACTS; 1172 1173 String restrictedContactsSelect = "SELECT " 1174 + ContactsColumns.CONCRETE_ID + " AS " + Contacts._ID + "," 1175 + contactsColumns 1176 + " FROM " + Tables.CONTACTS 1177 + " WHERE " + ContactsColumns.SINGLE_IS_RESTRICTED + "=0"; 1178 1179 db.execSQL("CREATE VIEW " + Views.CONTACTS_ALL + " AS " + contactsSelect); 1180 db.execSQL("CREATE VIEW " + Views.CONTACTS_RESTRICTED + " AS " + restrictedContactsSelect); 1181 } 1182 createGroupsView(SQLiteDatabase db)1183 private static void createGroupsView(SQLiteDatabase db) { 1184 db.execSQL("DROP VIEW IF EXISTS " + Views.GROUPS_ALL + ";"); 1185 String groupsColumns = 1186 Groups.ACCOUNT_NAME + "," 1187 + Groups.ACCOUNT_TYPE + "," 1188 + Groups.SOURCE_ID + "," 1189 + Groups.VERSION + "," 1190 + Groups.DIRTY + "," 1191 + Groups.TITLE + "," 1192 + Groups.TITLE_RES + "," 1193 + Groups.NOTES + "," 1194 + Groups.SYSTEM_ID + "," 1195 + Groups.DELETED + "," 1196 + Groups.GROUP_VISIBLE + "," 1197 + Groups.SHOULD_SYNC + "," 1198 + Groups.SYNC1 + "," 1199 + Groups.SYNC2 + "," 1200 + Groups.SYNC3 + "," 1201 + Groups.SYNC4 + "," 1202 + PackagesColumns.PACKAGE + " AS " + Groups.RES_PACKAGE; 1203 1204 String groupsSelect = "SELECT " 1205 + GroupsColumns.CONCRETE_ID + " AS " + Groups._ID + "," 1206 + groupsColumns 1207 + " FROM " + Tables.GROUPS_JOIN_PACKAGES; 1208 1209 db.execSQL("CREATE VIEW " + Views.GROUPS_ALL + " AS " + groupsSelect); 1210 } 1211 createContactEntitiesView(SQLiteDatabase db)1212 private static void createContactEntitiesView(SQLiteDatabase db) { 1213 db.execSQL("DROP VIEW IF EXISTS " + Tables.CONTACT_ENTITIES + ";"); 1214 db.execSQL("DROP VIEW IF EXISTS " + Tables.CONTACT_ENTITIES_RESTRICTED + ";"); 1215 1216 String contactEntitiesSelect = "SELECT " 1217 + RawContactsColumns.CONCRETE_ACCOUNT_NAME + " AS " + RawContacts.ACCOUNT_NAME + "," 1218 + RawContactsColumns.CONCRETE_ACCOUNT_TYPE + " AS " + RawContacts.ACCOUNT_TYPE + "," 1219 + RawContactsColumns.CONCRETE_SOURCE_ID + " AS " + RawContacts.SOURCE_ID + "," 1220 + RawContactsColumns.CONCRETE_VERSION + " AS " + RawContacts.VERSION + "," 1221 + RawContactsColumns.CONCRETE_DIRTY + " AS " + RawContacts.DIRTY + "," 1222 + RawContactsColumns.CONCRETE_DELETED + " AS " + RawContacts.DELETED + "," 1223 + PackagesColumns.PACKAGE + " AS " + Data.RES_PACKAGE + "," 1224 + RawContacts.CONTACT_ID + ", " 1225 + RawContactsColumns.CONCRETE_SYNC1 + " AS " + RawContacts.SYNC1 + ", " 1226 + RawContactsColumns.CONCRETE_SYNC2 + " AS " + RawContacts.SYNC2 + ", " 1227 + RawContactsColumns.CONCRETE_SYNC3 + " AS " + RawContacts.SYNC3 + ", " 1228 + RawContactsColumns.CONCRETE_SYNC4 + " AS " + RawContacts.SYNC4 + ", " 1229 + Data.MIMETYPE + ", " 1230 + Data.DATA1 + ", " 1231 + Data.DATA2 + ", " 1232 + Data.DATA3 + ", " 1233 + Data.DATA4 + ", " 1234 + Data.DATA5 + ", " 1235 + Data.DATA6 + ", " 1236 + Data.DATA7 + ", " 1237 + Data.DATA8 + ", " 1238 + Data.DATA9 + ", " 1239 + Data.DATA10 + ", " 1240 + Data.DATA11 + ", " 1241 + Data.DATA12 + ", " 1242 + Data.DATA13 + ", " 1243 + Data.DATA14 + ", " 1244 + Data.DATA15 + ", " 1245 + Data.SYNC1 + ", " 1246 + Data.SYNC2 + ", " 1247 + Data.SYNC3 + ", " 1248 + Data.SYNC4 + ", " 1249 + RawContactsColumns.CONCRETE_ID + " AS " + RawContacts._ID + ", " 1250 + Data.IS_PRIMARY + ", " 1251 + Data.IS_SUPER_PRIMARY + ", " 1252 + Data.DATA_VERSION + ", " 1253 + DataColumns.CONCRETE_ID + " AS " + RawContacts.Entity.DATA_ID + "," 1254 + RawContactsColumns.CONCRETE_STARRED + " AS " + RawContacts.STARRED + "," 1255 + RawContactsColumns.CONCRETE_IS_RESTRICTED + " AS " 1256 + RawContacts.IS_RESTRICTED + "," 1257 + Tables.GROUPS + "." + Groups.SOURCE_ID + " AS " + GroupMembership.GROUP_SOURCE_ID 1258 + " FROM " + Tables.RAW_CONTACTS 1259 + " LEFT OUTER JOIN " + Tables.DATA + " ON (" 1260 + DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")" 1261 + " LEFT OUTER JOIN " + Tables.PACKAGES + " ON (" 1262 + DataColumns.CONCRETE_PACKAGE_ID + "=" + PackagesColumns.CONCRETE_ID + ")" 1263 + " LEFT OUTER JOIN " + Tables.MIMETYPES + " ON (" 1264 + DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")" 1265 + " LEFT OUTER JOIN " + Tables.GROUPS + " ON (" 1266 + MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE 1267 + "' AND " + GroupsColumns.CONCRETE_ID + "=" 1268 + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")"; 1269 1270 db.execSQL("CREATE VIEW " + Tables.CONTACT_ENTITIES + " AS " 1271 + contactEntitiesSelect); 1272 db.execSQL("CREATE VIEW " + Tables.CONTACT_ENTITIES_RESTRICTED + " AS " 1273 + contactEntitiesSelect + " WHERE " + RawContacts.IS_RESTRICTED + "=0"); 1274 } 1275 1276 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)1277 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 1278 if (oldVersion < 99) { 1279 Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion 1280 + ", data will be lost!"); 1281 1282 db.execSQL("DROP TABLE IF EXISTS " + Tables.CONTACTS + ";"); 1283 db.execSQL("DROP TABLE IF EXISTS " + Tables.RAW_CONTACTS + ";"); 1284 db.execSQL("DROP TABLE IF EXISTS " + Tables.PACKAGES + ";"); 1285 db.execSQL("DROP TABLE IF EXISTS " + Tables.MIMETYPES + ";"); 1286 db.execSQL("DROP TABLE IF EXISTS " + Tables.DATA + ";"); 1287 db.execSQL("DROP TABLE IF EXISTS " + Tables.PHONE_LOOKUP + ";"); 1288 db.execSQL("DROP TABLE IF EXISTS " + Tables.NAME_LOOKUP + ";"); 1289 db.execSQL("DROP TABLE IF EXISTS " + Tables.NICKNAME_LOOKUP + ";"); 1290 db.execSQL("DROP TABLE IF EXISTS " + Tables.GROUPS + ";"); 1291 db.execSQL("DROP TABLE IF EXISTS " + Tables.ACTIVITIES + ";"); 1292 db.execSQL("DROP TABLE IF EXISTS " + Tables.CALLS + ";"); 1293 db.execSQL("DROP TABLE IF EXISTS " + Tables.SETTINGS + ";"); 1294 db.execSQL("DROP TABLE IF EXISTS " + Tables.STATUS_UPDATES + ";"); 1295 1296 // TODO: we should not be dropping agg_exceptions and contact_options. In case that 1297 // table's schema changes, we should try to preserve the data, because it was entered 1298 // by the user and has never been synched to the server. 1299 db.execSQL("DROP TABLE IF EXISTS " + Tables.AGGREGATION_EXCEPTIONS + ";"); 1300 1301 onCreate(db); 1302 return; 1303 } 1304 1305 Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion); 1306 1307 if (oldVersion == 99) { 1308 createContactEntitiesView(db); 1309 oldVersion++; 1310 } 1311 1312 if (oldVersion == 100) { 1313 db.execSQL("CREATE INDEX IF NOT EXISTS mimetypes_mimetype_index ON " 1314 + Tables.MIMETYPES + " (" 1315 + MimetypesColumns.MIMETYPE + "," 1316 + MimetypesColumns._ID + ");"); 1317 updateIndexStats(db, Tables.MIMETYPES, 1318 "mimetypes_mimetype_index", "50 1 1"); 1319 1320 createContactsViews(db); 1321 oldVersion++; 1322 } 1323 1324 if (oldVersion == 101) { 1325 createContactsTriggers(db); 1326 oldVersion++; 1327 } 1328 1329 if (oldVersion == 102) { 1330 LegacyApiSupport.createViews(db); 1331 oldVersion++; 1332 } 1333 1334 if (oldVersion == 103) { 1335 createContactEntitiesView(db); 1336 oldVersion++; 1337 } 1338 1339 if (oldVersion == 104) { 1340 LegacyApiSupport.createViews(db); 1341 LegacyApiSupport.createSettingsTable(db); 1342 oldVersion++; 1343 } 1344 1345 if (oldVersion != newVersion) { 1346 throw new IllegalStateException( 1347 "error upgrading the database to version " + newVersion); 1348 } 1349 } 1350 1351 /** 1352 * Adds index stats into the SQLite database to force it to always use the lookup indexes. 1353 */ updateSqliteStats(SQLiteDatabase db)1354 private void updateSqliteStats(SQLiteDatabase db) { 1355 1356 // Specific stats strings are based on an actual large database after running ANALYZE 1357 try { 1358 updateIndexStats(db, Tables.CONTACTS, 1359 "contacts_restricted_index", "10000 9000"); 1360 updateIndexStats(db, Tables.CONTACTS, 1361 "contacts_has_phone_index", "10000 500"); 1362 updateIndexStats(db, Tables.CONTACTS, 1363 "contacts_visible_index", "10000 500 1"); 1364 1365 updateIndexStats(db, Tables.RAW_CONTACTS, 1366 "raw_contacts_source_id_index", "10000 1 1 1"); 1367 updateIndexStats(db, Tables.RAW_CONTACTS, 1368 "raw_contacts_contact_id_index", "10000 2"); 1369 1370 updateIndexStats(db, Tables.NAME_LOOKUP, 1371 "name_lookup_raw_contact_id_index", "10000 3"); 1372 updateIndexStats(db, Tables.NAME_LOOKUP, 1373 "name_lookup_index", "10000 3 2 2"); 1374 updateIndexStats(db, Tables.NAME_LOOKUP, 1375 "sqlite_autoindex_name_lookup_1", "10000 3 2 1"); 1376 1377 updateIndexStats(db, Tables.PHONE_LOOKUP, 1378 "phone_lookup_index", "10000 2 2 1"); 1379 1380 updateIndexStats(db, Tables.DATA, 1381 "data_mimetype_data1_index", "60000 5000 2"); 1382 updateIndexStats(db, Tables.DATA, 1383 "data_raw_contact_id", "60000 10"); 1384 1385 updateIndexStats(db, Tables.GROUPS, 1386 "groups_source_id_index", "50 1 1 1"); 1387 1388 updateIndexStats(db, Tables.NICKNAME_LOOKUP, 1389 "sqlite_autoindex_name_lookup_1", "500 2 1"); 1390 1391 } catch (SQLException e) { 1392 Log.e(TAG, "Could not update index stats", e); 1393 } 1394 } 1395 1396 /** 1397 * Stores statistics for a given index. 1398 * 1399 * @param stats has the following structure: the first index is the expected size of 1400 * the table. The following integer(s) are the expected number of records selected with the 1401 * index. There should be one integer per indexed column. 1402 */ updateIndexStats(SQLiteDatabase db, String table, String index, String stats)1403 private void updateIndexStats(SQLiteDatabase db, String table, String index, String stats) { 1404 db.execSQL("DELETE FROM sqlite_stat1 WHERE tbl='" + table + "' AND idx='" + index + "';"); 1405 db.execSQL("INSERT INTO sqlite_stat1 (tbl,idx,stat)" 1406 + " VALUES ('" + table + "','" + index + "','" + stats + "');"); 1407 } 1408 1409 @Override getWritableDatabase()1410 public synchronized SQLiteDatabase getWritableDatabase() { 1411 SQLiteDatabase db = super.getWritableDatabase(); 1412 if (mReopenDatabase) { 1413 mReopenDatabase = false; 1414 close(); 1415 db = super.getWritableDatabase(); 1416 } 1417 return db; 1418 } 1419 1420 /** 1421 * Wipes all data except mime type and package lookup tables. 1422 */ wipeData()1423 public void wipeData() { 1424 SQLiteDatabase db = getWritableDatabase(); 1425 1426 db.execSQL("DELETE FROM " + Tables.CONTACTS + ";"); 1427 db.execSQL("DELETE FROM " + Tables.RAW_CONTACTS + ";"); 1428 db.execSQL("DELETE FROM " + Tables.DATA + ";"); 1429 db.execSQL("DELETE FROM " + Tables.PHONE_LOOKUP + ";"); 1430 db.execSQL("DELETE FROM " + Tables.NAME_LOOKUP + ";"); 1431 db.execSQL("DELETE FROM " + Tables.GROUPS + ";"); 1432 db.execSQL("DELETE FROM " + Tables.AGGREGATION_EXCEPTIONS + ";"); 1433 db.execSQL("DELETE FROM " + Tables.SETTINGS + ";"); 1434 db.execSQL("DELETE FROM " + Tables.ACTIVITIES + ";"); 1435 db.execSQL("DELETE FROM " + Tables.CALLS + ";"); 1436 1437 // Note: we are not removing reference data from Tables.NICKNAME_LOOKUP 1438 1439 db.execSQL("VACUUM;"); 1440 } 1441 1442 /** 1443 * Return the {@link ApplicationInfo#uid} for the given package name. 1444 */ getUidForPackageName(PackageManager pm, String packageName)1445 public static int getUidForPackageName(PackageManager pm, String packageName) { 1446 try { 1447 ApplicationInfo clientInfo = pm.getApplicationInfo(packageName, 0 /* no flags */); 1448 return clientInfo.uid; 1449 } catch (NameNotFoundException e) { 1450 throw new RuntimeException(e); 1451 } 1452 } 1453 1454 /** 1455 * Perform an internal string-to-integer lookup using the compiled 1456 * {@link SQLiteStatement} provided, using the in-memory cache to speed up 1457 * lookups. If a mapping isn't found in cache or database, it will be 1458 * created. All new, uncached answers are added to the cache automatically. 1459 * 1460 * @param query Compiled statement used to query for the mapping. 1461 * @param insert Compiled statement used to insert a new mapping when no 1462 * existing one is found in cache or from query. 1463 * @param value Value to find mapping for. 1464 * @param cache In-memory cache of previous answers. 1465 * @return An unique integer mapping for the given value. 1466 */ getCachedId(SQLiteStatement query, SQLiteStatement insert, String value, HashMap<String, Long> cache)1467 private synchronized long getCachedId(SQLiteStatement query, SQLiteStatement insert, 1468 String value, HashMap<String, Long> cache) { 1469 // Try an in-memory cache lookup 1470 if (cache.containsKey(value)) { 1471 return cache.get(value); 1472 } 1473 1474 long id = -1; 1475 try { 1476 // Try searching database for mapping 1477 DatabaseUtils.bindObjectToProgram(query, 1, value); 1478 id = query.simpleQueryForLong(); 1479 } catch (SQLiteDoneException e) { 1480 // Nothing found, so try inserting new mapping 1481 DatabaseUtils.bindObjectToProgram(insert, 1, value); 1482 id = insert.executeInsert(); 1483 } 1484 1485 if (id != -1) { 1486 // Cache and return the new answer 1487 cache.put(value, id); 1488 return id; 1489 } else { 1490 // Otherwise throw if no mapping found or created 1491 throw new IllegalStateException("Couldn't find or create internal " 1492 + "lookup table entry for value " + value); 1493 } 1494 } 1495 1496 /** 1497 * Convert a package name into an integer, using {@link Tables#PACKAGES} for 1498 * lookups and possible allocation of new IDs as needed. 1499 */ getPackageId(String packageName)1500 public long getPackageId(String packageName) { 1501 // Make sure compiled statements are ready by opening database 1502 getReadableDatabase(); 1503 return getCachedId(mPackageQuery, mPackageInsert, packageName, mPackageCache); 1504 } 1505 1506 /** 1507 * Convert a mimetype into an integer, using {@link Tables#MIMETYPES} for 1508 * lookups and possible allocation of new IDs as needed. 1509 */ getMimeTypeId(String mimetype)1510 public long getMimeTypeId(String mimetype) { 1511 // Make sure compiled statements are ready by opening database 1512 getReadableDatabase(); 1513 return getCachedId(mMimetypeQuery, mMimetypeInsert, mimetype, mMimetypeCache); 1514 } 1515 1516 /** 1517 * Find the mimetype for the given {@link Data#_ID}. 1518 */ getDataMimeType(long dataId)1519 public String getDataMimeType(long dataId) { 1520 // Make sure compiled statements are ready by opening database 1521 getReadableDatabase(); 1522 try { 1523 // Try database query to find mimetype 1524 DatabaseUtils.bindObjectToProgram(mDataMimetypeQuery, 1, dataId); 1525 String mimetype = mDataMimetypeQuery.simpleQueryForString(); 1526 return mimetype; 1527 } catch (SQLiteDoneException e) { 1528 // No valid mapping found, so return null 1529 return null; 1530 } 1531 } 1532 1533 /** 1534 * Find the mime-type for the given {@link Activities#_ID}. 1535 */ getActivityMimeType(long activityId)1536 public String getActivityMimeType(long activityId) { 1537 // Make sure compiled statements are ready by opening database 1538 getReadableDatabase(); 1539 try { 1540 // Try database query to find mimetype 1541 DatabaseUtils.bindObjectToProgram(mActivitiesMimetypeQuery, 1, activityId); 1542 String mimetype = mActivitiesMimetypeQuery.simpleQueryForString(); 1543 return mimetype; 1544 } catch (SQLiteDoneException e) { 1545 // No valid mapping found, so return null 1546 return null; 1547 } 1548 } 1549 1550 /** 1551 * Update {@link Contacts#IN_VISIBLE_GROUP} for all contacts. 1552 */ updateAllVisible()1553 public void updateAllVisible() { 1554 final long groupMembershipMimetypeId = getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE); 1555 mVisibleUpdate.bindLong(1, groupMembershipMimetypeId); 1556 mVisibleUpdate.execute(); 1557 } 1558 1559 /** 1560 * Update {@link Contacts#IN_VISIBLE_GROUP} for a specific contact. 1561 */ updateContactVisible(long aggId)1562 public void updateContactVisible(long aggId) { 1563 final long groupMembershipMimetypeId = getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE); 1564 mVisibleSpecificUpdate.bindLong(1, groupMembershipMimetypeId); 1565 mVisibleSpecificUpdate.bindLong(2, aggId); 1566 mVisibleSpecificUpdate.execute(); 1567 } 1568 1569 /** 1570 * Returns contact ID for the given contact or zero if it is NULL. 1571 */ getContactId(long rawContactId)1572 public long getContactId(long rawContactId) { 1573 getReadableDatabase(); 1574 try { 1575 DatabaseUtils.bindObjectToProgram(mContactIdQuery, 1, rawContactId); 1576 return mContactIdQuery.simpleQueryForLong(); 1577 } catch (SQLiteDoneException e) { 1578 // No valid mapping found, so return 0 1579 return 0; 1580 } 1581 } 1582 getAggregationMode(long rawContactId)1583 public int getAggregationMode(long rawContactId) { 1584 getReadableDatabase(); 1585 try { 1586 DatabaseUtils.bindObjectToProgram(mAggregationModeQuery, 1, rawContactId); 1587 return (int)mAggregationModeQuery.simpleQueryForLong(); 1588 } catch (SQLiteDoneException e) { 1589 // No valid row found, so return "disabled" 1590 return RawContacts.AGGREGATION_MODE_DISABLED; 1591 } 1592 } 1593 buildPhoneLookupAndRawContactQuery(SQLiteQueryBuilder qb, String number)1594 public void buildPhoneLookupAndRawContactQuery(SQLiteQueryBuilder qb, String number) { 1595 String normalizedNumber = PhoneNumberUtils.toCallerIDMinMatch(number); 1596 qb.setTables(Tables.DATA_JOIN_RAW_CONTACTS + 1597 " JOIN " + Tables.PHONE_LOOKUP 1598 + " ON(" + DataColumns.CONCRETE_ID + "=" + PhoneLookupColumns.DATA_ID + ")"); 1599 1600 StringBuilder sb = new StringBuilder(); 1601 sb.append(PhoneLookupColumns.NORMALIZED_NUMBER + " GLOB '"); 1602 sb.append(normalizedNumber); 1603 sb.append("*' AND PHONE_NUMBERS_EQUAL(data." + Phone.NUMBER + ", "); 1604 DatabaseUtils.appendEscapedSQLString(sb, number); 1605 sb.append(mUseStrictPhoneNumberComparation ? ", 1)" : ", 0)"); 1606 1607 qb.appendWhere(sb.toString()); 1608 } 1609 buildPhoneLookupAndContactQuery(SQLiteQueryBuilder qb, String number)1610 public void buildPhoneLookupAndContactQuery(SQLiteQueryBuilder qb, String number) { 1611 String normalizedNumber = PhoneNumberUtils.toCallerIDMinMatch(number); 1612 StringBuilder sb = new StringBuilder(); 1613 appendPhoneLookupTables(sb, normalizedNumber, true); 1614 qb.setTables(sb.toString()); 1615 1616 sb = new StringBuilder(); 1617 appendPhoneLookupSelection(sb, number); 1618 qb.appendWhere(sb.toString()); 1619 } 1620 buildPhoneLookupAsNestedQuery(String number)1621 public String buildPhoneLookupAsNestedQuery(String number) { 1622 StringBuilder sb = new StringBuilder(); 1623 final String normalizedNumber = PhoneNumberUtils.toCallerIDMinMatch(number); 1624 sb.append("(SELECT DISTINCT raw_contact_id" + " FROM "); 1625 appendPhoneLookupTables(sb, normalizedNumber, false); 1626 sb.append(" WHERE "); 1627 appendPhoneLookupSelection(sb, number); 1628 sb.append(")"); 1629 return sb.toString(); 1630 } 1631 appendPhoneLookupTables(StringBuilder sb, final String normalizedNumber, boolean joinContacts)1632 private void appendPhoneLookupTables(StringBuilder sb, final String normalizedNumber, 1633 boolean joinContacts) { 1634 sb.append(Tables.RAW_CONTACTS); 1635 if (joinContacts) { 1636 sb.append(" JOIN " + getContactView() + " contacts" 1637 + " ON (contacts._id = raw_contacts.contact_id)"); 1638 } 1639 sb.append(", (SELECT data_id FROM phone_lookup " 1640 + "WHERE (phone_lookup.normalized_number GLOB '"); 1641 sb.append(normalizedNumber); 1642 sb.append("*')) AS lookup, " + Tables.DATA); 1643 } 1644 appendPhoneLookupSelection(StringBuilder sb, String number)1645 private void appendPhoneLookupSelection(StringBuilder sb, String number) { 1646 sb.append("lookup.data_id=data._id AND data.raw_contact_id=raw_contacts._id" 1647 + " AND PHONE_NUMBERS_EQUAL(data." + Phone.NUMBER + ", "); 1648 DatabaseUtils.appendEscapedSQLString(sb, number); 1649 sb.append(mUseStrictPhoneNumberComparation ? ", 1)" : ", 0)"); 1650 } 1651 1652 /** 1653 * Loads common nickname mappings into the database. 1654 */ loadNicknameLookupTable(SQLiteDatabase db)1655 private void loadNicknameLookupTable(SQLiteDatabase db) { 1656 String[] strings = mContext.getResources().getStringArray( 1657 com.android.internal.R.array.common_nicknames); 1658 if (strings == null || strings.length == 0) { 1659 return; 1660 } 1661 1662 SQLiteStatement nicknameLookupInsert = db.compileStatement("INSERT INTO " 1663 + Tables.NICKNAME_LOOKUP + "(" + NicknameLookupColumns.NAME + "," 1664 + NicknameLookupColumns.CLUSTER + ") VALUES (?,?)"); 1665 1666 for (int clusterId = 0; clusterId < strings.length; clusterId++) { 1667 String[] names = strings[clusterId].split(","); 1668 for (int j = 0; j < names.length; j++) { 1669 String name = NameNormalizer.normalize(names[j]); 1670 try { 1671 DatabaseUtils.bindObjectToProgram(nicknameLookupInsert, 1, name); 1672 DatabaseUtils.bindObjectToProgram(nicknameLookupInsert, 2, 1673 String.valueOf(clusterId)); 1674 nicknameLookupInsert.executeInsert(); 1675 } catch (SQLiteException e) { 1676 1677 // Print the exception and keep going - this is not a fatal error 1678 Log.e(TAG, "Cannot insert nickname: " + names[j], e); 1679 } 1680 } 1681 } 1682 } 1683 copyStringValue(ContentValues toValues, String toKey, ContentValues fromValues, String fromKey)1684 public static void copyStringValue(ContentValues toValues, String toKey, 1685 ContentValues fromValues, String fromKey) { 1686 if (fromValues.containsKey(fromKey)) { 1687 toValues.put(toKey, fromValues.getAsString(fromKey)); 1688 } 1689 } 1690 copyLongValue(ContentValues toValues, String toKey, ContentValues fromValues, String fromKey)1691 public static void copyLongValue(ContentValues toValues, String toKey, 1692 ContentValues fromValues, String fromKey) { 1693 if (fromValues.containsKey(fromKey)) { 1694 long longValue; 1695 Object value = fromValues.get(fromKey); 1696 if (value instanceof Boolean) { 1697 if ((Boolean)value) { 1698 longValue = 1; 1699 } else { 1700 longValue = 0; 1701 } 1702 } else if (value instanceof String) { 1703 longValue = Long.parseLong((String)value); 1704 } else { 1705 longValue = ((Number)value).longValue(); 1706 } 1707 toValues.put(toKey, longValue); 1708 } 1709 } 1710 getSyncState()1711 public SyncStateContentProviderHelper getSyncState() { 1712 return mSyncState; 1713 } 1714 1715 /** 1716 * Delete the aggregate contact if it has no constituent raw contacts other 1717 * than the supplied one. 1718 */ removeContactIfSingleton(long rawContactId)1719 public void removeContactIfSingleton(long rawContactId) { 1720 SQLiteDatabase db = getWritableDatabase(); 1721 1722 // Obtain contact ID from the supplied raw contact ID 1723 String contactIdFromRawContactId = "(SELECT " + RawContacts.CONTACT_ID + " FROM " 1724 + Tables.RAW_CONTACTS + " WHERE " + RawContacts._ID + "=" + rawContactId + ")"; 1725 1726 // Find other raw contacts in the same aggregate contact 1727 String otherRawContacts = "(SELECT contacts1." + RawContacts._ID + " FROM " 1728 + Tables.RAW_CONTACTS + " contacts1 JOIN " + Tables.RAW_CONTACTS + " contacts2 ON (" 1729 + "contacts1." + RawContacts.CONTACT_ID + "=contacts2." + RawContacts.CONTACT_ID 1730 + ") WHERE contacts1." + RawContacts._ID + "!=" + rawContactId + "" 1731 + " AND contacts2." + RawContacts._ID + "=" + rawContactId + ")"; 1732 1733 db.execSQL("DELETE FROM " + Tables.CONTACTS 1734 + " WHERE " + Contacts._ID + "=" + contactIdFromRawContactId 1735 + " AND NOT EXISTS " + otherRawContacts + ";"); 1736 } 1737 1738 /** 1739 * Check if {@link Binder#getCallingUid()} should be allowed access to 1740 * {@link RawContacts#IS_RESTRICTED} data. 1741 */ hasAccessToRestrictedData()1742 boolean hasAccessToRestrictedData() { 1743 final PackageManager pm = mContext.getPackageManager(); 1744 final String[] callerPackages = pm.getPackagesForUid(Binder.getCallingUid()); 1745 1746 // Has restricted access if caller matches any packages 1747 for (String callerPackage : callerPackages) { 1748 if (hasAccessToRestrictedData(callerPackage)) { 1749 return true; 1750 } 1751 } 1752 return false; 1753 } 1754 1755 /** 1756 * Check if requestingPackage should be allowed access to 1757 * {@link RawContacts#IS_RESTRICTED} data. 1758 */ hasAccessToRestrictedData(String requestingPackage)1759 boolean hasAccessToRestrictedData(String requestingPackage) { 1760 if (mUnrestrictedPackages != null) { 1761 for (String allowedPackage : mUnrestrictedPackages) { 1762 if (allowedPackage.equals(requestingPackage)) { 1763 return true; 1764 } 1765 } 1766 } 1767 return false; 1768 } 1769 getDataView()1770 public String getDataView() { 1771 return getDataView(false); 1772 } 1773 getDataView(boolean requireRestrictedView)1774 public String getDataView(boolean requireRestrictedView) { 1775 return (hasAccessToRestrictedData() && !requireRestrictedView) ? 1776 Views.DATA_ALL : Views.DATA_RESTRICTED; 1777 } 1778 getRawContactView()1779 public String getRawContactView() { 1780 return getRawContactView(false); 1781 } 1782 getRawContactView(boolean requireRestrictedView)1783 public String getRawContactView(boolean requireRestrictedView) { 1784 return (hasAccessToRestrictedData() && !requireRestrictedView) ? 1785 Views.RAW_CONTACTS_ALL : Views.RAW_CONTACTS_RESTRICTED; 1786 } 1787 getContactView()1788 public String getContactView() { 1789 return getContactView(false); 1790 } 1791 getContactView(boolean requireRestrictedView)1792 public String getContactView(boolean requireRestrictedView) { 1793 return (hasAccessToRestrictedData() && !requireRestrictedView) ? 1794 Views.CONTACTS_ALL : Views.CONTACTS_RESTRICTED; 1795 } 1796 getGroupView()1797 public String getGroupView() { 1798 return Views.GROUPS_ALL; 1799 } 1800 getContactEntitiesView()1801 public String getContactEntitiesView() { 1802 return getContactEntitiesView(false); 1803 } 1804 getContactEntitiesView(boolean requireRestrictedView)1805 public String getContactEntitiesView(boolean requireRestrictedView) { 1806 return (hasAccessToRestrictedData() && !requireRestrictedView) ? 1807 Tables.CONTACT_ENTITIES : Tables.CONTACT_ENTITIES_RESTRICTED; 1808 } 1809 1810 /** 1811 * Test if any of the columns appear in the given projection. 1812 */ isInProjection(String[] projection, String... columns)1813 public boolean isInProjection(String[] projection, String... columns) { 1814 if (projection == null) { 1815 return true; 1816 } 1817 1818 // Optimized for a single-column test 1819 if (columns.length == 1) { 1820 String column = columns[0]; 1821 for (String test : projection) { 1822 if (column.equals(test)) { 1823 return true; 1824 } 1825 } 1826 } else { 1827 for (String test : projection) { 1828 for (String column : columns) { 1829 if (column.equals(test)) { 1830 return true; 1831 } 1832 } 1833 } 1834 } 1835 return false; 1836 } 1837 } 1838