1 /* 2 * Copyright (C) 2019 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.RcsColumns.RcsEventTypes.PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE; 19 import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN; 20 import static android.provider.Telephony.RcsColumns.RcsParticipantEventColumns.NEW_ALIAS_COLUMN; 21 import static android.provider.Telephony.RcsColumns.RcsEventTypes.ICON_CHANGED_EVENT_TYPE; 22 import static android.provider.Telephony.RcsColumns.RcsEventTypes.NAME_CHANGED_EVENT_TYPE; 23 import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_JOINED_EVENT_TYPE; 24 import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_LEFT_EVENT_TYPE; 25 import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN; 26 import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.DESTINATION_PARTICIPANT_ID_COLUMN; 27 import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.EVENT_ID_COLUMN; 28 import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.EVENT_TYPE_COLUMN; 29 import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_ICON_URI_COLUMN; 30 import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_NAME_COLUMN; 31 import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.SOURCE_PARTICIPANT_ID_COLUMN; 32 import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.TIMESTAMP_COLUMN; 33 import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED; 34 import static android.telephony.ims.RcsEventQueryParams.ALL_EVENTS; 35 import static android.telephony.ims.RcsEventQueryParams.ALL_GROUP_THREAD_EVENTS; 36 import static android.telephony.ims.RcsEventQueryParams.EVENT_QUERY_PARAMETERS_KEY; 37 38 import static android.telephony.ims.RcsQueryContinuationToken.EVENT_QUERY_CONTINUATION_TOKEN_TYPE; 39 import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN; 40 import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_EVENT_TABLE; 41 import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_TABLE; 42 import static com.android.providers.telephony.RcsProvider.RCS_THREAD_EVENT_TABLE; 43 import static com.android.providers.telephony.RcsProvider.RCS_THREAD_TABLE; 44 import static com.android.providers.telephony.RcsProvider.RCS_UNIFIED_EVENT_VIEW; 45 import static com.android.providers.telephony.RcsProvider.TAG; 46 import static com.android.providers.telephony.RcsProviderThreadHelper.getThreadIdFromUri; 47 import static com.android.providers.telephony.RcsProviderUtil.INSERTION_FAILED; 48 49 import android.content.ContentValues; 50 import android.database.Cursor; 51 import android.database.sqlite.SQLiteDatabase; 52 import android.database.sqlite.SQLiteOpenHelper; 53 import android.net.Uri; 54 import android.os.Bundle; 55 import android.telephony.ims.RcsEventQueryParams; 56 import android.telephony.ims.RcsQueryContinuationToken; 57 import android.util.Log; 58 59 import com.android.internal.annotations.VisibleForTesting; 60 61 /** 62 * Constants and helpers related to events for {@link RcsProvider} to keep the code clean. 63 * 64 * @hide 65 */ 66 class RcsProviderEventHelper { 67 private static final int PARTICIPANT_INDEX_IN_EVENT_URI = 1; 68 private static final int EVENT_INDEX_IN_EVENT_URI = 3; 69 70 @VisibleForTesting createRcsEventTables(SQLiteDatabase db)71 public static void createRcsEventTables(SQLiteDatabase db) { 72 Log.d(TAG, "Creating event tables"); 73 74 // Add the event tables 75 db.execSQL("CREATE TABLE " + RCS_THREAD_EVENT_TABLE + "(" + EVENT_ID_COLUMN 76 + " INTEGER PRIMARY KEY AUTOINCREMENT, " + RCS_THREAD_ID_COLUMN + " INTEGER, " 77 + SOURCE_PARTICIPANT_ID_COLUMN + " INTEGER, " + EVENT_TYPE_COLUMN + " INTEGER, " 78 + TIMESTAMP_COLUMN + " INTEGER, " + DESTINATION_PARTICIPANT_ID_COLUMN + " INTEGER, " 79 + NEW_ICON_URI_COLUMN + " TEXT, " + NEW_NAME_COLUMN + " TEXT, " + " FOREIGN KEY (" 80 + RCS_THREAD_ID_COLUMN + ") REFERENCES " + RCS_THREAD_TABLE + " (" 81 + RCS_THREAD_ID_COLUMN + "), FOREIGN KEY (" + SOURCE_PARTICIPANT_ID_COLUMN 82 + ") REFERENCES " + RCS_PARTICIPANT_TABLE + " (" + RCS_PARTICIPANT_ID_COLUMN 83 + "))"); 84 85 db.execSQL("CREATE TABLE " + RCS_PARTICIPANT_EVENT_TABLE + "(" + EVENT_ID_COLUMN 86 + " INTEGER PRIMARY KEY AUTOINCREMENT, " + SOURCE_PARTICIPANT_ID_COLUMN + 87 " INTEGER, " + TIMESTAMP_COLUMN + " INTEGER, " 88 + NEW_ALIAS_COLUMN + " TEXT," + " FOREIGN KEY (" + SOURCE_PARTICIPANT_ID_COLUMN 89 + ") REFERENCES " + RCS_PARTICIPANT_TABLE + " (" + RCS_PARTICIPANT_ID_COLUMN 90 + "))"); 91 92 // Add the views 93 94 // The following is a unified event view that puts every entry in both tables into one query 95 db.execSQL("CREATE VIEW " + RCS_UNIFIED_EVENT_VIEW + " AS " 96 + "SELECT " + PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE + " AS " + EVENT_TYPE_COLUMN 97 + ", " + EVENT_ID_COLUMN + ", " + SOURCE_PARTICIPANT_ID_COLUMN + ", " 98 + TIMESTAMP_COLUMN + ", " + NEW_ALIAS_COLUMN + ", NULL as " + RCS_THREAD_ID_COLUMN 99 + ", NULL as " + DESTINATION_PARTICIPANT_ID_COLUMN + ", NULL as " 100 + NEW_ICON_URI_COLUMN + ", NULL as " + NEW_NAME_COLUMN + " " 101 + "FROM " + RCS_PARTICIPANT_EVENT_TABLE + " " 102 + "UNION " 103 + "SELECT " + EVENT_TYPE_COLUMN + ", " + EVENT_ID_COLUMN + ", " 104 + SOURCE_PARTICIPANT_ID_COLUMN + ", " + TIMESTAMP_COLUMN + ", " 105 + "NULL as " + NEW_ALIAS_COLUMN + ", " + RCS_THREAD_ID_COLUMN + ", " 106 + DESTINATION_PARTICIPANT_ID_COLUMN + ", " + NEW_ICON_URI_COLUMN + ", " 107 + NEW_NAME_COLUMN + " " 108 + "FROM " + RCS_THREAD_EVENT_TABLE); 109 } 110 111 private final SQLiteOpenHelper mSqLiteOpenHelper; 112 queryEvents(Bundle bundle)113 Cursor queryEvents(Bundle bundle) { 114 RcsEventQueryParams queryParameters = null; 115 RcsQueryContinuationToken continuationToken = null; 116 117 if (bundle != null) { 118 queryParameters = bundle.getParcelable(EVENT_QUERY_PARAMETERS_KEY); 119 continuationToken = bundle.getParcelable(QUERY_CONTINUATION_TOKEN); 120 } 121 122 if (continuationToken != null) { 123 return RcsProviderUtil.performContinuationQuery(mSqLiteOpenHelper.getReadableDatabase(), 124 continuationToken); 125 } 126 127 // if no query parameters were entered, build an empty query parameters object 128 if (queryParameters == null) { 129 queryParameters = new RcsEventQueryParams.Builder().build(); 130 } 131 132 return performInitialQuery(queryParameters); 133 } 134 performInitialQuery(RcsEventQueryParams queryParameters)135 private Cursor performInitialQuery(RcsEventQueryParams queryParameters) { 136 SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase(); 137 StringBuilder rawQuery = new StringBuilder("SELECT * FROM ").append(RCS_UNIFIED_EVENT_VIEW); 138 139 int eventType = queryParameters.getEventType(); 140 if (eventType != ALL_EVENTS) { 141 rawQuery.append(" WHERE ").append(EVENT_TYPE_COLUMN); 142 if (eventType == ALL_GROUP_THREAD_EVENTS) { 143 rawQuery.append(" IN (").append( 144 PARTICIPANT_JOINED_EVENT_TYPE).append(", ").append( 145 PARTICIPANT_LEFT_EVENT_TYPE).append(", ").append( 146 ICON_CHANGED_EVENT_TYPE).append(", ").append( 147 NAME_CHANGED_EVENT_TYPE).append( 148 ")"); 149 } else { 150 rawQuery.append("=").append(eventType); 151 } 152 } 153 154 rawQuery.append(" ORDER BY "); 155 156 int sortingProperty = queryParameters.getSortingProperty(); 157 if (sortingProperty == RcsEventQueryParams.SORT_BY_TIMESTAMP) { 158 rawQuery.append(TIMESTAMP_COLUMN); 159 } else { 160 rawQuery.append(EVENT_ID_COLUMN); 161 } 162 163 rawQuery.append(queryParameters.getSortDirection() ? " ASC " : " DESC "); 164 165 RcsProviderUtil.appendLimit(rawQuery, queryParameters.getLimit()); 166 String rawQueryAsString = rawQuery.toString(); 167 Cursor cursor = db.rawQuery(rawQueryAsString, null); 168 169 // if the query was paginated, build the next query 170 int limit = queryParameters.getLimit(); 171 if (limit > 0) { 172 RcsProviderUtil.createContinuationTokenBundle(cursor, 173 new RcsQueryContinuationToken(EVENT_QUERY_CONTINUATION_TOKEN_TYPE, 174 rawQueryAsString, limit, limit), QUERY_CONTINUATION_TOKEN); 175 } 176 177 return cursor; 178 } 179 RcsProviderEventHelper(SQLiteOpenHelper sqLiteOpenHelper)180 RcsProviderEventHelper(SQLiteOpenHelper sqLiteOpenHelper) { 181 mSqLiteOpenHelper = sqLiteOpenHelper; 182 } 183 insertParticipantEvent(Uri uri, ContentValues values)184 long insertParticipantEvent(Uri uri, ContentValues values) { 185 String participantId = getParticipantIdFromUri(uri); 186 187 SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase(); 188 values.put(SOURCE_PARTICIPANT_ID_COLUMN, participantId); 189 long rowId = db.insert(RCS_PARTICIPANT_EVENT_TABLE, SOURCE_PARTICIPANT_ID_COLUMN, values); 190 values.remove(SOURCE_PARTICIPANT_ID_COLUMN); 191 192 if (rowId == INSERTION_FAILED) { 193 return TRANSACTION_FAILED; 194 } 195 196 return rowId; 197 } 198 insertParticipantJoinedEvent(Uri uri, ContentValues values)199 long insertParticipantJoinedEvent(Uri uri, ContentValues values) { 200 return insertGroupThreadEvent(uri, values, PARTICIPANT_JOINED_EVENT_TYPE); 201 } 202 insertParticipantLeftEvent(Uri uri, ContentValues values)203 long insertParticipantLeftEvent(Uri uri, ContentValues values) { 204 return insertGroupThreadEvent(uri, values, PARTICIPANT_LEFT_EVENT_TYPE); 205 } 206 insertThreadNameChangeEvent(Uri uri, ContentValues values)207 long insertThreadNameChangeEvent(Uri uri, ContentValues values) { 208 return insertGroupThreadEvent(uri, values, NAME_CHANGED_EVENT_TYPE); 209 } 210 insertThreadIconChangeEvent(Uri uri, ContentValues values)211 long insertThreadIconChangeEvent(Uri uri, ContentValues values) { 212 return insertGroupThreadEvent(uri, values, ICON_CHANGED_EVENT_TYPE); 213 } 214 insertGroupThreadEvent(Uri uri, ContentValues valuesParameter, int eventType)215 private long insertGroupThreadEvent(Uri uri, ContentValues valuesParameter, 216 int eventType) { 217 String threadId = getThreadIdFromUri(uri); 218 219 SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase(); 220 ContentValues values = new ContentValues(valuesParameter); 221 values.put(EVENT_TYPE_COLUMN, eventType); 222 values.put(RCS_THREAD_ID_COLUMN, threadId); 223 long rowId = db.insert(RCS_THREAD_EVENT_TABLE, EVENT_ID_COLUMN, values); 224 225 if (rowId == INSERTION_FAILED) { 226 return TRANSACTION_FAILED; 227 } 228 229 return rowId; 230 } 231 deleteParticipantEvent(Uri uri)232 int deleteParticipantEvent(Uri uri) { 233 String eventId = getEventIdFromEventUri(uri); 234 SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase(); 235 236 return db.delete(RCS_PARTICIPANT_EVENT_TABLE, EVENT_ID_COLUMN + "=?", 237 new String[]{eventId}); 238 } 239 deleteGroupThreadEvent(Uri uri)240 int deleteGroupThreadEvent(Uri uri) { 241 String eventId = getEventIdFromEventUri(uri); 242 SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase(); 243 244 return db.delete(RCS_THREAD_EVENT_TABLE, EVENT_ID_COLUMN + "=?", new String[]{eventId}); 245 } 246 getEventIdFromEventUri(Uri uri)247 private String getEventIdFromEventUri(Uri uri) { 248 return uri.getPathSegments().get(EVENT_INDEX_IN_EVENT_URI); 249 } 250 getParticipantIdFromUri(Uri uri)251 private String getParticipantIdFromUri(Uri uri) { 252 return uri.getPathSegments().get(PARTICIPANT_INDEX_IN_EVENT_URI); 253 } 254 } 255