1 /* 2 * Copyright (C) 2020 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.ims.rcs.uce.eab; 18 19 import static android.content.ContentResolver.NOTIFY_DELETE; 20 import static android.content.ContentResolver.NOTIFY_INSERT; 21 import static android.content.ContentResolver.NOTIFY_UPDATE; 22 23 import android.content.ContentProvider; 24 import android.content.ContentValues; 25 import android.content.Context; 26 import android.content.UriMatcher; 27 import android.database.Cursor; 28 import android.database.sqlite.SQLiteDatabase; 29 import android.database.sqlite.SQLiteOpenHelper; 30 import android.database.sqlite.SQLiteQueryBuilder; 31 import android.net.Uri; 32 import android.provider.BaseColumns; 33 import android.text.TextUtils; 34 import android.util.Log; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 38 import java.util.ArrayList; 39 import java.util.List; 40 41 /** 42 * This class provides the ability to query the enhanced address book databases(A.K.A. EAB) based on 43 * both SIP options and UCE presence server data. 44 * 45 * <p> 46 * There are 4 tables in EAB DB: 47 * <ul> 48 * <li><em>Contact:</em> It stores the name and phone number of the contact. 49 * 50 * <li><em>Common:</em> It's a general table for storing the query results and the mechanisms of 51 * querying UCE capabilities. It should be 1:1 mapped to the contact table and has a foreign 52 * key(eab_contact_id) that refers to the id of contact table. If the value of mechanism is 53 * 1 ({@link android.telephony.ims.RcsContactUceCapability#CAPABILITY_MECHANISM_PRESENCE}), the 54 * capability information should be stored in presence table, if the value of mechanism is 55 * 2({@link android.telephony.ims.RcsContactUceCapability#CAPABILITY_MECHANISM_OPTIONS}), it 56 * should be stored in options table. 57 * 58 * <li><em>Presence:</em> It stores the information 59 * ({@link android.telephony.ims.RcsContactUceCapability}) that queried through presence server. 60 * It should be *:1 mapped to the common table and has a foreign key(eab_common_id) that refers 61 * to the id of common table. 62 * 63 * <li><em>Options:</em> It stores the information 64 * ({@link android.telephony.ims.RcsContactUceCapability}) that queried through SIP OPTIONS. It 65 * should be *:1 mapped to the common table and it has a foreign key(eab_common_id) that refers 66 * to the id of common table. 67 * </ul> 68 * </p> 69 */ 70 public class EabProvider extends ContentProvider { 71 // The public URI for operating Eab DB. They support query, insert, delete and update. 72 public static final Uri CONTACT_URI = Uri.parse("content://eab/contact"); 73 public static final Uri COMMON_URI = Uri.parse("content://eab/common"); 74 public static final Uri PRESENCE_URI = Uri.parse("content://eab/presence"); 75 public static final Uri OPTIONS_URI = Uri.parse("content://eab/options"); 76 77 // The public URI for querying EAB DB. Only support query. 78 public static final Uri ALL_DATA_URI = Uri.parse("content://eab/all"); 79 80 @VisibleForTesting 81 public static final String AUTHORITY = "eab"; 82 83 private static final String TAG = "EabProvider"; 84 private static final int DATABASE_VERSION = 3; 85 86 public static final String EAB_CONTACT_TABLE_NAME = "eab_contact"; 87 public static final String EAB_COMMON_TABLE_NAME = "eab_common"; 88 public static final String EAB_PRESENCE_TUPLE_TABLE_NAME = "eab_presence"; 89 public static final String EAB_OPTIONS_TABLE_NAME = "eab_options"; 90 91 private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); 92 private static final int URL_CONTACT = 1; 93 private static final int URL_COMMON = 2; 94 private static final int URL_PRESENCE = 3; 95 private static final int URL_OPTIONS = 4; 96 private static final int URL_ALL = 5; 97 private static final int URL_ALL_WITH_SUB_ID_AND_PHONE_NUMBER = 6; 98 99 static { URI_MATCHER.addURI(AUTHORITY, R, URL_CONTACT)100 URI_MATCHER.addURI(AUTHORITY, "contact", URL_CONTACT); URI_MATCHER.addURI(AUTHORITY, R, URL_COMMON)101 URI_MATCHER.addURI(AUTHORITY, "common", URL_COMMON); URI_MATCHER.addURI(AUTHORITY, R, URL_PRESENCE)102 URI_MATCHER.addURI(AUTHORITY, "presence", URL_PRESENCE); URI_MATCHER.addURI(AUTHORITY, R, URL_OPTIONS)103 URI_MATCHER.addURI(AUTHORITY, "options", URL_OPTIONS); URI_MATCHER.addURI(AUTHORITY, R, URL_ALL)104 URI_MATCHER.addURI(AUTHORITY, "all", URL_ALL); URI_MATCHER.addURI(AUTHORITY, R, URL_ALL_WITH_SUB_ID_AND_PHONE_NUMBER)105 URI_MATCHER.addURI(AUTHORITY, "all/#/*", URL_ALL_WITH_SUB_ID_AND_PHONE_NUMBER); 106 } 107 108 private static final String QUERY_CONTACT_TABLE = 109 " SELECT * FROM " + EAB_CONTACT_TABLE_NAME; 110 111 private static final String JOIN_ALL_TABLES = 112 // join common table 113 " INNER JOIN " + EAB_COMMON_TABLE_NAME 114 + " ON " + EAB_CONTACT_TABLE_NAME + "." + ContactColumns._ID 115 + "=" + EAB_COMMON_TABLE_NAME + "." + EabCommonColumns.EAB_CONTACT_ID 116 117 // join options table 118 + " LEFT JOIN " + EAB_OPTIONS_TABLE_NAME 119 + " ON " + EAB_COMMON_TABLE_NAME + "." + EabCommonColumns._ID 120 + "=" + EAB_OPTIONS_TABLE_NAME + "." + OptionsColumns.EAB_COMMON_ID 121 122 // join presence table 123 + " LEFT JOIN " + EAB_PRESENCE_TUPLE_TABLE_NAME 124 + " ON " + EAB_COMMON_TABLE_NAME + "." + EabCommonColumns._ID 125 + "=" + EAB_PRESENCE_TUPLE_TABLE_NAME + "." 126 + PresenceTupleColumns.EAB_COMMON_ID; 127 128 /** 129 * The contact table's columns. 130 */ 131 public static class ContactColumns implements BaseColumns { 132 133 /** 134 * The contact's phone number. It may come from contact provider or someone via 135 * {@link EabControllerImpl#saveCapabilities(List)} to save the capability but the phone 136 * number not in contact provider. 137 * 138 * <P>Type: TEXT</P> 139 */ 140 public static final String PHONE_NUMBER = "phone_number"; 141 142 /** 143 * The ID of contact that store in contact provider. It refer to the 144 * {@link android.provider.ContactsContract.Data#CONTACT_ID}. If the phone number not in 145 * contact provider, the value should be null. 146 * 147 * <P>Type: INTEGER</P> 148 */ 149 public static final String CONTACT_ID = "contact_id"; 150 151 /** 152 * The ID of contact that store in contact provider. It refer to the 153 * {@link android.provider.ContactsContract.Data#RAW_CONTACT_ID}. If the phone number not in 154 * contact provider, the value should be null. 155 * 156 * <P>Type: INTEGER</P> 157 */ 158 public static final String RAW_CONTACT_ID = "raw_contact_id"; 159 160 /** 161 * The ID of phone number that store in contact provider. It refer to the 162 * {@link android.provider.ContactsContract.Data#_ID}. If the phone number not in 163 * contact provider, the value should be null. 164 * 165 * <P>Type: INTEGER</P> 166 */ 167 public static final String DATA_ID = "data_id"; 168 } 169 170 /** 171 * The common table's columns. The eab_contact_id should refer to the id of contact table. 172 */ 173 public static class EabCommonColumns implements BaseColumns { 174 175 /** 176 * A reference to the {@link ContactColumns#_ID} that this data belongs to. 177 * <P>Type: INTEGER</P> 178 */ 179 public static final String EAB_CONTACT_ID = "eab_contact_id"; 180 181 /** 182 * The mechanism of querying UCE capability. Possible values are 183 * {@link android.telephony.ims.RcsContactUceCapability#CAPABILITY_MECHANISM_OPTIONS } 184 * and 185 * {@link android.telephony.ims.RcsContactUceCapability#CAPABILITY_MECHANISM_PRESENCE } 186 * <P>Type: INTEGER</P> 187 */ 188 public static final String MECHANISM = "mechanism"; 189 190 /** 191 * The result of querying UCE capability. Possible values are 192 * {@link android.telephony.ims.RcsContactUceCapability#REQUEST_RESULT_NOT_ONLINE } 193 * and 194 * {@link android.telephony.ims.RcsContactUceCapability#REQUEST_RESULT_NOT_FOUND } 195 * and 196 * {@link android.telephony.ims.RcsContactUceCapability#REQUEST_RESULT_FOUND } 197 * and 198 * {@link android.telephony.ims.RcsContactUceCapability#REQUEST_RESULT_UNKNOWN } 199 * <P>Type: INTEGER</P> 200 */ 201 public static final String REQUEST_RESULT = "request_result"; 202 203 /** 204 * The subscription id. 205 * <P>Type: INTEGER</P> 206 */ 207 public static final String SUBSCRIPTION_ID = "subscription_id"; 208 } 209 210 /** 211 * This is used to generate a instance of {@link android.telephony.ims.RcsContactPresenceTuple}. 212 * See that class for more information on each of these parameters. 213 */ 214 public static class PresenceTupleColumns implements BaseColumns { 215 216 /** 217 * A reference to the {@link ContactColumns#_ID} that this data belongs to. 218 * <P>Type: INTEGER</P> 219 */ 220 public static final String EAB_COMMON_ID = "eab_common_id"; 221 222 /** 223 * The basic status of service capabilities. Possible values are 224 * {@link android.telephony.ims.RcsContactPresenceTuple#TUPLE_BASIC_STATUS_OPEN} 225 * and 226 * {@link android.telephony.ims.RcsContactPresenceTuple#TUPLE_BASIC_STATUS_CLOSED} 227 * <P>Type: TEXT</P> 228 */ 229 public static final String BASIC_STATUS = "basic_status"; 230 231 /** 232 * The OMA Presence service-id associated with this capability. See the OMA Presence SIMPLE 233 * specification v1.1, section 10.5.1. 234 * <P>Type: TEXT</P> 235 */ 236 public static final String SERVICE_ID = "service_id"; 237 238 /** 239 * The contact uri of service capabilities. 240 * <P>Type: TEXT</P> 241 */ 242 public static final String CONTACT_URI = "contact_uri"; 243 244 /** 245 * The service version of service capabilities. 246 * <P>Type: TEXT</P> 247 */ 248 public static final String SERVICE_VERSION = "service_version"; 249 250 /** 251 * The description of service capabilities. 252 * <P>Type: TEXT</P> 253 */ 254 public static final String DESCRIPTION = "description"; 255 256 /** 257 * The supported duplex mode of service capabilities. Possible values are 258 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_FULL} 259 * and 260 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_HALF} 261 * and 262 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_RECEIVE_ONLY} 263 * and 264 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_SEND_ONLY} 265 * <P>Type: TEXT</P> 266 */ 267 public static final String DUPLEX_MODE = "duplex_mode"; 268 269 /** 270 * The unsupported duplex mode of service capabilities. Possible values are 271 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_FULL} 272 * and 273 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_HALF} 274 * and 275 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_RECEIVE_ONLY} 276 * and 277 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_SEND_ONLY} 278 * <P>Type: TEXT</P> 279 */ 280 public static final String UNSUPPORTED_DUPLEX_MODE = "unsupported_duplex_mode"; 281 282 /** 283 * The presence request timestamp. Represents seconds of UTC time since Unix epoch 284 * 1970-01-01 00:00:00. 285 * <P>Type: LONG</P> 286 */ 287 public static final String REQUEST_TIMESTAMP = "presence_request_timestamp"; 288 289 /** 290 * The audio capable. 291 * <P>Type: BOOLEAN </P> 292 */ 293 public static final String AUDIO_CAPABLE = "audio_capable"; 294 295 /** 296 * The video capable. 297 * <P>Type: BOOLEAN </P> 298 */ 299 public static final String VIDEO_CAPABLE = "video_capable"; 300 } 301 302 /** 303 * This is used to generate a instance of {@link android.telephony.ims.RcsContactPresenceTuple}. 304 * See that class for more information on each of these parameters. 305 */ 306 public static class OptionsColumns implements BaseColumns { 307 308 /** 309 * A reference to the {@link ContactColumns#_ID} that this data belongs to. 310 * <P>Type: INTEGER</P> 311 */ 312 public static final String EAB_COMMON_ID = "eab_common_id"; 313 314 /** 315 * An IMS feature tag indicating the capabilities of the contact. See RFC3840 #section-9. 316 * <P>Type: TEXT</P> 317 */ 318 public static final String FEATURE_TAG = "feature_tag"; 319 320 /** 321 * The request timestamp of options capabilities. 322 * <P>Type: LONG</P> 323 */ 324 public static final String REQUEST_TIMESTAMP = "options_request_timestamp"; 325 } 326 327 @VisibleForTesting 328 public static final class EabDatabaseHelper extends SQLiteOpenHelper { 329 private static final String DB_NAME = "EabDatabase"; 330 private static final List<String> CONTACT_UNIQUE_FIELDS = new ArrayList<>(); 331 private static final List<String> COMMON_UNIQUE_FIELDS = new ArrayList<>(); 332 333 static { 334 CONTACT_UNIQUE_FIELDS.add(ContactColumns.PHONE_NUMBER); 335 } 336 337 @VisibleForTesting 338 public static final String SQL_CREATE_CONTACT_TABLE = "CREATE TABLE " 339 + EAB_CONTACT_TABLE_NAME 340 + " (" 341 + ContactColumns._ID + " INTEGER PRIMARY KEY, " 342 + ContactColumns.PHONE_NUMBER + " Text DEFAULT NULL, " 343 + ContactColumns.CONTACT_ID + " INTEGER DEFAULT -1, " 344 + ContactColumns.RAW_CONTACT_ID + " INTEGER DEFAULT -1, " 345 + ContactColumns.DATA_ID + " INTEGER DEFAULT -1, " 346 + "UNIQUE (" + TextUtils.join(", ", CONTACT_UNIQUE_FIELDS) + ")" 347 + ");"; 348 349 @VisibleForTesting 350 public static final String SQL_CREATE_COMMON_TABLE = "CREATE TABLE " 351 + EAB_COMMON_TABLE_NAME 352 + " (" 353 + EabCommonColumns._ID + " INTEGER PRIMARY KEY, " 354 + EabCommonColumns.EAB_CONTACT_ID + " INTEGER DEFAULT -1, " 355 + EabCommonColumns.MECHANISM + " INTEGER DEFAULT NULL, " 356 + EabCommonColumns.REQUEST_RESULT + " INTEGER DEFAULT -1, " 357 + EabCommonColumns.SUBSCRIPTION_ID + " INTEGER DEFAULT -1 " 358 + ");"; 359 360 @VisibleForTesting 361 public static final String SQL_CREATE_PRESENCE_TUPLE_TABLE = "CREATE TABLE " 362 + EAB_PRESENCE_TUPLE_TABLE_NAME 363 + " (" 364 + PresenceTupleColumns._ID + " INTEGER PRIMARY KEY, " 365 + PresenceTupleColumns.EAB_COMMON_ID + " INTEGER DEFAULT -1, " 366 + PresenceTupleColumns.BASIC_STATUS + " TEXT DEFAULT NULL, " 367 + PresenceTupleColumns.SERVICE_ID + " TEXT DEFAULT NULL, " 368 + PresenceTupleColumns.SERVICE_VERSION + " TEXT DEFAULT NULL, " 369 + PresenceTupleColumns.DESCRIPTION + " TEXT DEFAULT NULL, " 370 + PresenceTupleColumns.REQUEST_TIMESTAMP + " LONG DEFAULT NULL, " 371 + PresenceTupleColumns.CONTACT_URI + " TEXT DEFAULT NULL, " 372 373 // For ServiceCapabilities 374 + PresenceTupleColumns.DUPLEX_MODE + " TEXT DEFAULT NULL, " 375 + PresenceTupleColumns.UNSUPPORTED_DUPLEX_MODE + " TEXT DEFAULT NULL, " 376 + PresenceTupleColumns.AUDIO_CAPABLE + " BOOLEAN DEFAULT NULL, " 377 + PresenceTupleColumns.VIDEO_CAPABLE + " BOOLEAN DEFAULT NULL" 378 + ");"; 379 380 @VisibleForTesting 381 public static final String SQL_CREATE_OPTIONS_TABLE = "CREATE TABLE " 382 + EAB_OPTIONS_TABLE_NAME 383 + " (" 384 + OptionsColumns._ID + " INTEGER PRIMARY KEY, " 385 + OptionsColumns.EAB_COMMON_ID + " INTEGER DEFAULT -1, " 386 + OptionsColumns.REQUEST_TIMESTAMP + " LONG DEFAULT NULL, " 387 + OptionsColumns.FEATURE_TAG + " TEXT DEFAULT NULL " 388 + ");"; 389 EabDatabaseHelper(Context context)390 EabDatabaseHelper(Context context) { 391 super(context, DB_NAME, null, DATABASE_VERSION); 392 } 393 onCreate(SQLiteDatabase db)394 public void onCreate(SQLiteDatabase db) { 395 db.execSQL(SQL_CREATE_CONTACT_TABLE); 396 db.execSQL(SQL_CREATE_COMMON_TABLE); 397 db.execSQL(SQL_CREATE_PRESENCE_TUPLE_TABLE); 398 db.execSQL(SQL_CREATE_OPTIONS_TABLE); 399 } 400 401 @Override onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion)402 public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) { 403 Log.d(TAG, "DB upgrade from " + oldVersion + " to " + newVersion); 404 405 if (oldVersion < 2) { 406 sqLiteDatabase.execSQL("ALTER TABLE " + EAB_CONTACT_TABLE_NAME + " ADD COLUMN " 407 + ContactColumns.CONTACT_ID + " INTEGER DEFAULT -1;"); 408 oldVersion = 2; 409 } 410 411 if (oldVersion < 3) { 412 // Drop UNIQUE constraint in EAB_COMMON_TABLE, SQLite didn't support DROP 413 // constraint, so drop the old one and migrate data to new one 414 415 // Create temp table 416 final String createTempTableCommand = "CREATE TABLE temp" 417 + " (" 418 + EabCommonColumns._ID + " INTEGER PRIMARY KEY, " 419 + EabCommonColumns.EAB_CONTACT_ID + " INTEGER DEFAULT -1, " 420 + EabCommonColumns.MECHANISM + " INTEGER DEFAULT NULL, " 421 + EabCommonColumns.REQUEST_RESULT + " INTEGER DEFAULT -1, " 422 + EabCommonColumns.SUBSCRIPTION_ID + " INTEGER DEFAULT -1 " 423 + ");"; 424 sqLiteDatabase.execSQL(createTempTableCommand); 425 426 // Migrate data to temp table 427 sqLiteDatabase.execSQL("INSERT INTO temp (" 428 + EabCommonColumns._ID + ", " 429 + EabCommonColumns.EAB_CONTACT_ID + ", " 430 + EabCommonColumns.MECHANISM + ", " 431 + EabCommonColumns.REQUEST_RESULT + ", " 432 + EabCommonColumns.SUBSCRIPTION_ID + ") " 433 + " SELECT " 434 + EabCommonColumns._ID + ", " 435 + EabCommonColumns.EAB_CONTACT_ID + ", " 436 + EabCommonColumns.MECHANISM + ", " 437 + EabCommonColumns.REQUEST_RESULT + ", " 438 + EabCommonColumns.SUBSCRIPTION_ID + " " 439 + " FROM " 440 + EAB_COMMON_TABLE_NAME 441 +";"); 442 443 // Drop old one 444 sqLiteDatabase.execSQL("DROP TABLE " + EAB_COMMON_TABLE_NAME + ";"); 445 446 // Rename temp to eab_common 447 sqLiteDatabase.execSQL("ALTER TABLE temp RENAME TO " + EAB_COMMON_TABLE_NAME + ";"); 448 oldVersion = 3; 449 } 450 } 451 } 452 453 private EabDatabaseHelper mOpenHelper; 454 455 @Override onCreate()456 public boolean onCreate() { 457 mOpenHelper = new EabDatabaseHelper(getContext()); 458 return true; 459 } 460 461 /** 462 * Support 6 URLs for querying: 463 * 464 * <ul> 465 * <li>{@link #URL_CONTACT}: query contact table. 466 * 467 * <li>{@link #URL_COMMON}: query common table. 468 * 469 * <li>{@link #URL_PRESENCE}: query presence capability table. 470 * 471 * <li>{@link #URL_OPTIONS}: query options capability table. 472 * 473 * <li>{@link #URL_ALL_WITH_SUB_ID_AND_PHONE_NUMBER}: To provide more efficient query way, 474 * filter by the {@link ContactColumns#PHONE_NUMBER} first and join with others tables. The 475 * format is like content://eab/all/[sub_id]/[phone_number] 476 * 477 * <li> {@link #URL_ALL}: Join all of tables at once 478 * </ul> 479 */ 480 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)481 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 482 String sortOrder) { 483 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 484 SQLiteDatabase db = getReadableDatabase(); 485 int match = URI_MATCHER.match(uri); 486 int subId; 487 String subIdString; 488 489 Log.d(TAG, "Query URI: " + match); 490 491 switch (match) { 492 case URL_CONTACT: 493 qb.setTables(EAB_CONTACT_TABLE_NAME); 494 break; 495 496 case URL_COMMON: 497 qb.setTables(EAB_COMMON_TABLE_NAME); 498 break; 499 500 case URL_PRESENCE: 501 qb.setTables(EAB_PRESENCE_TUPLE_TABLE_NAME); 502 break; 503 504 case URL_OPTIONS: 505 qb.setTables(EAB_OPTIONS_TABLE_NAME); 506 break; 507 508 case URL_ALL_WITH_SUB_ID_AND_PHONE_NUMBER: 509 List<String> pathSegment = uri.getPathSegments(); 510 511 subIdString = pathSegment.get(1); 512 try { 513 subId = Integer.parseInt(subIdString); 514 } catch (NumberFormatException e) { 515 Log.e(TAG, "NumberFormatException" + e); 516 return null; 517 } 518 qb.appendWhereStandalone(EabCommonColumns.SUBSCRIPTION_ID + "=" + subId); 519 520 String phoneNumber = pathSegment.get(2); 521 String whereClause; 522 if (TextUtils.isEmpty(phoneNumber)) { 523 Log.e(TAG, "phone number is null"); 524 return null; 525 } 526 whereClause = " where " + ContactColumns.PHONE_NUMBER + "='" + phoneNumber + "' "; 527 qb.setTables( 528 "((" + QUERY_CONTACT_TABLE + whereClause + ") AS " + EAB_CONTACT_TABLE_NAME 529 + JOIN_ALL_TABLES + ")"); 530 break; 531 532 case URL_ALL: 533 qb.setTables("(" + QUERY_CONTACT_TABLE + JOIN_ALL_TABLES + ")"); 534 break; 535 536 default: 537 Log.d(TAG, "Query failed. Not support URL."); 538 return null; 539 } 540 return qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); 541 } 542 543 @Override insert(Uri uri, ContentValues contentValues)544 public Uri insert(Uri uri, ContentValues contentValues) { 545 SQLiteDatabase db = getWritableDatabase(); 546 int match = URI_MATCHER.match(uri); 547 long result = 0; 548 String tableName = ""; 549 switch (match) { 550 case URL_CONTACT: 551 tableName = EAB_CONTACT_TABLE_NAME; 552 break; 553 case URL_COMMON: 554 tableName = EAB_COMMON_TABLE_NAME; 555 break; 556 case URL_PRESENCE: 557 tableName = EAB_PRESENCE_TUPLE_TABLE_NAME; 558 break; 559 case URL_OPTIONS: 560 tableName = EAB_OPTIONS_TABLE_NAME; 561 break; 562 } 563 if (!TextUtils.isEmpty(tableName)) { 564 result = db.insertWithOnConflict(tableName, null, contentValues, 565 SQLiteDatabase.CONFLICT_REPLACE); 566 Log.d(TAG, "Insert uri: " + match + " ID: " + result); 567 if (result > 0) { 568 getContext().getContentResolver().notifyChange(uri, null, NOTIFY_INSERT); 569 } 570 } else { 571 Log.d(TAG, "Insert. Not support URI."); 572 } 573 574 return Uri.withAppendedPath(uri, String.valueOf(result)); 575 } 576 577 @Override bulkInsert(Uri uri, ContentValues[] values)578 public int bulkInsert(Uri uri, ContentValues[] values) { 579 SQLiteDatabase db = getWritableDatabase(); 580 int match = URI_MATCHER.match(uri); 581 int result = 0; 582 String tableName = ""; 583 switch (match) { 584 case URL_CONTACT: 585 tableName = EAB_CONTACT_TABLE_NAME; 586 break; 587 case URL_COMMON: 588 tableName = EAB_COMMON_TABLE_NAME; 589 break; 590 case URL_PRESENCE: 591 tableName = EAB_PRESENCE_TUPLE_TABLE_NAME; 592 break; 593 case URL_OPTIONS: 594 tableName = EAB_OPTIONS_TABLE_NAME; 595 break; 596 } 597 598 if (TextUtils.isEmpty(tableName)) { 599 Log.d(TAG, "bulkInsert. Not support URI."); 600 return 0; 601 } 602 603 try { 604 // Batch all insertions in a single transaction to improve efficiency. 605 db.beginTransaction(); 606 for (ContentValues contentValue : values) { 607 if (contentValue != null) { 608 db.insertWithOnConflict(tableName, null, contentValue, 609 SQLiteDatabase.CONFLICT_REPLACE); 610 result++; 611 } 612 } 613 db.setTransactionSuccessful(); 614 } finally { 615 db.endTransaction(); 616 } 617 if (result > 0) { 618 getContext().getContentResolver().notifyChange(uri, null, NOTIFY_INSERT); 619 } 620 Log.d(TAG, "bulkInsert uri: " + match + " count: " + result); 621 return result; 622 } 623 624 @Override delete(Uri uri, String selection, String[] selectionArgs)625 public int delete(Uri uri, String selection, String[] selectionArgs) { 626 SQLiteDatabase db = getWritableDatabase(); 627 int match = URI_MATCHER.match(uri); 628 int result = 0; 629 String tableName = ""; 630 switch (match) { 631 case URL_CONTACT: 632 tableName = EAB_CONTACT_TABLE_NAME; 633 break; 634 case URL_COMMON: 635 tableName = EAB_COMMON_TABLE_NAME; 636 break; 637 case URL_PRESENCE: 638 tableName = EAB_PRESENCE_TUPLE_TABLE_NAME; 639 break; 640 case URL_OPTIONS: 641 tableName = EAB_OPTIONS_TABLE_NAME; 642 break; 643 } 644 if (!TextUtils.isEmpty(tableName)) { 645 result = db.delete(tableName, selection, selectionArgs); 646 if (result > 0) { 647 getContext().getContentResolver().notifyChange(uri, null, NOTIFY_DELETE); 648 } 649 Log.d(TAG, "Delete uri: " + match + " result: " + result); 650 } else { 651 Log.d(TAG, "Delete. Not support URI."); 652 } 653 return result; 654 } 655 656 @Override update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs)657 public int update(Uri uri, ContentValues contentValues, String selection, 658 String[] selectionArgs) { 659 SQLiteDatabase db = getWritableDatabase(); 660 int match = URI_MATCHER.match(uri); 661 int result = 0; 662 String tableName = ""; 663 switch (match) { 664 case URL_CONTACT: 665 tableName = EAB_CONTACT_TABLE_NAME; 666 break; 667 case URL_COMMON: 668 tableName = EAB_COMMON_TABLE_NAME; 669 break; 670 case URL_PRESENCE: 671 tableName = EAB_PRESENCE_TUPLE_TABLE_NAME; 672 break; 673 case URL_OPTIONS: 674 tableName = EAB_OPTIONS_TABLE_NAME; 675 break; 676 } 677 if (!TextUtils.isEmpty(tableName)) { 678 result = db.updateWithOnConflict(tableName, contentValues, 679 selection, selectionArgs, SQLiteDatabase.CONFLICT_REPLACE); 680 if (result > 0) { 681 getContext().getContentResolver().notifyChange(uri, null, NOTIFY_UPDATE); 682 } 683 Log.d(TAG, "Update uri: " + match + " result: " + result); 684 } else { 685 Log.d(TAG, "Update. Not support URI."); 686 } 687 return result; 688 } 689 690 @Override getType(Uri uri)691 public String getType(Uri uri) { 692 return null; 693 } 694 695 @VisibleForTesting getWritableDatabase()696 public SQLiteDatabase getWritableDatabase() { 697 return mOpenHelper.getWritableDatabase(); 698 } 699 700 @VisibleForTesting getReadableDatabase()701 public SQLiteDatabase getReadableDatabase() { 702 return mOpenHelper.getReadableDatabase(); 703 } 704 } 705