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 = 4; 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, "contact", URL_CONTACT)100 URI_MATCHER.addURI(AUTHORITY, "contact", URL_CONTACT); URI_MATCHER.addURI(AUTHORITY, "common", URL_COMMON)101 URI_MATCHER.addURI(AUTHORITY, "common", URL_COMMON); URI_MATCHER.addURI(AUTHORITY, "presence", URL_PRESENCE)102 URI_MATCHER.addURI(AUTHORITY, "presence", URL_PRESENCE); URI_MATCHER.addURI(AUTHORITY, "options", URL_OPTIONS)103 URI_MATCHER.addURI(AUTHORITY, "options", URL_OPTIONS); URI_MATCHER.addURI(AUTHORITY, "all", URL_ALL)104 URI_MATCHER.addURI(AUTHORITY, "all", URL_ALL); URI_MATCHER.addURI(AUTHORITY, "all/#/*", 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 * The value of the 'entity' attribute is the 'pres' URL of the PRESENTITY publishing 211 * presence document 212 * <P>Type: TEXT</P> 213 */ 214 public static final String ENTITY_URI = "entity_uri"; 215 } 216 217 /** 218 * This is used to generate a instance of {@link android.telephony.ims.RcsContactPresenceTuple}. 219 * See that class for more information on each of these parameters. 220 */ 221 public static class PresenceTupleColumns implements BaseColumns { 222 223 /** 224 * A reference to the {@link ContactColumns#_ID} that this data belongs to. 225 * <P>Type: INTEGER</P> 226 */ 227 public static final String EAB_COMMON_ID = "eab_common_id"; 228 229 /** 230 * The basic status of service capabilities. Possible values are 231 * {@link android.telephony.ims.RcsContactPresenceTuple#TUPLE_BASIC_STATUS_OPEN} 232 * and 233 * {@link android.telephony.ims.RcsContactPresenceTuple#TUPLE_BASIC_STATUS_CLOSED} 234 * <P>Type: TEXT</P> 235 */ 236 public static final String BASIC_STATUS = "basic_status"; 237 238 /** 239 * The OMA Presence service-id associated with this capability. See the OMA Presence SIMPLE 240 * specification v1.1, section 10.5.1. 241 * <P>Type: TEXT</P> 242 */ 243 public static final String SERVICE_ID = "service_id"; 244 245 /** 246 * The contact uri of service capabilities. 247 * <P>Type: TEXT</P> 248 */ 249 public static final String CONTACT_URI = "contact_uri"; 250 251 /** 252 * The service version of service capabilities. 253 * <P>Type: TEXT</P> 254 */ 255 public static final String SERVICE_VERSION = "service_version"; 256 257 /** 258 * The description of service capabilities. 259 * <P>Type: TEXT</P> 260 */ 261 public static final String DESCRIPTION = "description"; 262 263 /** 264 * The supported duplex mode of service capabilities. Possible values are 265 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_FULL} 266 * and 267 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_HALF} 268 * and 269 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_RECEIVE_ONLY} 270 * and 271 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_SEND_ONLY} 272 * <P>Type: TEXT</P> 273 */ 274 public static final String DUPLEX_MODE = "duplex_mode"; 275 276 /** 277 * The unsupported duplex mode of service capabilities. Possible values are 278 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_FULL} 279 * and 280 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_HALF} 281 * and 282 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_RECEIVE_ONLY} 283 * and 284 * {@link android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#DUPLEX_MODE_SEND_ONLY} 285 * <P>Type: TEXT</P> 286 */ 287 public static final String UNSUPPORTED_DUPLEX_MODE = "unsupported_duplex_mode"; 288 289 /** 290 * The presence request timestamp. Represents seconds of UTC time since Unix epoch 291 * 1970-01-01 00:00:00. 292 * <P>Type: LONG</P> 293 */ 294 public static final String REQUEST_TIMESTAMP = "presence_request_timestamp"; 295 296 /** 297 * The audio capable. 298 * <P>Type: BOOLEAN </P> 299 */ 300 public static final String AUDIO_CAPABLE = "audio_capable"; 301 302 /** 303 * The video capable. 304 * <P>Type: BOOLEAN </P> 305 */ 306 public static final String VIDEO_CAPABLE = "video_capable"; 307 } 308 309 /** 310 * This is used to generate a instance of {@link android.telephony.ims.RcsContactPresenceTuple}. 311 * See that class for more information on each of these parameters. 312 */ 313 public static class OptionsColumns implements BaseColumns { 314 315 /** 316 * A reference to the {@link ContactColumns#_ID} that this data belongs to. 317 * <P>Type: INTEGER</P> 318 */ 319 public static final String EAB_COMMON_ID = "eab_common_id"; 320 321 /** 322 * An IMS feature tag indicating the capabilities of the contact. See RFC3840 #section-9. 323 * <P>Type: TEXT</P> 324 */ 325 public static final String FEATURE_TAG = "feature_tag"; 326 327 /** 328 * The request timestamp of options capabilities. 329 * <P>Type: LONG</P> 330 */ 331 public static final String REQUEST_TIMESTAMP = "options_request_timestamp"; 332 } 333 334 @VisibleForTesting 335 public static final class EabDatabaseHelper extends SQLiteOpenHelper { 336 private static final String DB_NAME = "EabDatabase"; 337 private static final List<String> CONTACT_UNIQUE_FIELDS = new ArrayList<>(); 338 private static final List<String> COMMON_UNIQUE_FIELDS = new ArrayList<>(); 339 340 static { 341 CONTACT_UNIQUE_FIELDS.add(ContactColumns.PHONE_NUMBER); 342 } 343 344 @VisibleForTesting 345 public static final String SQL_CREATE_CONTACT_TABLE = "CREATE TABLE " 346 + EAB_CONTACT_TABLE_NAME 347 + " (" 348 + ContactColumns._ID + " INTEGER PRIMARY KEY, " 349 + ContactColumns.PHONE_NUMBER + " Text DEFAULT NULL, " 350 + ContactColumns.CONTACT_ID + " INTEGER DEFAULT -1, " 351 + ContactColumns.RAW_CONTACT_ID + " INTEGER DEFAULT -1, " 352 + ContactColumns.DATA_ID + " INTEGER DEFAULT -1, " 353 + "UNIQUE (" + TextUtils.join(", ", CONTACT_UNIQUE_FIELDS) + ")" 354 + ");"; 355 356 @VisibleForTesting 357 public static final String SQL_CREATE_COMMON_TABLE = "CREATE TABLE " 358 + EAB_COMMON_TABLE_NAME 359 + " (" 360 + EabCommonColumns._ID + " INTEGER PRIMARY KEY, " 361 + EabCommonColumns.EAB_CONTACT_ID + " INTEGER DEFAULT -1, " 362 + EabCommonColumns.MECHANISM + " INTEGER DEFAULT NULL, " 363 + EabCommonColumns.REQUEST_RESULT + " INTEGER DEFAULT -1, " 364 + EabCommonColumns.SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " 365 + EabCommonColumns.ENTITY_URI + " TEXT DEFAULT NULL " 366 + ");"; 367 368 @VisibleForTesting 369 public static final String SQL_CREATE_PRESENCE_TUPLE_TABLE = "CREATE TABLE " 370 + EAB_PRESENCE_TUPLE_TABLE_NAME 371 + " (" 372 + PresenceTupleColumns._ID + " INTEGER PRIMARY KEY, " 373 + PresenceTupleColumns.EAB_COMMON_ID + " INTEGER DEFAULT -1, " 374 + PresenceTupleColumns.BASIC_STATUS + " TEXT DEFAULT NULL, " 375 + PresenceTupleColumns.SERVICE_ID + " TEXT DEFAULT NULL, " 376 + PresenceTupleColumns.SERVICE_VERSION + " TEXT DEFAULT NULL, " 377 + PresenceTupleColumns.DESCRIPTION + " TEXT DEFAULT NULL, " 378 + PresenceTupleColumns.REQUEST_TIMESTAMP + " LONG DEFAULT NULL, " 379 + PresenceTupleColumns.CONTACT_URI + " TEXT DEFAULT NULL, " 380 381 // For ServiceCapabilities 382 + PresenceTupleColumns.DUPLEX_MODE + " TEXT DEFAULT NULL, " 383 + PresenceTupleColumns.UNSUPPORTED_DUPLEX_MODE + " TEXT DEFAULT NULL, " 384 + PresenceTupleColumns.AUDIO_CAPABLE + " BOOLEAN DEFAULT NULL, " 385 + PresenceTupleColumns.VIDEO_CAPABLE + " BOOLEAN DEFAULT NULL" 386 + ");"; 387 388 @VisibleForTesting 389 public static final String SQL_CREATE_OPTIONS_TABLE = "CREATE TABLE " 390 + EAB_OPTIONS_TABLE_NAME 391 + " (" 392 + OptionsColumns._ID + " INTEGER PRIMARY KEY, " 393 + OptionsColumns.EAB_COMMON_ID + " INTEGER DEFAULT -1, " 394 + OptionsColumns.REQUEST_TIMESTAMP + " LONG DEFAULT NULL, " 395 + OptionsColumns.FEATURE_TAG + " TEXT DEFAULT NULL " 396 + ");"; 397 EabDatabaseHelper(Context context)398 EabDatabaseHelper(Context context) { 399 super(context, DB_NAME, null, DATABASE_VERSION); 400 } 401 onCreate(SQLiteDatabase db)402 public void onCreate(SQLiteDatabase db) { 403 db.execSQL(SQL_CREATE_CONTACT_TABLE); 404 db.execSQL(SQL_CREATE_COMMON_TABLE); 405 db.execSQL(SQL_CREATE_PRESENCE_TUPLE_TABLE); 406 db.execSQL(SQL_CREATE_OPTIONS_TABLE); 407 } 408 409 @Override onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion)410 public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) { 411 Log.d(TAG, "DB upgrade from " + oldVersion + " to " + newVersion); 412 413 if (oldVersion < 2) { 414 sqLiteDatabase.execSQL("ALTER TABLE " + EAB_CONTACT_TABLE_NAME + " ADD COLUMN " 415 + ContactColumns.CONTACT_ID + " INTEGER DEFAULT -1;"); 416 oldVersion = 2; 417 } 418 419 if (oldVersion < 3) { 420 // Drop UNIQUE constraint in EAB_COMMON_TABLE, SQLite didn't support DROP 421 // constraint, so drop the old one and migrate data to new one 422 423 // Create temp table 424 final String createTempTableCommand = "CREATE TABLE temp" 425 + " (" 426 + EabCommonColumns._ID + " INTEGER PRIMARY KEY, " 427 + EabCommonColumns.EAB_CONTACT_ID + " INTEGER DEFAULT -1, " 428 + EabCommonColumns.MECHANISM + " INTEGER DEFAULT NULL, " 429 + EabCommonColumns.REQUEST_RESULT + " INTEGER DEFAULT -1, " 430 + EabCommonColumns.SUBSCRIPTION_ID + " INTEGER DEFAULT -1 " 431 + ");"; 432 sqLiteDatabase.execSQL(createTempTableCommand); 433 434 // Migrate data to temp table 435 sqLiteDatabase.execSQL("INSERT INTO temp (" 436 + EabCommonColumns._ID + ", " 437 + EabCommonColumns.EAB_CONTACT_ID + ", " 438 + EabCommonColumns.MECHANISM + ", " 439 + EabCommonColumns.REQUEST_RESULT + ", " 440 + EabCommonColumns.SUBSCRIPTION_ID + ") " 441 + " SELECT " 442 + EabCommonColumns._ID + ", " 443 + EabCommonColumns.EAB_CONTACT_ID + ", " 444 + EabCommonColumns.MECHANISM + ", " 445 + EabCommonColumns.REQUEST_RESULT + ", " 446 + EabCommonColumns.SUBSCRIPTION_ID + " " 447 + " FROM " 448 + EAB_COMMON_TABLE_NAME 449 +";"); 450 451 // Drop old one 452 sqLiteDatabase.execSQL("DROP TABLE " + EAB_COMMON_TABLE_NAME + ";"); 453 454 // Rename temp to eab_common 455 sqLiteDatabase.execSQL("ALTER TABLE temp RENAME TO " + EAB_COMMON_TABLE_NAME + ";"); 456 oldVersion = 3; 457 } 458 459 if (oldVersion < 4) { 460 sqLiteDatabase.execSQL("ALTER TABLE " + EAB_COMMON_TABLE_NAME + " ADD COLUMN " 461 + EabCommonColumns.ENTITY_URI + " Text DEFAULT NULL;"); 462 oldVersion = 4; 463 } 464 } 465 } 466 467 private EabDatabaseHelper mOpenHelper; 468 469 @Override onCreate()470 public boolean onCreate() { 471 mOpenHelper = new EabDatabaseHelper(getContext()); 472 return true; 473 } 474 475 /** 476 * Support 6 URLs for querying: 477 * 478 * <ul> 479 * <li>{@link #URL_CONTACT}: query contact table. 480 * 481 * <li>{@link #URL_COMMON}: query common table. 482 * 483 * <li>{@link #URL_PRESENCE}: query presence capability table. 484 * 485 * <li>{@link #URL_OPTIONS}: query options capability table. 486 * 487 * <li>{@link #URL_ALL_WITH_SUB_ID_AND_PHONE_NUMBER}: To provide more efficient query way, 488 * filter by the {@link ContactColumns#PHONE_NUMBER} first and join with others tables. The 489 * format is like content://eab/all/[sub_id]/[phone_number] 490 * 491 * <li> {@link #URL_ALL}: Join all of tables at once 492 * </ul> 493 */ 494 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)495 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 496 String sortOrder) { 497 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 498 SQLiteDatabase db = getReadableDatabase(); 499 int match = URI_MATCHER.match(uri); 500 int subId; 501 String subIdString; 502 503 Log.d(TAG, "Query URI: " + match); 504 505 switch (match) { 506 case URL_CONTACT: 507 qb.setTables(EAB_CONTACT_TABLE_NAME); 508 break; 509 510 case URL_COMMON: 511 qb.setTables(EAB_COMMON_TABLE_NAME); 512 break; 513 514 case URL_PRESENCE: 515 qb.setTables(EAB_PRESENCE_TUPLE_TABLE_NAME); 516 break; 517 518 case URL_OPTIONS: 519 qb.setTables(EAB_OPTIONS_TABLE_NAME); 520 break; 521 522 case URL_ALL_WITH_SUB_ID_AND_PHONE_NUMBER: 523 List<String> pathSegment = uri.getPathSegments(); 524 525 subIdString = pathSegment.get(1); 526 try { 527 subId = Integer.parseInt(subIdString); 528 } catch (NumberFormatException e) { 529 Log.e(TAG, "NumberFormatException" + e); 530 return null; 531 } 532 qb.appendWhereStandalone(EabCommonColumns.SUBSCRIPTION_ID + "=" + subId); 533 534 String phoneNumber = pathSegment.get(2); 535 String whereClause; 536 if (TextUtils.isEmpty(phoneNumber)) { 537 Log.e(TAG, "phone number is null"); 538 return null; 539 } 540 whereClause = " where " + ContactColumns.PHONE_NUMBER + "='" + phoneNumber + "' "; 541 qb.setTables( 542 "((" + QUERY_CONTACT_TABLE + whereClause + ") AS " + EAB_CONTACT_TABLE_NAME 543 + JOIN_ALL_TABLES + ")"); 544 break; 545 546 case URL_ALL: 547 qb.setTables("(" + QUERY_CONTACT_TABLE + JOIN_ALL_TABLES + ")"); 548 break; 549 550 default: 551 Log.d(TAG, "Query failed. Not support URL."); 552 return null; 553 } 554 return qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); 555 } 556 557 @Override insert(Uri uri, ContentValues contentValues)558 public Uri insert(Uri uri, ContentValues contentValues) { 559 SQLiteDatabase db = getWritableDatabase(); 560 int match = URI_MATCHER.match(uri); 561 long result = 0; 562 String tableName = ""; 563 switch (match) { 564 case URL_CONTACT: 565 tableName = EAB_CONTACT_TABLE_NAME; 566 break; 567 case URL_COMMON: 568 tableName = EAB_COMMON_TABLE_NAME; 569 break; 570 case URL_PRESENCE: 571 tableName = EAB_PRESENCE_TUPLE_TABLE_NAME; 572 break; 573 case URL_OPTIONS: 574 tableName = EAB_OPTIONS_TABLE_NAME; 575 break; 576 } 577 if (!TextUtils.isEmpty(tableName)) { 578 result = db.insertWithOnConflict(tableName, null, contentValues, 579 SQLiteDatabase.CONFLICT_REPLACE); 580 Log.d(TAG, "Insert uri: " + match + " ID: " + result); 581 if (result > 0) { 582 getContext().getContentResolver().notifyChange(uri, null, NOTIFY_INSERT); 583 } 584 } else { 585 Log.d(TAG, "Insert. Not support URI."); 586 } 587 588 return Uri.withAppendedPath(uri, String.valueOf(result)); 589 } 590 591 @Override bulkInsert(Uri uri, ContentValues[] values)592 public int bulkInsert(Uri uri, ContentValues[] values) { 593 SQLiteDatabase db = getWritableDatabase(); 594 int match = URI_MATCHER.match(uri); 595 int result = 0; 596 String tableName = ""; 597 switch (match) { 598 case URL_CONTACT: 599 tableName = EAB_CONTACT_TABLE_NAME; 600 break; 601 case URL_COMMON: 602 tableName = EAB_COMMON_TABLE_NAME; 603 break; 604 case URL_PRESENCE: 605 tableName = EAB_PRESENCE_TUPLE_TABLE_NAME; 606 break; 607 case URL_OPTIONS: 608 tableName = EAB_OPTIONS_TABLE_NAME; 609 break; 610 } 611 612 if (TextUtils.isEmpty(tableName)) { 613 Log.d(TAG, "bulkInsert. Not support URI."); 614 return 0; 615 } 616 617 try { 618 // Batch all insertions in a single transaction to improve efficiency. 619 db.beginTransaction(); 620 for (ContentValues contentValue : values) { 621 if (contentValue != null) { 622 db.insertWithOnConflict(tableName, null, contentValue, 623 SQLiteDatabase.CONFLICT_REPLACE); 624 result++; 625 } 626 } 627 db.setTransactionSuccessful(); 628 } finally { 629 db.endTransaction(); 630 } 631 if (result > 0) { 632 getContext().getContentResolver().notifyChange(uri, null, NOTIFY_INSERT); 633 } 634 Log.d(TAG, "bulkInsert uri: " + match + " count: " + result); 635 return result; 636 } 637 638 @Override delete(Uri uri, String selection, String[] selectionArgs)639 public int delete(Uri uri, String selection, String[] selectionArgs) { 640 SQLiteDatabase db = getWritableDatabase(); 641 int match = URI_MATCHER.match(uri); 642 int result = 0; 643 String tableName = ""; 644 switch (match) { 645 case URL_CONTACT: 646 tableName = EAB_CONTACT_TABLE_NAME; 647 break; 648 case URL_COMMON: 649 tableName = EAB_COMMON_TABLE_NAME; 650 break; 651 case URL_PRESENCE: 652 tableName = EAB_PRESENCE_TUPLE_TABLE_NAME; 653 break; 654 case URL_OPTIONS: 655 tableName = EAB_OPTIONS_TABLE_NAME; 656 break; 657 } 658 if (!TextUtils.isEmpty(tableName)) { 659 result = db.delete(tableName, selection, selectionArgs); 660 if (result > 0) { 661 getContext().getContentResolver().notifyChange(uri, null, NOTIFY_DELETE); 662 } 663 Log.d(TAG, "Delete uri: " + match + " result: " + result); 664 } else { 665 Log.d(TAG, "Delete. Not support URI."); 666 } 667 return result; 668 } 669 670 @Override update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs)671 public int update(Uri uri, ContentValues contentValues, String selection, 672 String[] selectionArgs) { 673 SQLiteDatabase db = getWritableDatabase(); 674 int match = URI_MATCHER.match(uri); 675 int result = 0; 676 String tableName = ""; 677 switch (match) { 678 case URL_CONTACT: 679 tableName = EAB_CONTACT_TABLE_NAME; 680 break; 681 case URL_COMMON: 682 tableName = EAB_COMMON_TABLE_NAME; 683 break; 684 case URL_PRESENCE: 685 tableName = EAB_PRESENCE_TUPLE_TABLE_NAME; 686 break; 687 case URL_OPTIONS: 688 tableName = EAB_OPTIONS_TABLE_NAME; 689 break; 690 } 691 if (!TextUtils.isEmpty(tableName)) { 692 result = db.updateWithOnConflict(tableName, contentValues, 693 selection, selectionArgs, SQLiteDatabase.CONFLICT_REPLACE); 694 if (result > 0) { 695 getContext().getContentResolver().notifyChange(uri, null, NOTIFY_UPDATE); 696 } 697 Log.d(TAG, "Update uri: " + match + " result: " + result); 698 } else { 699 Log.d(TAG, "Update. Not support URI."); 700 } 701 return result; 702 } 703 704 @Override getType(Uri uri)705 public String getType(Uri uri) { 706 return null; 707 } 708 709 @VisibleForTesting getWritableDatabase()710 public SQLiteDatabase getWritableDatabase() { 711 return mOpenHelper.getWritableDatabase(); 712 } 713 714 @VisibleForTesting getReadableDatabase()715 public SQLiteDatabase getReadableDatabase() { 716 return mOpenHelper.getReadableDatabase(); 717 } 718 } 719