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