• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 package com.android.providers.telephony;
17 
18 import static android.provider.Telephony.CanonicalAddressesColumns.ADDRESS;
19 import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.CANONICAL_ADDRESS_ID_COLUMN;
20 import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_ALIAS_COLUMN;
21 import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
22 import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_URI_PART;
23 import static android.provider.Telephony.RcsColumns.RcsParticipantHelpers.RCS_PARTICIPANT_WITH_ADDRESS_VIEW;
24 import static android.provider.Telephony.RcsColumns.RcsParticipantHelpers.RCS_PARTICIPANT_WITH_THREAD_VIEW;
25 import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
26 import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
27 import static android.telephony.ims.RcsParticipantQueryParams.PARTICIPANT_QUERY_PARAMETERS_KEY;
28 
29 import static android.telephony.ims.RcsQueryContinuationToken.PARTICIPANT_QUERY_CONTINUATION_TOKEN_TYPE;
30 import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
31 import static com.android.providers.telephony.RcsProvider.GROUP_THREAD_URI_PREFIX;
32 import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_TABLE;
33 import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_THREAD_JUNCTION_TABLE;
34 import static com.android.providers.telephony.RcsProvider.RCS_THREAD_TABLE;
35 import static com.android.providers.telephony.RcsProvider.TAG;
36 import static com.android.providers.telephony.RcsProviderThreadHelper.getThreadIdFromUri;
37 import static com.android.providers.telephony.RcsProviderUtil.INSERTION_FAILED;
38 
39 import android.content.ContentValues;
40 import android.database.Cursor;
41 import android.database.sqlite.SQLiteDatabase;
42 import android.database.sqlite.SQLiteOpenHelper;
43 import android.net.Uri;
44 import android.os.Bundle;
45 import android.telephony.ims.RcsParticipantQueryParams;
46 import android.telephony.ims.RcsQueryContinuationToken;
47 import android.text.TextUtils;
48 import android.util.Log;
49 
50 import com.android.internal.annotations.VisibleForTesting;
51 
52 /**
53  * Constants and helpers related to participants for {@link RcsProvider} to keep the code clean.
54  *
55  * @hide
56  */
57 class RcsProviderParticipantHelper {
58     private static final int PARTICIPANT_ID_INDEX_IN_URI = 1;
59     private static final int PARTICIPANT_ID_INDEX_IN_THREAD_URI = 3;
60 
61     @VisibleForTesting
createParticipantTables(SQLiteDatabase db)62     public static void createParticipantTables(SQLiteDatabase db) {
63         Log.d(TAG, "Creating participant tables");
64 
65         // create participant tables
66         db.execSQL("CREATE TABLE " + RCS_PARTICIPANT_TABLE + " (" +
67                 RCS_PARTICIPANT_ID_COLUMN + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
68                 CANONICAL_ADDRESS_ID_COLUMN + " INTEGER ," +
69                 RCS_ALIAS_COLUMN + " TEXT, " +
70                 "FOREIGN KEY(" + CANONICAL_ADDRESS_ID_COLUMN + ") "
71                 + "REFERENCES canonical_addresses(_id)" +
72                 ");");
73 
74         db.execSQL("CREATE TABLE " + RCS_PARTICIPANT_THREAD_JUNCTION_TABLE + " (" +
75                 RCS_THREAD_ID_COLUMN + " INTEGER, " +
76                 RCS_PARTICIPANT_ID_COLUMN + " INTEGER, " +
77                 "CONSTRAINT thread_participant PRIMARY KEY("
78                 + RCS_THREAD_ID_COLUMN + ", " + RCS_PARTICIPANT_ID_COLUMN + "), " +
79                 "FOREIGN KEY(" + RCS_THREAD_ID_COLUMN
80                 + ") REFERENCES " + RCS_THREAD_TABLE + "(" + RCS_THREAD_ID_COLUMN + "), " +
81                 "FOREIGN KEY(" + RCS_PARTICIPANT_ID_COLUMN
82                 + ") REFERENCES " + RCS_PARTICIPANT_TABLE + "(" + RCS_PARTICIPANT_ID_COLUMN + "))");
83 
84         // create views
85 
86         // The following view joins rcs_participant table with canonical_addresses table to add the
87         // actual address of a participant in the result.
88         db.execSQL("CREATE VIEW " + RCS_PARTICIPANT_WITH_ADDRESS_VIEW + " AS SELECT "
89                 + "rcs_participant.rcs_participant_id, rcs_participant.canonical_address_id, "
90                 + "rcs_participant.rcs_alias, canonical_addresses.address FROM rcs_participant "
91                 + "LEFT JOIN canonical_addresses ON "
92                 + "rcs_participant.canonical_address_id=canonical_addresses._id");
93 
94         // The following view is the rcs_participant_with_address_view above, plus the information
95         // on which threads this participant contributes to, to enable getting participants of a
96         // thread
97         db.execSQL("CREATE VIEW " + RCS_PARTICIPANT_WITH_THREAD_VIEW + " AS SELECT "
98                 + "rcs_participant.rcs_participant_id, rcs_participant.canonical_address_id, "
99                 + "rcs_participant.rcs_alias, canonical_addresses.address, rcs_thread_participant"
100                 + ".rcs_thread_id FROM rcs_participant LEFT JOIN canonical_addresses ON "
101                 + "rcs_participant.canonical_address_id=canonical_addresses._id LEFT JOIN "
102                 + "rcs_thread_participant ON rcs_participant.rcs_participant_id="
103                 + "rcs_thread_participant.rcs_participant_id");
104 
105         // TODO - create indexes for faster querying
106     }
107 
108     private final SQLiteOpenHelper mSqLiteOpenHelper;
109 
RcsProviderParticipantHelper(SQLiteOpenHelper sqLiteOpenHelper)110     RcsProviderParticipantHelper(SQLiteOpenHelper sqLiteOpenHelper) {
111         mSqLiteOpenHelper = sqLiteOpenHelper;
112     }
113 
queryParticipant(Bundle bundle)114     Cursor queryParticipant(Bundle bundle) {
115         RcsParticipantQueryParams queryParameters = null;
116         RcsQueryContinuationToken continuationToken = null;
117 
118         if (bundle != null) {
119             queryParameters = bundle.getParcelable(PARTICIPANT_QUERY_PARAMETERS_KEY);
120             continuationToken = bundle.getParcelable(QUERY_CONTINUATION_TOKEN);
121         }
122 
123         if (continuationToken != null) {
124             return RcsProviderUtil.performContinuationQuery(mSqLiteOpenHelper.getReadableDatabase(),
125                     continuationToken);
126         }
127 
128         if (queryParameters == null) {
129             queryParameters = new RcsParticipantQueryParams.Builder().build();
130         }
131 
132         return performInitialQuery(queryParameters);
133     }
134 
performInitialQuery(RcsParticipantQueryParams queryParameters)135     private Cursor performInitialQuery(RcsParticipantQueryParams queryParameters) {
136         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
137 
138         StringBuilder rawQuery = buildInitialRawQuery(queryParameters);
139         RcsProviderUtil.appendLimit(rawQuery, queryParameters.getLimit());
140         String rawQueryAsString = rawQuery.toString();
141         Cursor cursor = db.rawQuery(rawQueryAsString, null);
142 
143         // If the query was paginated, build the next query
144         int limit = queryParameters.getLimit();
145         if (limit > 0) {
146             RcsProviderUtil.createContinuationTokenBundle(cursor,
147                     new RcsQueryContinuationToken(PARTICIPANT_QUERY_CONTINUATION_TOKEN_TYPE,
148                             rawQueryAsString, limit, limit), QUERY_CONTINUATION_TOKEN);
149         }
150 
151         return cursor;
152     }
153 
buildInitialRawQuery(RcsParticipantQueryParams queryParameters)154     private StringBuilder buildInitialRawQuery(RcsParticipantQueryParams queryParameters) {
155         StringBuilder rawQuery = new StringBuilder("SELECT * FROM ");
156 
157         boolean isThreadFiltered = queryParameters.getThreadId() > 0;
158 
159         if (isThreadFiltered) {
160             rawQuery.append(RCS_PARTICIPANT_WITH_THREAD_VIEW);
161         } else {
162             rawQuery.append(RCS_PARTICIPANT_WITH_ADDRESS_VIEW);
163         }
164 
165         boolean isAliasFiltered = !TextUtils.isEmpty(queryParameters.getAliasLike());
166         boolean isCanonicalAddressFiltered = !TextUtils.isEmpty(
167                 queryParameters.getCanonicalAddressLike());
168 
169         if (isAliasFiltered || isCanonicalAddressFiltered || isThreadFiltered) {
170             rawQuery.append(" WHERE ");
171         }
172 
173         if (isAliasFiltered) {
174             rawQuery.append(RCS_ALIAS_COLUMN).append(" LIKE \"").append(
175                     queryParameters.getAliasLike()).append("\"");
176         }
177 
178         if (isCanonicalAddressFiltered) {
179             if (isAliasFiltered) {
180                 rawQuery.append(" AND ");
181             }
182             rawQuery.append(ADDRESS).append(" LIKE \"").append(
183                     queryParameters.getCanonicalAddressLike()).append("\"");
184         }
185 
186         if (isThreadFiltered) {
187             if (isAliasFiltered || isCanonicalAddressFiltered) {
188                 rawQuery.append(" AND ");
189             }
190             rawQuery.append(RCS_THREAD_ID_COLUMN).append("=").append(queryParameters.getThreadId());
191         }
192 
193         rawQuery.append(" ORDER BY ");
194 
195         int sortingProperty = queryParameters.getSortingProperty();
196         if (sortingProperty == RcsParticipantQueryParams.SORT_BY_ALIAS) {
197             rawQuery.append(RCS_ALIAS_COLUMN);
198         } else if (sortingProperty == RcsParticipantQueryParams.SORT_BY_CANONICAL_ADDRESS) {
199             rawQuery.append(ADDRESS);
200         } else {
201             rawQuery.append(RCS_PARTICIPANT_ID_COLUMN);
202         }
203         rawQuery.append(queryParameters.getSortDirection() ? " ASC " : " DESC ");
204 
205         return rawQuery;
206     }
207 
queryParticipantWithId(Uri uri, String[] projection)208     Cursor queryParticipantWithId(Uri uri, String[] projection) {
209         SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
210         return db.query(RCS_PARTICIPANT_WITH_ADDRESS_VIEW, projection,
211                 getParticipantIdSelection(uri), null, null, null, null);
212     }
213 
queryParticipantIn1To1Thread(Uri uri)214     Cursor queryParticipantIn1To1Thread(Uri uri) {
215         String threadId = getThreadIdFromUri(uri);
216         SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
217 
218         return db.rawQuery(
219                 "  SELECT * "
220                         + "FROM rcs_participant "
221                         + "WHERE rcs_participant.rcs_participant_id = ("
222                         + "  SELECT rcs_thread_participant.rcs_participant_id "
223                         + "  FROM rcs_thread_participant "
224                         + "  WHERE rcs_thread_participant.rcs_thread_id=" + threadId + ")", null);
225     }
226 
queryParticipantsInGroupThread(Uri uri)227     Cursor queryParticipantsInGroupThread(Uri uri) {
228         String threadId = getThreadIdFromUri(uri);
229         SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
230 
231         return db.rawQuery("  SELECT * "
232                 + "FROM rcs_participant "
233                 + "WHERE rcs_participant.rcs_participant_id = ("
234                 + "  SELECT rcs_participant_id "
235                 + "  FROM rcs_thread_participant "
236                 + "  WHERE rcs_thread_id= " + threadId + ")", null);
237     }
238 
queryParticipantInGroupThreadWithId(Uri uri)239     Cursor queryParticipantInGroupThreadWithId(Uri uri) {
240         String threadId = getThreadIdFromUri(uri);
241         String participantId = getParticipantIdFromUri(uri);
242         SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
243 
244         return db.rawQuery("  SELECT * "
245                         + "FROM rcs_participant "
246                         + "WHERE rcs_participant.rcs_participant_id = ("
247                         + "  SELECT rcs_participant_id "
248                         + "  FROM rcs_thread_participant "
249                         + "  WHERE rcs_thread_id=? AND rcs_participant_id=?)",
250                 new String[]{threadId, participantId});
251     }
252 
insertParticipant(ContentValues contentValues)253     long insertParticipant(ContentValues contentValues) {
254         if (!contentValues.containsKey(CANONICAL_ADDRESS_ID_COLUMN) || TextUtils.isEmpty(
255                 contentValues.getAsString(CANONICAL_ADDRESS_ID_COLUMN))) {
256             Log.e(TAG,
257                     "RcsProviderParticipantHelper: Inserting participants without canonical "
258                             + "address is not supported");
259             return TRANSACTION_FAILED;
260         }
261         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
262         long rowId = db.insert(RCS_PARTICIPANT_TABLE, RCS_PARTICIPANT_ID_COLUMN, contentValues);
263         Log.e(TAG, "Inserted participant with rowId=" + rowId);
264         if (rowId < 0) {
265             return TRANSACTION_FAILED;
266         }
267         return rowId;
268     }
269 
270     /**
271      * Inserts a participant into group thread. This function returns the participant ID instead of
272      * the row id in the junction table
273      */
insertParticipantIntoGroupThread(ContentValues values)274     long insertParticipantIntoGroupThread(ContentValues values) {
275         if (!values.containsKey(RCS_THREAD_ID_COLUMN) || !values.containsKey(
276                 RCS_PARTICIPANT_ID_COLUMN)) {
277             Log.e(TAG, "RcsProviderParticipantHelper: Cannot insert participant into group.");
278             return TRANSACTION_FAILED;
279         }
280         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
281         long insertedRowId = db.insert(RCS_PARTICIPANT_THREAD_JUNCTION_TABLE,
282                 RCS_PARTICIPANT_ID_COLUMN,
283                 values);
284 
285         if (insertedRowId == INSERTION_FAILED) {
286             return TRANSACTION_FAILED;
287         }
288 
289         return values.getAsLong(RCS_PARTICIPANT_ID_COLUMN);
290     }
291 
292     /**
293      * Inserts a participant into group thread. This function returns the participant ID instead of
294      * the row id in the junction table
295      */
insertParticipantIntoGroupThreadWithId(Uri uri)296     long insertParticipantIntoGroupThreadWithId(Uri uri) {
297         String threadId = getThreadIdFromUri(uri);
298         String participantId = getParticipantIdFromUri(uri);
299         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
300 
301         ContentValues contentValues = new ContentValues(2);
302         contentValues.put(RCS_THREAD_ID_COLUMN, threadId);
303         contentValues.put(RCS_PARTICIPANT_ID_COLUMN, participantId);
304 
305         long insertedRowId = db.insert(
306                 RCS_PARTICIPANT_THREAD_JUNCTION_TABLE, RCS_PARTICIPANT_ID_COLUMN, contentValues);
307 
308         if (insertedRowId == INSERTION_FAILED) {
309             return TRANSACTION_FAILED;
310         }
311 
312         return Long.parseLong(participantId);
313     }
314 
deleteParticipantWithId(Uri uri)315     int deleteParticipantWithId(Uri uri) {
316         String participantId = uri.getPathSegments().get(PARTICIPANT_ID_INDEX_IN_URI);
317         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
318 
319         // See if this participant is involved in any threads
320         Cursor cursor = db.query(RCS_PARTICIPANT_THREAD_JUNCTION_TABLE, null,
321                 RCS_PARTICIPANT_ID_COLUMN + "=?", new String[]{participantId}, null, null, null);
322 
323         int participatingThreadCount = 0;
324         if (cursor != null) {
325             participatingThreadCount = cursor.getCount();
326             cursor.close();
327         }
328 
329         if (participatingThreadCount > 0) {
330             Log.e(TAG,
331                     "RcsProviderParticipantHelper: Can't delete participant while it is still in "
332                             + "RCS threads, uri:"
333                             + uri);
334             return 0;
335         }
336 
337         return db.delete(RCS_PARTICIPANT_TABLE, RCS_PARTICIPANT_ID_COLUMN + "=?",
338                 new String[]{participantId});
339     }
340 
deleteParticipantFromGroupThread(Uri uri)341     int deleteParticipantFromGroupThread(Uri uri) {
342         String threadId = getThreadIdFromUri(uri);
343         String participantId = getParticipantIdFromUri(uri);
344         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
345         // TODO check to remove owner
346         return db.delete(RCS_PARTICIPANT_THREAD_JUNCTION_TABLE,
347                 RCS_THREAD_ID_COLUMN + "=? AND " + RCS_PARTICIPANT_ID_COLUMN + "=?",
348                 new String[]{threadId, participantId});
349     }
350 
updateParticipant(ContentValues contentValues, String selection, String[] selectionArgs)351     int updateParticipant(ContentValues contentValues, String selection, String[] selectionArgs) {
352         if (contentValues.containsKey(RCS_PARTICIPANT_ID_COLUMN)) {
353             Log.e(TAG, "RcsProviderParticipantHelper: Updating participant id is not supported");
354             return 0;
355         }
356 
357         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
358         return db.update(RCS_PARTICIPANT_TABLE, contentValues, selection, selectionArgs);
359     }
360 
updateParticipantWithId(ContentValues contentValues, Uri uri)361     int updateParticipantWithId(ContentValues contentValues, Uri uri) {
362         return updateParticipant(contentValues, getParticipantIdSelection(uri), null);
363     }
364 
getParticipantIdSelection(Uri uri)365     private String getParticipantIdSelection(Uri uri) {
366         return RCS_PARTICIPANT_ID_COLUMN + "=" + uri.getPathSegments().get(
367                 PARTICIPANT_ID_INDEX_IN_URI);
368     }
369 
getParticipantInThreadUri(ContentValues values, long rowId)370     Uri getParticipantInThreadUri(ContentValues values, long rowId) {
371         if (values == null) {
372             return null;
373         }
374         Integer threadId = values.getAsInteger(RCS_THREAD_ID_COLUMN);
375         if (threadId == null) {
376             return null;
377         }
378 
379         return GROUP_THREAD_URI_PREFIX.buildUpon().appendPath(
380                 Integer.toString(threadId)).appendPath(RCS_PARTICIPANT_URI_PART).appendPath(
381                 Long.toString(rowId)).build();
382     }
383 
getParticipantIdFromUri(Uri uri)384     private String getParticipantIdFromUri(Uri uri) {
385         return uri.getPathSegments().get(PARTICIPANT_ID_INDEX_IN_THREAD_URI);
386     }
387 }
388