1 /* 2 * Copyright (C) 2008 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.providers.telephony; 18 19 import android.app.AppOpsManager; 20 import android.content.ContentProvider; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.content.UriMatcher; 24 import android.database.Cursor; 25 import android.database.DatabaseUtils; 26 import android.database.MatrixCursor; 27 import android.database.sqlite.SQLiteDatabase; 28 import android.database.sqlite.SQLiteOpenHelper; 29 import android.database.sqlite.SQLiteQueryBuilder; 30 import android.net.Uri; 31 import android.os.Binder; 32 import android.os.Bundle; 33 import android.os.UserHandle; 34 import android.provider.BaseColumns; 35 import android.provider.Telephony; 36 import android.provider.Telephony.CanonicalAddressesColumns; 37 import android.provider.Telephony.Mms; 38 import android.provider.Telephony.MmsSms; 39 import android.provider.Telephony.MmsSms.PendingMessages; 40 import android.provider.Telephony.Sms; 41 import android.provider.Telephony.Sms.Conversations; 42 import android.provider.Telephony.Threads; 43 import android.provider.Telephony.ThreadsColumns; 44 import android.telephony.SmsManager; 45 import android.telephony.SubscriptionManager; 46 import android.text.TextUtils; 47 import android.util.Log; 48 49 import com.android.internal.telephony.TelephonyPermissions; 50 import com.android.internal.telephony.TelephonyStatsLog; 51 import com.android.internal.telephony.util.TelephonyUtils; 52 53 import com.google.android.mms.pdu.PduHeaders; 54 55 import java.io.FileDescriptor; 56 import java.io.PrintWriter; 57 import java.util.Arrays; 58 import java.util.HashSet; 59 import java.util.List; 60 import java.util.Locale; 61 import java.util.Set; 62 63 /** 64 * This class provides the ability to query the MMS and SMS databases 65 * at the same time, mixing messages from both in a single thread 66 * (A.K.A. conversation). 67 * 68 * A virtual column, MmsSms.TYPE_DISCRIMINATOR_COLUMN, may be 69 * requested in the projection for a query. Its value is either "mms" 70 * or "sms", depending on whether the message represented by the row 71 * is an MMS message or an SMS message, respectively. 72 * 73 * This class also provides the ability to find out what addresses 74 * participated in a particular thread. It doesn't support updates 75 * for either of these. 76 * 77 * This class provides a way to allocate and retrieve thread IDs. 78 * This is done atomically through a query. There is no insert URI 79 * for this. 80 * 81 * Finally, this class provides a way to delete or update all messages 82 * in a thread. 83 */ 84 public class MmsSmsProvider extends ContentProvider { 85 private static final UriMatcher URI_MATCHER = 86 new UriMatcher(UriMatcher.NO_MATCH); 87 private static final String LOG_TAG = "MmsSmsProvider"; 88 private static final boolean DEBUG = false; 89 private static final int MULTIPLE_THREAD_IDS_FOUND = TelephonyStatsLog 90 .MMS_SMS_PROVIDER_GET_THREAD_ID_FAILED__FAILURE_CODE__FAILURE_MULTIPLE_THREAD_IDS_FOUND; 91 private static final int FAILURE_FIND_OR_CREATE_THREAD_ID_SQL = TelephonyStatsLog 92 .MMS_SMS_PROVIDER_GET_THREAD_ID_FAILED__FAILURE_CODE__FAILURE_FIND_OR_CREATE_THREAD_ID_SQL; 93 94 private static final String NO_DELETES_INSERTS_OR_UPDATES = 95 "MmsSmsProvider does not support deletes, inserts, or updates for this URI."; 96 private static final int URI_CONVERSATIONS = 0; 97 private static final int URI_CONVERSATIONS_MESSAGES = 1; 98 private static final int URI_CONVERSATIONS_RECIPIENTS = 2; 99 private static final int URI_MESSAGES_BY_PHONE = 3; 100 private static final int URI_THREAD_ID = 4; 101 private static final int URI_CANONICAL_ADDRESS = 5; 102 private static final int URI_PENDING_MSG = 6; 103 private static final int URI_COMPLETE_CONVERSATIONS = 7; 104 private static final int URI_UNDELIVERED_MSG = 8; 105 private static final int URI_CONVERSATIONS_SUBJECT = 9; 106 private static final int URI_NOTIFICATIONS = 10; 107 private static final int URI_OBSOLETE_THREADS = 11; 108 private static final int URI_DRAFT = 12; 109 private static final int URI_CANONICAL_ADDRESSES = 13; 110 private static final int URI_SEARCH = 14; 111 private static final int URI_SEARCH_SUGGEST = 15; 112 private static final int URI_FIRST_LOCKED_MESSAGE_ALL = 16; 113 private static final int URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID = 17; 114 private static final int URI_MESSAGE_ID_TO_THREAD = 18; 115 116 /** 117 * the name of the table that is used to store the queue of 118 * messages(both MMS and SMS) to be sent/downloaded. 119 */ 120 public static final String TABLE_PENDING_MSG = "pending_msgs"; 121 122 /** 123 * the name of the table that is used to store the canonical addresses for both SMS and MMS. 124 */ 125 static final String TABLE_CANONICAL_ADDRESSES = "canonical_addresses"; 126 127 /** 128 * the name of the table that is used to store the conversation threads. 129 */ 130 static final String TABLE_THREADS = "threads"; 131 132 // These constants are used to construct union queries across the 133 // MMS and SMS base tables. 134 135 // These are the columns that appear in both the MMS ("pdu") and 136 // SMS ("sms") message tables. 137 private static final String[] MMS_SMS_COLUMNS = 138 { BaseColumns._ID, Mms.DATE, Mms.DATE_SENT, Mms.READ, Mms.THREAD_ID, Mms.LOCKED, 139 Mms.SUBSCRIPTION_ID }; 140 141 // These are the columns that appear only in the MMS message 142 // table. 143 private static final String[] MMS_ONLY_COLUMNS = { 144 Mms.CONTENT_CLASS, Mms.CONTENT_LOCATION, Mms.CONTENT_TYPE, 145 Mms.DELIVERY_REPORT, Mms.EXPIRY, Mms.MESSAGE_CLASS, Mms.MESSAGE_ID, 146 Mms.MESSAGE_SIZE, Mms.MESSAGE_TYPE, Mms.MESSAGE_BOX, Mms.PRIORITY, 147 Mms.READ_STATUS, Mms.RESPONSE_STATUS, Mms.RESPONSE_TEXT, 148 Mms.RETRIEVE_STATUS, Mms.RETRIEVE_TEXT_CHARSET, Mms.REPORT_ALLOWED, 149 Mms.READ_REPORT, Mms.STATUS, Mms.SUBJECT, Mms.SUBJECT_CHARSET, 150 Mms.TRANSACTION_ID, Mms.MMS_VERSION, Mms.TEXT_ONLY }; 151 152 // These are the columns that appear only in the SMS message 153 // table. 154 private static final String[] SMS_ONLY_COLUMNS = 155 { "address", "body", "person", "reply_path_present", 156 "service_center", "status", "subject", "type", "error_code" }; 157 158 // These are all the columns that appear in the "threads" table. 159 private static final String[] THREADS_COLUMNS = { 160 BaseColumns._ID, 161 ThreadsColumns.DATE, 162 ThreadsColumns.RECIPIENT_IDS, 163 ThreadsColumns.MESSAGE_COUNT 164 }; 165 166 private static final String[] CANONICAL_ADDRESSES_COLUMNS_1 = 167 new String[] { CanonicalAddressesColumns.ADDRESS }; 168 169 private static final String[] CANONICAL_ADDRESSES_COLUMNS_2 = 170 new String[] { CanonicalAddressesColumns._ID, 171 CanonicalAddressesColumns.ADDRESS }; 172 173 // These are all the columns that appear in the MMS and SMS 174 // message tables. 175 private static final String[] UNION_COLUMNS = 176 new String[MMS_SMS_COLUMNS.length 177 + MMS_ONLY_COLUMNS.length 178 + SMS_ONLY_COLUMNS.length]; 179 180 // These are all the columns that appear in the MMS table. 181 private static final Set<String> MMS_COLUMNS = new HashSet<String>(); 182 183 // These are all the columns that appear in the SMS table. 184 private static final Set<String> SMS_COLUMNS = new HashSet<String>(); 185 186 private static final String VND_ANDROID_DIR_MMS_SMS = 187 "vnd.android-dir/mms-sms"; 188 189 private static final String[] ID_PROJECTION = { BaseColumns._ID }; 190 191 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 192 193 private static final String[] SEARCH_STRING = new String[1]; 194 private static final String SEARCH_QUERY = "SELECT snippet(words, '', ' ', '', 1, 1) as " + 195 "snippet FROM words WHERE index_text MATCH ? ORDER BY snippet LIMIT 50;"; 196 197 private static final String SMS_CONVERSATION_CONSTRAINT = "(" + 198 Sms.TYPE + " != " + Sms.MESSAGE_TYPE_DRAFT + ")"; 199 200 private static final String MMS_CONVERSATION_CONSTRAINT = "(" + 201 Mms.MESSAGE_BOX + " != " + Mms.MESSAGE_BOX_DRAFTS + " AND (" + 202 Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_SEND_REQ + " OR " + 203 Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF + " OR " + 204 Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + "))"; 205 getTextSearchQuery(String smsTable, String pduTable)206 private static String getTextSearchQuery(String smsTable, String pduTable) { 207 // Search on the words table but return the rows from the corresponding sms table 208 final String smsQuery = "SELECT " 209 + smsTable + "._id AS _id," 210 + "thread_id," 211 + "address," 212 + "body," 213 + "date," 214 + "date_sent," 215 + "index_text," 216 + "words._id " 217 + "FROM " + smsTable + ",words " 218 + "WHERE (index_text MATCH ? " 219 + "AND " + smsTable + "._id=words.source_id " 220 + "AND words.table_to_use=1)"; 221 222 // Search on the words table but return the rows from the corresponding parts table 223 final String mmsQuery = "SELECT " 224 + pduTable + "._id," 225 + "thread_id," 226 + "addr.address," 227 + "part.text AS body," 228 + pduTable + ".date," 229 + pduTable + ".date_sent," 230 + "index_text," 231 + "words._id " 232 + "FROM " + pduTable + ",part,addr,words " 233 + "WHERE ((part.mid=" + pduTable + "._id) " 234 + "AND (addr.msg_id=" + pduTable + "._id) " 235 + "AND (addr.type=" + PduHeaders.TO + ") " 236 + "AND (part.ct='text/plain') " 237 + "AND (index_text MATCH ?) " 238 + "AND (part._id = words.source_id) " 239 + "AND (words.table_to_use=2))"; 240 241 // This code queries the sms and mms tables and returns a unified result set 242 // of text matches. We query the sms table which is pretty simple. We also 243 // query the pdu, part and addr table to get the mms result. Note we're 244 // using a UNION so we have to have the same number of result columns from 245 // both queries. 246 return smsQuery + " UNION " + mmsQuery + " " 247 + "GROUP BY thread_id " 248 + "ORDER BY thread_id ASC, date DESC"; 249 } 250 251 private static final String AUTHORITY = "mms-sms"; 252 253 static { URI_MATCHER.addURI(AUTHORITY, "conversations", URI_CONVERSATIONS)254 URI_MATCHER.addURI(AUTHORITY, "conversations", URI_CONVERSATIONS); URI_MATCHER.addURI(AUTHORITY, "complete-conversations", URI_COMPLETE_CONVERSATIONS)255 URI_MATCHER.addURI(AUTHORITY, "complete-conversations", URI_COMPLETE_CONVERSATIONS); 256 257 // In these patterns, "#" is the thread ID. URI_MATCHER.addURI( AUTHORITY, "conversations/#", URI_CONVERSATIONS_MESSAGES)258 URI_MATCHER.addURI( 259 AUTHORITY, "conversations/#", URI_CONVERSATIONS_MESSAGES); URI_MATCHER.addURI( AUTHORITY, "conversations/#/recipients", URI_CONVERSATIONS_RECIPIENTS)260 URI_MATCHER.addURI( 261 AUTHORITY, "conversations/#/recipients", 262 URI_CONVERSATIONS_RECIPIENTS); 263 URI_MATCHER.addURI( AUTHORITY, "conversations/#/subject", URI_CONVERSATIONS_SUBJECT)264 URI_MATCHER.addURI( 265 AUTHORITY, "conversations/#/subject", 266 URI_CONVERSATIONS_SUBJECT); 267 268 // URI for deleting obsolete threads. URI_MATCHER.addURI(AUTHORITY, "conversations/obsolete", URI_OBSOLETE_THREADS)269 URI_MATCHER.addURI(AUTHORITY, "conversations/obsolete", URI_OBSOLETE_THREADS); 270 URI_MATCHER.addURI( AUTHORITY, "messages/byphone/*", URI_MESSAGES_BY_PHONE)271 URI_MATCHER.addURI( 272 AUTHORITY, "messages/byphone/*", 273 URI_MESSAGES_BY_PHONE); 274 275 // In this pattern, two query parameter names are expected: 276 // "subject" and "recipient." Multiple "recipient" parameters 277 // may be present. URI_MATCHER.addURI(AUTHORITY, "threadID", URI_THREAD_ID)278 URI_MATCHER.addURI(AUTHORITY, "threadID", URI_THREAD_ID); 279 280 // Use this pattern to query the canonical address by given ID. URI_MATCHER.addURI(AUTHORITY, "canonical-address/#", URI_CANONICAL_ADDRESS)281 URI_MATCHER.addURI(AUTHORITY, "canonical-address/#", URI_CANONICAL_ADDRESS); 282 283 // Use this pattern to query all canonical addresses. URI_MATCHER.addURI(AUTHORITY, "canonical-addresses", URI_CANONICAL_ADDRESSES)284 URI_MATCHER.addURI(AUTHORITY, "canonical-addresses", URI_CANONICAL_ADDRESSES); 285 URI_MATCHER.addURI(AUTHORITY, "search", URI_SEARCH)286 URI_MATCHER.addURI(AUTHORITY, "search", URI_SEARCH); URI_MATCHER.addURI(AUTHORITY, "searchSuggest", URI_SEARCH_SUGGEST)287 URI_MATCHER.addURI(AUTHORITY, "searchSuggest", URI_SEARCH_SUGGEST); 288 289 // In this pattern, two query parameters may be supplied: 290 // "protocol" and "message." For example: 291 // content://mms-sms/pending? 292 // -> Return all pending messages; 293 // content://mms-sms/pending?protocol=sms 294 // -> Only return pending SMs; 295 // content://mms-sms/pending?protocol=mms&message=1 296 // -> Return the the pending MM which ID equals '1'. 297 // URI_MATCHER.addURI(AUTHORITY, "pending", URI_PENDING_MSG)298 URI_MATCHER.addURI(AUTHORITY, "pending", URI_PENDING_MSG); 299 300 // Use this pattern to get a list of undelivered messages. URI_MATCHER.addURI(AUTHORITY, "undelivered", URI_UNDELIVERED_MSG)301 URI_MATCHER.addURI(AUTHORITY, "undelivered", URI_UNDELIVERED_MSG); 302 303 // Use this pattern to see what delivery status reports (for 304 // both MMS and SMS) have not been delivered to the user. URI_MATCHER.addURI(AUTHORITY, "notifications", URI_NOTIFICATIONS)305 URI_MATCHER.addURI(AUTHORITY, "notifications", URI_NOTIFICATIONS); 306 URI_MATCHER.addURI(AUTHORITY, "draft", URI_DRAFT)307 URI_MATCHER.addURI(AUTHORITY, "draft", URI_DRAFT); 308 URI_MATCHER.addURI(AUTHORITY, "locked", URI_FIRST_LOCKED_MESSAGE_ALL)309 URI_MATCHER.addURI(AUTHORITY, "locked", URI_FIRST_LOCKED_MESSAGE_ALL); 310 URI_MATCHER.addURI(AUTHORITY, "locked/#", URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID)311 URI_MATCHER.addURI(AUTHORITY, "locked/#", URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID); 312 URI_MATCHER.addURI(AUTHORITY, "messageIdToThread", URI_MESSAGE_ID_TO_THREAD)313 URI_MATCHER.addURI(AUTHORITY, "messageIdToThread", URI_MESSAGE_ID_TO_THREAD); initializeColumnSets()314 initializeColumnSets(); 315 } 316 317 private SQLiteOpenHelper mOpenHelper; 318 319 private boolean mUseStrictPhoneNumberComparation; 320 321 // Call() methods and parameters 322 private static final String METHOD_IS_RESTORING = "is_restoring"; 323 private static final String IS_RESTORING_KEY = "restoring"; 324 private static final String METHOD_GARBAGE_COLLECT = "garbage_collect"; 325 private static final String DO_DELETE = "delete"; 326 327 @Override onCreate()328 public boolean onCreate() { 329 setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS); 330 mOpenHelper = MmsSmsDatabaseHelper.getInstanceForCe(getContext()); 331 mUseStrictPhoneNumberComparation = 332 getContext().getResources().getBoolean( 333 com.android.internal.R.bool.config_use_strict_phone_number_comparation); 334 TelephonyBackupAgent.DeferredSmsMmsRestoreService.startIfFilesExist(getContext()); 335 return true; 336 } 337 338 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)339 public Cursor query(Uri uri, String[] projection, 340 String selection, String[] selectionArgs, String sortOrder) { 341 final int callerUid = Binder.getCallingUid(); 342 final UserHandle callerUserHandle = Binder.getCallingUserHandle(); 343 344 // First check if restricted views of the "sms" and "pdu" tables should be used based on the 345 // caller's identity. Only system, phone or the default sms app can have full access 346 // of sms/mms data. For other apps, we present a restricted view which only contains sent 347 // or received messages, without wap pushes. 348 final boolean accessRestricted = ProviderUtil.isAccessRestricted( 349 getContext(), getCallingPackage(), callerUid); 350 final String pduTable = MmsProvider.getPduTable(accessRestricted); 351 final String smsTable = SmsProvider.getSmsTable(accessRestricted); 352 353 // If access is restricted, we don't allow subqueries in the query. 354 if (accessRestricted) { 355 try { 356 SqlQueryChecker.checkQueryParametersForSubqueries(projection, selection, sortOrder); 357 } catch (IllegalArgumentException e) { 358 Log.w(LOG_TAG, "Query rejected: " + e.getMessage()); 359 return null; 360 } 361 } 362 363 String selectionBySubIds; 364 final long token = Binder.clearCallingIdentity(); 365 try { 366 // Filter MMS/SMS based on subId 367 selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(), callerUserHandle); 368 } finally { 369 Binder.restoreCallingIdentity(token); 370 } 371 372 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 373 Cursor cursor = null; 374 Cursor emptyCursor = new MatrixCursor((projection == null) ? 375 (new String[] {}) : projection); 376 final int match = URI_MATCHER.match(uri); 377 switch (match) { 378 case URI_COMPLETE_CONVERSATIONS: 379 if (selectionBySubIds == null) { 380 // No subscriptions associated with user, return empty cursor. 381 return emptyCursor; 382 } 383 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 384 385 cursor = getCompleteConversations(projection, selection, sortOrder, smsTable, 386 pduTable); 387 break; 388 case URI_CONVERSATIONS: 389 String simple = uri.getQueryParameter("simple"); 390 if ((simple != null) && simple.equals("true")) { 391 String threadType = uri.getQueryParameter("thread_type"); 392 if (!TextUtils.isEmpty(threadType)) { 393 try { 394 Integer.parseInt(threadType); 395 selection = concatSelections( 396 selection, Threads.TYPE + "=" + threadType); 397 } catch (NumberFormatException ex) { 398 Log.e(LOG_TAG, "Thread type must be int"); 399 // return empty cursor 400 break; 401 } 402 } 403 cursor = getSimpleConversations( 404 projection, selection, selectionArgs, sortOrder); 405 } else { 406 if (selectionBySubIds == null) { 407 // No subscriptions associated with user, return empty cursor. 408 return emptyCursor; 409 } 410 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 411 412 cursor = getConversations( 413 projection, selection, sortOrder, smsTable, pduTable); 414 } 415 break; 416 case URI_CONVERSATIONS_MESSAGES: 417 if (selectionBySubIds == null) { 418 // No subscriptions associated with user, return empty cursor. 419 return emptyCursor; 420 } 421 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 422 423 cursor = getConversationMessages(uri.getPathSegments().get(1), projection, 424 selection, sortOrder, smsTable, pduTable); 425 break; 426 case URI_CONVERSATIONS_RECIPIENTS: 427 cursor = getConversationById( 428 uri.getPathSegments().get(1), projection, selection, 429 selectionArgs, sortOrder); 430 break; 431 case URI_CONVERSATIONS_SUBJECT: 432 cursor = getConversationById( 433 uri.getPathSegments().get(1), projection, selection, 434 selectionArgs, sortOrder); 435 break; 436 case URI_MESSAGES_BY_PHONE: 437 if (selectionBySubIds == null) { 438 // No subscriptions associated with user, return emptyCursor. 439 return emptyCursor; 440 } 441 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 442 443 cursor = getMessagesByPhoneNumber( 444 uri.getPathSegments().get(2), projection, selection, sortOrder, smsTable, 445 pduTable); 446 break; 447 case URI_THREAD_ID: 448 List<String> recipients = uri.getQueryParameters("recipient"); 449 450 cursor = getThreadId(recipients); 451 break; 452 case URI_CANONICAL_ADDRESS: { 453 if (selectionBySubIds == null) { 454 // No subscriptions associated with user, return empty cursor. 455 return emptyCursor; 456 } 457 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 458 459 String extraSelection = "_id=" + uri.getPathSegments().get(1); 460 String finalSelection = TextUtils.isEmpty(selection) 461 ? extraSelection : extraSelection + " AND " + selection; 462 cursor = db.query(TABLE_CANONICAL_ADDRESSES, 463 CANONICAL_ADDRESSES_COLUMNS_1, 464 finalSelection, 465 selectionArgs, 466 null, null, 467 sortOrder); 468 break; 469 } 470 case URI_CANONICAL_ADDRESSES: 471 if (selectionBySubIds == null) { 472 // No subscriptions associated with user, return empty cursor. 473 return emptyCursor; 474 } 475 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 476 477 cursor = db.query(TABLE_CANONICAL_ADDRESSES, 478 CANONICAL_ADDRESSES_COLUMNS_2, 479 selection, 480 selectionArgs, 481 null, null, 482 sortOrder); 483 break; 484 case URI_SEARCH_SUGGEST: { 485 SEARCH_STRING[0] = uri.getQueryParameter("pattern") + '*' ; 486 487 // find the words which match the pattern using the snippet function. The 488 // snippet function parameters mainly describe how to format the result. 489 // See http://www.sqlite.org/fts3.html#section_4_2 for details. 490 if ( sortOrder != null 491 || selection != null 492 || selectionArgs != null 493 || projection != null) { 494 throw new IllegalArgumentException( 495 "do not specify sortOrder, selection, selectionArgs, or projection" + 496 "with this query"); 497 } 498 499 cursor = db.rawQuery(SEARCH_QUERY, SEARCH_STRING); 500 break; 501 } 502 case URI_MESSAGE_ID_TO_THREAD: { 503 // Given a message ID and an indicator for SMS vs. MMS return 504 // the thread id of the corresponding thread. 505 try { 506 long id = Long.parseLong(uri.getQueryParameter("row_id")); 507 switch (Integer.parseInt(uri.getQueryParameter("table_to_use"))) { 508 case 1: // sms 509 cursor = db.query( 510 smsTable, 511 new String[] { "thread_id" }, 512 "_id=?", 513 new String[] { String.valueOf(id) }, 514 null, 515 null, 516 null); 517 break; 518 case 2: // mms 519 String mmsQuery = "SELECT thread_id " 520 + "FROM " + pduTable + ",part " 521 + "WHERE ((part.mid=" + pduTable + "._id) " 522 + "AND " + "(part._id=?))"; 523 cursor = db.rawQuery(mmsQuery, new String[] { String.valueOf(id) }); 524 break; 525 } 526 } catch (NumberFormatException ex) { 527 // ignore... return empty cursor 528 } 529 break; 530 } 531 case URI_SEARCH: { 532 if ( sortOrder != null 533 || selection != null 534 || selectionArgs != null 535 || projection != null) { 536 throw new IllegalArgumentException( 537 "do not specify sortOrder, selection, selectionArgs, or projection" + 538 "with this query"); 539 } 540 541 String searchString = uri.getQueryParameter("pattern") + "*"; 542 543 try { 544 cursor = db.rawQuery(getTextSearchQuery(smsTable, pduTable), 545 new String[] { searchString, searchString }); 546 } catch (Exception ex) { 547 Log.e(LOG_TAG, "got exception: " + ex.toString()); 548 } 549 break; 550 } 551 case URI_PENDING_MSG: { 552 String protoName = uri.getQueryParameter("protocol"); 553 String msgId = uri.getQueryParameter("message"); 554 int proto = TextUtils.isEmpty(protoName) ? -1 555 : (protoName.equals("sms") ? MmsSms.SMS_PROTO : MmsSms.MMS_PROTO); 556 557 String extraSelection = (proto != -1) ? 558 (PendingMessages.PROTO_TYPE + "=" + proto) : " 0=0 "; 559 if (!TextUtils.isEmpty(msgId)) { 560 try { 561 Long.parseLong(msgId); 562 extraSelection += " AND " + PendingMessages.MSG_ID + "=" + msgId; 563 } catch(NumberFormatException ex) { 564 Log.e(LOG_TAG, "MSG ID must be a Long."); 565 // return empty cursor 566 break; 567 } 568 } 569 if (selectionBySubIds == null) { 570 // No subscriptions associated with user, return empty cursor. 571 return emptyCursor; 572 } 573 // In PendingMessages table, SUBSCRIPTION_ID column name is pending_sub_id. 574 selectionBySubIds = "pending_" + selectionBySubIds; 575 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 576 577 String finalSelection = TextUtils.isEmpty(selection) 578 ? extraSelection : ("(" + extraSelection + ") AND " + selection); 579 String finalOrder = TextUtils.isEmpty(sortOrder) 580 ? PendingMessages.DUE_TIME : sortOrder; 581 cursor = db.query(TABLE_PENDING_MSG, null, 582 finalSelection, selectionArgs, null, null, finalOrder); 583 break; 584 } 585 case URI_UNDELIVERED_MSG: { 586 if (selectionBySubIds == null) { 587 // No subscriptions associated with user, return empty cursor. 588 return emptyCursor; 589 } 590 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 591 592 cursor = getUndeliveredMessages(projection, selection, 593 selectionArgs, sortOrder, smsTable, pduTable); 594 break; 595 } 596 case URI_DRAFT: { 597 if (selectionBySubIds == null) { 598 // No subscriptions associated with user, return empty cursor. 599 return emptyCursor; 600 } 601 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 602 603 cursor = getDraftThread(projection, selection, sortOrder, smsTable, pduTable); 604 break; 605 } 606 case URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID: { 607 long threadId; 608 try { 609 threadId = Long.parseLong(uri.getLastPathSegment()); 610 } catch (NumberFormatException e) { 611 Log.e(LOG_TAG, "Thread ID must be a long."); 612 break; 613 } 614 selection = DatabaseUtils.concatenateWhere(selection, ("thread_id=" + threadId)); 615 616 if (selectionBySubIds == null) { 617 // No subscriptions associated with user, return empty cursor. 618 return emptyCursor; 619 } 620 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 621 622 cursor = getFirstLockedMessage(projection, selection, sortOrder, 623 smsTable, pduTable); 624 break; 625 } 626 case URI_FIRST_LOCKED_MESSAGE_ALL: { 627 if (selectionBySubIds == null) { 628 // No subscriptions associated with user, return empty cursor. 629 return emptyCursor; 630 } 631 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 632 633 cursor = getFirstLockedMessage( 634 projection, selection, sortOrder, smsTable, pduTable); 635 break; 636 } 637 default: 638 throw new IllegalStateException("Unrecognized URI:" + uri); 639 } 640 641 if (cursor != null) { 642 cursor.setNotificationUri(getContext().getContentResolver(), MmsSms.CONTENT_URI); 643 } 644 return cursor; 645 } 646 647 /** 648 * Return the canonical address ID for this address. 649 */ getSingleAddressId(String address)650 private long getSingleAddressId(String address) { 651 boolean isEmail = Mms.isEmailAddress(address); 652 boolean isPhoneNumber = Mms.isPhoneNumber(address); 653 654 // We lowercase all email addresses, but not addresses that aren't numbers, because 655 // that would incorrectly turn an address such as "My Vodafone" into "my vodafone" 656 // and the thread title would be incorrect when displayed in the UI. 657 String refinedAddress = isEmail ? address.toLowerCase(Locale.ROOT) : address; 658 659 String selection = "address=?"; 660 String[] selectionArgs; 661 long retVal = -1L; 662 int minMatch = 663 getContext().getResources().getInteger( 664 com.android.internal.R.integer.config_phonenumber_compare_min_match); 665 666 if (!isPhoneNumber) { 667 selectionArgs = new String[] { refinedAddress }; 668 } else { 669 selection += " OR PHONE_NUMBERS_EQUAL(address, ?, " + 670 (mUseStrictPhoneNumberComparation ? "1)" : "0, " + minMatch + ")"); 671 selectionArgs = new String[] { refinedAddress, refinedAddress }; 672 } 673 674 Cursor cursor = null; 675 676 try { 677 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 678 cursor = db.query( 679 "canonical_addresses", ID_PROJECTION, 680 selection, selectionArgs, null, null, null); 681 682 if (cursor.getCount() == 0) { 683 // TODO (b/256992531): Currently, one sim card is set as default sms subId in work 684 // profile. Default sms subId should be updated based on user pref. 685 int subId = SmsManager.getDefaultSmsSubscriptionId(); 686 ContentValues contentValues = new ContentValues(1); 687 contentValues.put(CanonicalAddressesColumns.ADDRESS, refinedAddress); 688 contentValues.put(CanonicalAddressesColumns.SUBSCRIPTION_ID, subId); 689 690 db = mOpenHelper.getWritableDatabase(); 691 retVal = db.insert("canonical_addresses", 692 CanonicalAddressesColumns.ADDRESS, contentValues); 693 694 Log.d(LOG_TAG, "getSingleAddressId: insert new canonical_address for " + 695 /*address*/ "xxxxxx" + ", sub_id=" + subId + ", _id=" + retVal); 696 697 return retVal; 698 } 699 700 if (cursor.moveToFirst()) { 701 retVal = cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID)); 702 } 703 } finally { 704 if (cursor != null) { 705 cursor.close(); 706 } 707 } 708 709 return retVal; 710 } 711 712 /** 713 * Return the canonical address IDs for these addresses. 714 */ getAddressIds(List<String> addresses)715 private Set<Long> getAddressIds(List<String> addresses) { 716 Set<Long> result = new HashSet<Long>(addresses.size()); 717 718 for (String address : addresses) { 719 if (!address.equals(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) { 720 long id = getSingleAddressId(address); 721 if (id != -1L) { 722 result.add(id); 723 } else { 724 Log.e(LOG_TAG, "getAddressIds: address ID not found for " + address); 725 } 726 } 727 } 728 return result; 729 } 730 731 /** 732 * Return a sorted array of the given Set of Longs. 733 */ getSortedSet(Set<Long> numbers)734 private long[] getSortedSet(Set<Long> numbers) { 735 int size = numbers.size(); 736 long[] result = new long[size]; 737 int i = 0; 738 739 for (Long number : numbers) { 740 result[i++] = number; 741 } 742 743 if (size > 1) { 744 Arrays.sort(result); 745 } 746 747 return result; 748 } 749 750 /** 751 * Return a String of the numbers in the given array, in order, 752 * separated by spaces. 753 */ getSpaceSeparatedNumbers(long[] numbers)754 private String getSpaceSeparatedNumbers(long[] numbers) { 755 int size = numbers.length; 756 StringBuilder buffer = new StringBuilder(); 757 758 for (int i = 0; i < size; i++) { 759 if (i != 0) { 760 buffer.append(' '); 761 } 762 buffer.append(numbers[i]); 763 } 764 return buffer.toString(); 765 } 766 767 /** 768 * Insert a record for a new thread. 769 */ insertThread(String recipientIds, int numberOfRecipients)770 private void insertThread(String recipientIds, int numberOfRecipients) { 771 ContentValues values = new ContentValues(4); 772 773 long date = System.currentTimeMillis(); 774 values.put(ThreadsColumns.DATE, date - date % 1000); 775 values.put(ThreadsColumns.RECIPIENT_IDS, recipientIds); 776 // TODO (b/256992531): Currently, one sim card is set as default sms subId in work 777 // profile. Default sms subId should be updated based on user pref. 778 values.put(ThreadsColumns.SUBSCRIPTION_ID, SmsManager.getDefaultSmsSubscriptionId()); 779 if (numberOfRecipients > 1) { 780 values.put(Threads.TYPE, Threads.BROADCAST_THREAD); 781 } 782 values.put(ThreadsColumns.MESSAGE_COUNT, 0); 783 784 long result = mOpenHelper.getWritableDatabase().insert(TABLE_THREADS, null, values); 785 Log.d(LOG_TAG, "insertThread: created new thread_id " + result + 786 " for recipientIds " + /*recipientIds*/ "xxxxxxx"); 787 788 getContext().getContentResolver().notifyChange(MmsSms.CONTENT_URI, null, true, 789 UserHandle.USER_ALL); 790 } 791 792 private static final String THREAD_QUERY = 793 "SELECT _id FROM threads " + "WHERE recipient_ids=?"; 794 795 /** 796 * Return the thread ID for this list of 797 * recipients IDs. If no thread exists with this ID, create 798 * one and return it. Callers should always use 799 * Threads.getThreadId to access this information. 800 */ getThreadId(List<String> recipients)801 private synchronized Cursor getThreadId(List<String> recipients) { 802 Set<Long> addressIds = getAddressIds(recipients); 803 String recipientIds = ""; 804 805 if (addressIds.size() == 0) { 806 Log.e(LOG_TAG, "getThreadId: NO receipients specified -- NOT creating thread", 807 new Exception()); 808 TelephonyStatsLog.write( 809 TelephonyStatsLog.MMS_SMS_PROVIDER_GET_THREAD_ID_FAILED, 810 TelephonyStatsLog 811 .MMS_SMS_PROVIDER_GET_THREAD_ID_FAILED__FAILURE_CODE__FAILURE_NO_RECIPIENTS); 812 return null; 813 } else if (addressIds.size() == 1) { 814 // optimize for size==1, which should be most of the cases 815 for (Long addressId : addressIds) { 816 recipientIds = Long.toString(addressId); 817 } 818 } else { 819 recipientIds = getSpaceSeparatedNumbers(getSortedSet(addressIds)); 820 } 821 822 if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) { 823 Log.d(LOG_TAG, "getThreadId: recipientIds (selectionArgs) =" + 824 /*recipientIds*/ "xxxxxxx"); 825 } 826 827 String[] selectionArgs = new String[] { recipientIds }; 828 829 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 830 db.beginTransaction(); 831 Cursor cursor = null; 832 try { 833 // Find the thread with the given recipients 834 cursor = db.rawQuery(THREAD_QUERY, selectionArgs); 835 836 if (cursor.getCount() == 0) { 837 // No thread with those recipients exists, so create the thread. 838 cursor.close(); 839 840 Log.d(LOG_TAG, "getThreadId: create new thread_id for recipients " + 841 /*recipients*/ "xxxxxxxx"); 842 insertThread(recipientIds, recipients.size()); 843 844 // The thread was just created, now find it and return it. 845 cursor = db.rawQuery(THREAD_QUERY, selectionArgs); 846 } 847 db.setTransactionSuccessful(); 848 } catch (Throwable ex) { 849 Log.e(LOG_TAG, ex.getMessage(), ex); 850 TelephonyStatsLog.write( 851 TelephonyStatsLog.MMS_SMS_PROVIDER_GET_THREAD_ID_FAILED, 852 FAILURE_FIND_OR_CREATE_THREAD_ID_SQL); 853 } finally { 854 db.endTransaction(); 855 } 856 857 if (cursor != null && cursor.getCount() > 1) { 858 Log.w(LOG_TAG, "getThreadId: why is cursorCount=" + cursor.getCount()); 859 TelephonyStatsLog.write( 860 TelephonyStatsLog.MMS_SMS_PROVIDER_GET_THREAD_ID_FAILED, 861 MULTIPLE_THREAD_IDS_FOUND); 862 } 863 return cursor; 864 } 865 concatSelections(String selection1, String selection2)866 private static String concatSelections(String selection1, String selection2) { 867 if (TextUtils.isEmpty(selection1)) { 868 return selection2; 869 } else if (TextUtils.isEmpty(selection2)) { 870 return selection1; 871 } else { 872 return selection1 + " AND " + selection2; 873 } 874 } 875 876 /** 877 * If a null projection is given, return the union of all columns 878 * in both the MMS and SMS messages tables. Otherwise, return the 879 * given projection. 880 */ handleNullMessageProjection( String[] projection)881 private static String[] handleNullMessageProjection( 882 String[] projection) { 883 return projection == null ? UNION_COLUMNS : projection; 884 } 885 886 /** 887 * If a null projection is given, return the set of all columns in 888 * the threads table. Otherwise, return the given projection. 889 */ handleNullThreadsProjection( String[] projection)890 private static String[] handleNullThreadsProjection( 891 String[] projection) { 892 return projection == null ? THREADS_COLUMNS : projection; 893 } 894 895 /** 896 * If a null sort order is given, return "normalized_date ASC". 897 * Otherwise, return the given sort order. 898 */ handleNullSortOrder(String sortOrder)899 private static String handleNullSortOrder (String sortOrder) { 900 return sortOrder == null ? "normalized_date ASC" : sortOrder; 901 } 902 903 /** 904 * Return existing threads in the database. 905 */ getSimpleConversations(String[] projection, String selection, String[] selectionArgs, String sortOrder)906 private Cursor getSimpleConversations(String[] projection, String selection, 907 String[] selectionArgs, String sortOrder) { 908 return mOpenHelper.getReadableDatabase().query(TABLE_THREADS, projection, 909 selection, selectionArgs, null, null, " date DESC"); 910 } 911 912 /** 913 * Return the thread which has draft in both MMS and SMS. 914 * 915 * Use this query: 916 * 917 * SELECT ... 918 * FROM (SELECT _id, thread_id, ... 919 * FROM pdu 920 * WHERE msg_box = 3 AND ... 921 * UNION 922 * SELECT _id, thread_id, ... 923 * FROM sms 924 * WHERE type = 3 AND ... 925 * ) 926 * ; 927 */ getDraftThread(String[] projection, String selection, String sortOrder, String smsTable, String pduTable)928 private Cursor getDraftThread(String[] projection, String selection, 929 String sortOrder, String smsTable, String pduTable) { 930 String[] innerProjection = new String[] {BaseColumns._ID, Conversations.THREAD_ID}; 931 SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); 932 SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); 933 934 mmsQueryBuilder.setTables(pduTable); 935 smsQueryBuilder.setTables(smsTable); 936 937 String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery( 938 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerProjection, 939 MMS_COLUMNS, 1, "mms", 940 concatSelections(selection, Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_DRAFTS), 941 null, null); 942 String smsSubQuery = smsQueryBuilder.buildUnionSubQuery( 943 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerProjection, 944 SMS_COLUMNS, 1, "sms", 945 concatSelections(selection, Sms.TYPE + "=" + Sms.MESSAGE_TYPE_DRAFT), 946 null, null); 947 SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder(); 948 949 unionQueryBuilder.setDistinct(true); 950 951 String unionQuery = unionQueryBuilder.buildUnionQuery( 952 new String[] { mmsSubQuery, smsSubQuery }, null, null); 953 954 SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder(); 955 956 outerQueryBuilder.setTables("(" + unionQuery + ")"); 957 958 String outerQuery = outerQueryBuilder.buildQuery( 959 projection, null, null, null, sortOrder, null); 960 961 return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY); 962 } 963 964 /** 965 * Return the most recent message in each conversation in both MMS 966 * and SMS. 967 * 968 * Use this query: 969 * 970 * SELECT ... 971 * FROM (SELECT thread_id AS tid, date * 1000 AS normalized_date, ... 972 * FROM pdu 973 * WHERE msg_box != 3 AND ... 974 * GROUP BY thread_id 975 * HAVING date = MAX(date) 976 * UNION 977 * SELECT thread_id AS tid, date AS normalized_date, ... 978 * FROM sms 979 * WHERE ... 980 * GROUP BY thread_id 981 * HAVING date = MAX(date)) 982 * GROUP BY tid 983 * HAVING normalized_date = MAX(normalized_date); 984 * 985 * The msg_box != 3 comparisons ensure that we don't include draft 986 * messages. 987 */ getConversations(String[] projection, String selection, String sortOrder, String smsTable, String pduTable)988 private Cursor getConversations(String[] projection, String selection, 989 String sortOrder, String smsTable, String pduTable) { 990 SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); 991 SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); 992 993 mmsQueryBuilder.setTables(pduTable); 994 smsQueryBuilder.setTables(smsTable); 995 996 String[] columns = handleNullMessageProjection(projection); 997 String[] innerMmsProjection = makeProjectionWithDateAndThreadId( 998 UNION_COLUMNS, 1000); 999 String[] innerSmsProjection = makeProjectionWithDateAndThreadId( 1000 UNION_COLUMNS, 1); 1001 String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery( 1002 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection, 1003 MMS_COLUMNS, 1, "mms", 1004 concatSelections(selection, MMS_CONVERSATION_CONSTRAINT), 1005 "thread_id", "date = MAX(date)"); 1006 String smsSubQuery = smsQueryBuilder.buildUnionSubQuery( 1007 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection, 1008 SMS_COLUMNS, 1, "sms", 1009 concatSelections(selection, SMS_CONVERSATION_CONSTRAINT), 1010 "thread_id", "date = MAX(date)"); 1011 SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder(); 1012 1013 unionQueryBuilder.setDistinct(true); 1014 1015 String unionQuery = unionQueryBuilder.buildUnionQuery( 1016 new String[] { mmsSubQuery, smsSubQuery }, null, null); 1017 1018 SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder(); 1019 1020 outerQueryBuilder.setTables("(" + unionQuery + ")"); 1021 1022 String outerQuery = outerQueryBuilder.buildQuery( 1023 columns, null, "tid", 1024 "normalized_date = MAX(normalized_date)", sortOrder, null); 1025 1026 return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY); 1027 } 1028 1029 /** 1030 * Return the first locked message found in the union of MMS 1031 * and SMS messages. 1032 * 1033 * Use this query: 1034 * 1035 * SELECT _id FROM pdu GROUP BY _id HAVING locked=1 UNION SELECT _id FROM sms GROUP 1036 * BY _id HAVING locked=1 LIMIT 1 1037 * 1038 * We limit by 1 because we're only interested in knowing if 1039 * there is *any* locked message, not the actual messages themselves. 1040 */ getFirstLockedMessage(String[] projection, String selection, String sortOrder, String smsTable, String pduTable)1041 private Cursor getFirstLockedMessage(String[] projection, String selection, 1042 String sortOrder, String smsTable, String pduTable) { 1043 SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); 1044 SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); 1045 1046 mmsQueryBuilder.setTables(pduTable); 1047 smsQueryBuilder.setTables(smsTable); 1048 1049 String[] idColumn = new String[] { BaseColumns._ID }; 1050 1051 // NOTE: buildUnionSubQuery *ignores* selectionArgs 1052 String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery( 1053 MmsSms.TYPE_DISCRIMINATOR_COLUMN, idColumn, 1054 null, 1, "mms", 1055 selection, 1056 BaseColumns._ID, "locked=1"); 1057 1058 String smsSubQuery = smsQueryBuilder.buildUnionSubQuery( 1059 MmsSms.TYPE_DISCRIMINATOR_COLUMN, idColumn, 1060 null, 1, "sms", 1061 selection, 1062 BaseColumns._ID, "locked=1"); 1063 1064 SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder(); 1065 1066 unionQueryBuilder.setDistinct(true); 1067 1068 String unionQuery = unionQueryBuilder.buildUnionQuery( 1069 new String[] { mmsSubQuery, smsSubQuery }, null, "1"); 1070 1071 Cursor cursor = mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY); 1072 1073 if (DEBUG) { 1074 Log.v("MmsSmsProvider", "getFirstLockedMessage query: " + unionQuery); 1075 Log.v("MmsSmsProvider", "cursor count: " + cursor.getCount()); 1076 } 1077 return cursor; 1078 } 1079 1080 /** 1081 * Return every message in each conversation in both MMS 1082 * and SMS. 1083 */ getCompleteConversations(String[] projection, String selection, String sortOrder, String smsTable, String pduTable)1084 private Cursor getCompleteConversations(String[] projection, 1085 String selection, String sortOrder, String smsTable, String pduTable) { 1086 String unionQuery = buildConversationQuery(projection, selection, sortOrder, smsTable, 1087 pduTable); 1088 1089 return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY); 1090 } 1091 1092 /** 1093 * Add normalized date and thread_id to the list of columns for an 1094 * inner projection. This is necessary so that the outer query 1095 * can have access to these columns even if the caller hasn't 1096 * requested them in the result. 1097 */ makeProjectionWithDateAndThreadId( String[] projection, int dateMultiple)1098 private String[] makeProjectionWithDateAndThreadId( 1099 String[] projection, int dateMultiple) { 1100 int projectionSize = projection.length; 1101 String[] result = new String[projectionSize + 2]; 1102 1103 result[0] = "thread_id AS tid"; 1104 result[1] = "date * " + dateMultiple + " AS normalized_date"; 1105 for (int i = 0; i < projectionSize; i++) { 1106 result[i + 2] = projection[i]; 1107 } 1108 return result; 1109 } 1110 1111 /** 1112 * Return the union of MMS and SMS messages for this thread ID. 1113 */ getConversationMessages( String threadIdString, String[] projection, String selection, String sortOrder, String smsTable, String pduTable)1114 private Cursor getConversationMessages( 1115 String threadIdString, String[] projection, String selection, 1116 String sortOrder, String smsTable, String pduTable) { 1117 try { 1118 Long.parseLong(threadIdString); 1119 } catch (NumberFormatException exception) { 1120 Log.e(LOG_TAG, "Thread ID must be a Long."); 1121 return null; 1122 } 1123 1124 String finalSelection = concatSelections( 1125 selection, "thread_id = " + threadIdString); 1126 String unionQuery = buildConversationQuery(projection, finalSelection, sortOrder, smsTable, 1127 pduTable); 1128 1129 return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY); 1130 } 1131 1132 /** 1133 * Return the union of MMS and SMS messages whose recipients 1134 * included this phone number. 1135 * 1136 * Use this query: 1137 * 1138 * SELECT ... 1139 * FROM pdu, (SELECT msg_id AS address_msg_id 1140 * FROM addr 1141 * WHERE (address='<phoneNumber>' OR 1142 * PHONE_NUMBERS_EQUAL(addr.address, '<phoneNumber>', 1/0, none/minMatch))) 1143 * AS matching_addresses 1144 * WHERE pdu._id = matching_addresses.address_msg_id 1145 * UNION 1146 * SELECT ... 1147 * FROM sms 1148 * WHERE (address='<phoneNumber>' OR 1149 * PHONE_NUMBERS_EQUAL(sms.address, '<phoneNumber>', 1/0, none/minMatch)); 1150 */ getMessagesByPhoneNumber( String phoneNumber, String[] projection, String selection, String sortOrder, String smsTable, String pduTable)1151 private Cursor getMessagesByPhoneNumber( 1152 String phoneNumber, String[] projection, String selection, 1153 String sortOrder, String smsTable, String pduTable) { 1154 int minMatch = 1155 getContext().getResources().getInteger( 1156 com.android.internal.R.integer.config_phonenumber_compare_min_match); 1157 String finalMmsSelection = 1158 concatSelections( 1159 selection, 1160 pduTable + "._id = matching_addresses.address_msg_id"); 1161 String finalSmsSelection = 1162 concatSelections( 1163 selection, 1164 "(address=? OR PHONE_NUMBERS_EQUAL(address, ?" + 1165 (mUseStrictPhoneNumberComparation ? ", 1))" : ", 0, " + minMatch + "))")); 1166 SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); 1167 SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); 1168 1169 mmsQueryBuilder.setDistinct(true); 1170 smsQueryBuilder.setDistinct(true); 1171 mmsQueryBuilder.setTables( 1172 pduTable + 1173 ", (SELECT msg_id AS address_msg_id " + 1174 "FROM addr WHERE (address=?" + 1175 " OR PHONE_NUMBERS_EQUAL(addr.address, ?" + 1176 (mUseStrictPhoneNumberComparation ? ", 1))) " : ", 0, " + minMatch + "))) ") + 1177 "AS matching_addresses"); 1178 smsQueryBuilder.setTables(smsTable); 1179 1180 String[] columns = handleNullMessageProjection(projection); 1181 String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery( 1182 MmsSms.TYPE_DISCRIMINATOR_COLUMN, columns, MMS_COLUMNS, 1183 0, "mms", finalMmsSelection, null, null); 1184 String smsSubQuery = smsQueryBuilder.buildUnionSubQuery( 1185 MmsSms.TYPE_DISCRIMINATOR_COLUMN, columns, SMS_COLUMNS, 1186 0, "sms", finalSmsSelection, null, null); 1187 SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder(); 1188 1189 unionQueryBuilder.setDistinct(true); 1190 1191 String unionQuery = unionQueryBuilder.buildUnionQuery( 1192 new String[] { mmsSubQuery, smsSubQuery }, sortOrder, null); 1193 1194 return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, 1195 new String[] { phoneNumber, phoneNumber, phoneNumber, phoneNumber }); 1196 } 1197 1198 /** 1199 * Return the conversation of certain thread ID. 1200 */ getConversationById( String threadIdString, String[] projection, String selection, String[] selectionArgs, String sortOrder)1201 private Cursor getConversationById( 1202 String threadIdString, String[] projection, String selection, 1203 String[] selectionArgs, String sortOrder) { 1204 try { 1205 Long.parseLong(threadIdString); 1206 } catch (NumberFormatException exception) { 1207 Log.e(LOG_TAG, "Thread ID must be a Long."); 1208 return null; 1209 } 1210 1211 String extraSelection = "_id=" + threadIdString; 1212 String finalSelection = concatSelections(selection, extraSelection); 1213 SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); 1214 String[] columns = handleNullThreadsProjection(projection); 1215 1216 queryBuilder.setDistinct(true); 1217 queryBuilder.setTables(TABLE_THREADS); 1218 return queryBuilder.query( 1219 mOpenHelper.getReadableDatabase(), columns, finalSelection, 1220 selectionArgs, sortOrder, null, null); 1221 } 1222 joinPduAndPendingMsgTables(String pduTable)1223 private static String joinPduAndPendingMsgTables(String pduTable) { 1224 return pduTable + " LEFT JOIN " + TABLE_PENDING_MSG 1225 + " ON " + pduTable + "._id = pending_msgs.msg_id"; 1226 } 1227 createMmsProjection(String[] old, String pduTable)1228 private static String[] createMmsProjection(String[] old, String pduTable) { 1229 String[] newProjection = new String[old.length]; 1230 for (int i = 0; i < old.length; i++) { 1231 if (old[i].equals(BaseColumns._ID)) { 1232 newProjection[i] = pduTable + "._id"; 1233 } else { 1234 newProjection[i] = old[i]; 1235 } 1236 } 1237 return newProjection; 1238 } 1239 getUndeliveredMessages( String[] projection, String selection, String[] selectionArgs, String sortOrder, String smsTable, String pduTable)1240 private Cursor getUndeliveredMessages( 1241 String[] projection, String selection, String[] selectionArgs, 1242 String sortOrder, String smsTable, String pduTable) { 1243 String[] mmsProjection = createMmsProjection(projection, pduTable); 1244 1245 SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); 1246 SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); 1247 1248 mmsQueryBuilder.setTables(joinPduAndPendingMsgTables(pduTable)); 1249 smsQueryBuilder.setTables(smsTable); 1250 1251 String finalMmsSelection = concatSelections( 1252 selection, Mms.MESSAGE_BOX + " = " + Mms.MESSAGE_BOX_OUTBOX); 1253 String finalSmsSelection = concatSelections( 1254 selection, "(" + Sms.TYPE + " = " + Sms.MESSAGE_TYPE_OUTBOX 1255 + " OR " + Sms.TYPE + " = " + Sms.MESSAGE_TYPE_FAILED 1256 + " OR " + Sms.TYPE + " = " + Sms.MESSAGE_TYPE_QUEUED + ")"); 1257 1258 String[] smsColumns = handleNullMessageProjection(projection); 1259 String[] mmsColumns = handleNullMessageProjection(mmsProjection); 1260 String[] innerMmsProjection = makeProjectionWithDateAndThreadId( 1261 mmsColumns, 1000); 1262 String[] innerSmsProjection = makeProjectionWithDateAndThreadId( 1263 smsColumns, 1); 1264 1265 Set<String> columnsPresentInTable = new HashSet<String>(MMS_COLUMNS); 1266 columnsPresentInTable.add(pduTable + "._id"); 1267 columnsPresentInTable.add(PendingMessages.ERROR_TYPE); 1268 String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery( 1269 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection, 1270 columnsPresentInTable, 1, "mms", finalMmsSelection, 1271 null, null); 1272 String smsSubQuery = smsQueryBuilder.buildUnionSubQuery( 1273 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection, 1274 SMS_COLUMNS, 1, "sms", finalSmsSelection, 1275 null, null); 1276 SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder(); 1277 1278 unionQueryBuilder.setDistinct(true); 1279 1280 String unionQuery = unionQueryBuilder.buildUnionQuery( 1281 new String[] { smsSubQuery, mmsSubQuery }, null, null); 1282 1283 SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder(); 1284 1285 outerQueryBuilder.setTables("(" + unionQuery + ")"); 1286 1287 String outerQuery = outerQueryBuilder.buildQuery( 1288 smsColumns, null, null, null, sortOrder, null); 1289 1290 return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY); 1291 } 1292 1293 /** 1294 * Add normalized date to the list of columns for an inner 1295 * projection. 1296 */ makeProjectionWithNormalizedDate( String[] projection, int dateMultiple)1297 private static String[] makeProjectionWithNormalizedDate( 1298 String[] projection, int dateMultiple) { 1299 int projectionSize = projection.length; 1300 String[] result = new String[projectionSize + 1]; 1301 1302 result[0] = "date * " + dateMultiple + " AS normalized_date"; 1303 System.arraycopy(projection, 0, result, 1, projectionSize); 1304 return result; 1305 } 1306 buildConversationQuery(String[] projection, String selection, String sortOrder, String smsTable, String pduTable)1307 private static String buildConversationQuery(String[] projection, 1308 String selection, String sortOrder, String smsTable, String pduTable) { 1309 String[] mmsProjection = createMmsProjection(projection, pduTable); 1310 1311 SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); 1312 SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); 1313 1314 mmsQueryBuilder.setDistinct(true); 1315 smsQueryBuilder.setDistinct(true); 1316 mmsQueryBuilder.setTables(joinPduAndPendingMsgTables(pduTable)); 1317 smsQueryBuilder.setTables(smsTable); 1318 1319 String[] smsColumns = handleNullMessageProjection(projection); 1320 String[] mmsColumns = handleNullMessageProjection(mmsProjection); 1321 String[] innerMmsProjection = makeProjectionWithNormalizedDate(mmsColumns, 1000); 1322 String[] innerSmsProjection = makeProjectionWithNormalizedDate(smsColumns, 1); 1323 1324 Set<String> columnsPresentInTable = new HashSet<String>(MMS_COLUMNS); 1325 columnsPresentInTable.add(pduTable + "._id"); 1326 columnsPresentInTable.add(PendingMessages.ERROR_TYPE); 1327 1328 String mmsSelection = concatSelections(selection, 1329 Mms.MESSAGE_BOX + " != " + Mms.MESSAGE_BOX_DRAFTS); 1330 String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery( 1331 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection, 1332 columnsPresentInTable, 0, "mms", 1333 concatSelections(mmsSelection, MMS_CONVERSATION_CONSTRAINT), 1334 null, null); 1335 String smsSubQuery = smsQueryBuilder.buildUnionSubQuery( 1336 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection, SMS_COLUMNS, 1337 0, "sms", concatSelections(selection, SMS_CONVERSATION_CONSTRAINT), 1338 null, null); 1339 SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder(); 1340 1341 unionQueryBuilder.setDistinct(true); 1342 1343 String unionQuery = unionQueryBuilder.buildUnionQuery( 1344 new String[] { smsSubQuery, mmsSubQuery }, 1345 handleNullSortOrder(sortOrder), null); 1346 1347 SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder(); 1348 1349 outerQueryBuilder.setTables("(" + unionQuery + ")"); 1350 1351 return outerQueryBuilder.buildQuery( 1352 smsColumns, null, null, null, sortOrder, null); 1353 } 1354 1355 @Override getType(Uri uri)1356 public String getType(Uri uri) { 1357 return VND_ANDROID_DIR_MMS_SMS; 1358 } 1359 1360 @Override delete(Uri uri, String selection, String[] selectionArgs)1361 public int delete(Uri uri, String selection, 1362 String[] selectionArgs) { 1363 final UserHandle callerUserHandle = Binder.getCallingUserHandle(); 1364 String selectionBySubIds; 1365 final long token = Binder.clearCallingIdentity(); 1366 try { 1367 // Filter MMS/SMS based on subId 1368 selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(), callerUserHandle); 1369 } finally { 1370 Binder.restoreCallingIdentity(token); 1371 } 1372 1373 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1374 Context context = getContext(); 1375 int affectedRows = 0; 1376 1377 switch(URI_MATCHER.match(uri)) { 1378 case URI_CONVERSATIONS_MESSAGES: 1379 long threadId; 1380 try { 1381 threadId = Long.parseLong(uri.getLastPathSegment()); 1382 } catch (NumberFormatException e) { 1383 Log.e(LOG_TAG, "Thread ID must be a long."); 1384 break; 1385 } 1386 1387 if (selectionBySubIds == null) { 1388 // No subscriptions associated with user, return 0. 1389 return 0; 1390 } 1391 selection = DatabaseUtils.concatenateWhere(selectionBySubIds, selection); 1392 1393 affectedRows = deleteConversation(uri, selection, selectionArgs); 1394 MmsSmsDatabaseHelper.updateThread(db, threadId); 1395 break; 1396 case URI_CONVERSATIONS: 1397 if (selectionBySubIds == null) { 1398 // No subscriptions associated with user, return 0. 1399 return 0; 1400 } 1401 selection = DatabaseUtils.concatenateWhere(selectionBySubIds, selection); 1402 1403 affectedRows = MmsProvider.deleteMessages(context, db, 1404 selection, selectionArgs, uri) 1405 + db.delete("sms", selection, selectionArgs); 1406 // Intentionally don't pass the selection variable to updateThreads. 1407 // When we pass in "locked=0" there, the thread will get excluded from 1408 // the selection and not get updated. 1409 MmsSmsDatabaseHelper.updateThreads(db, null, null); 1410 break; 1411 case URI_OBSOLETE_THREADS: 1412 affectedRows = db.delete(TABLE_THREADS, 1413 "_id NOT IN (SELECT DISTINCT thread_id FROM sms where thread_id NOT NULL " + 1414 "UNION SELECT DISTINCT thread_id FROM pdu where thread_id NOT NULL)", null); 1415 break; 1416 default: 1417 throw new UnsupportedOperationException(NO_DELETES_INSERTS_OR_UPDATES + uri); 1418 } 1419 1420 if (affectedRows > 0) { 1421 context.getContentResolver().notifyChange(MmsSms.CONTENT_URI, null, true, 1422 UserHandle.USER_ALL); 1423 } 1424 return affectedRows; 1425 } 1426 1427 /** 1428 * Delete the conversation with the given thread ID. 1429 */ deleteConversation(Uri uri, String selection, String[] selectionArgs)1430 private int deleteConversation(Uri uri, String selection, String[] selectionArgs) { 1431 String threadId = uri.getLastPathSegment(); 1432 1433 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1434 String finalSelection = concatSelections(selection, "thread_id = " + threadId); 1435 return MmsProvider.deleteMessages(getContext(), db, finalSelection, 1436 selectionArgs, uri) 1437 + db.delete("sms", finalSelection, selectionArgs); 1438 } 1439 1440 @Override insert(Uri uri, ContentValues values)1441 public Uri insert(Uri uri, ContentValues values) { 1442 final UserHandle callerUserHandle = Binder.getCallingUserHandle(); 1443 final int callerUid = Binder.getCallingUid(); 1444 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1445 int matchIndex = URI_MATCHER.match(uri); 1446 1447 // TODO (b/256992531): Currently, one sim card is set as default sms subId in work 1448 // profile. Default sms subId should be updated based on user pref. 1449 int defaultSmsSubId = SmsManager.getDefaultSmsSubscriptionId(); 1450 if (matchIndex == URI_PENDING_MSG) { 1451 int subId; 1452 if (values.containsKey(PendingMessages.SUBSCRIPTION_ID)) { 1453 subId = values.getAsInteger(PendingMessages.SUBSCRIPTION_ID); 1454 } else { 1455 subId = defaultSmsSubId; 1456 if (SubscriptionManager.isValidSubscriptionId(subId)) { 1457 values.put(PendingMessages.SUBSCRIPTION_ID, subId); 1458 } 1459 } 1460 1461 if (!TelephonyPermissions 1462 .checkSubscriptionAssociatedWithUser(getContext(), subId, callerUserHandle)) { 1463 TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(getContext(), subId, 1464 callerUid, getCallingPackage()); 1465 return null; 1466 } 1467 1468 long rowId = db.insert(TABLE_PENDING_MSG, null, values); 1469 return uri.buildUpon().appendPath(Long.toString(rowId)).build(); 1470 } else if (matchIndex == URI_CANONICAL_ADDRESS) { 1471 if (!values.containsKey(CanonicalAddressesColumns.SUBSCRIPTION_ID)) { 1472 if (SubscriptionManager.isValidSubscriptionId(defaultSmsSubId)) { 1473 values.put(CanonicalAddressesColumns.SUBSCRIPTION_ID, defaultSmsSubId); 1474 } 1475 } 1476 1477 long rowId = db.insert(TABLE_CANONICAL_ADDRESSES, null, values); 1478 return uri.buildUpon().appendPath(Long.toString(rowId)).build(); 1479 } 1480 throw new UnsupportedOperationException(NO_DELETES_INSERTS_OR_UPDATES + uri); 1481 } 1482 1483 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)1484 public int update(Uri uri, ContentValues values, 1485 String selection, String[] selectionArgs) { 1486 final int callerUid = Binder.getCallingUid(); 1487 final UserHandle callerUserHandle = Binder.getCallingUserHandle(); 1488 final String callerPkg = getCallingPackage(); 1489 1490 String selectionBySubIds; 1491 final long token = Binder.clearCallingIdentity(); 1492 try { 1493 // Filter MMS/SMS based on subId. 1494 selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(), callerUserHandle); 1495 } finally { 1496 Binder.restoreCallingIdentity(token); 1497 } 1498 1499 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1500 int affectedRows = 0; 1501 switch(URI_MATCHER.match(uri)) { 1502 case URI_CONVERSATIONS_MESSAGES: 1503 if (selectionBySubIds == null) { 1504 // No subscriptions associated with user, return 0. 1505 return 0; 1506 } 1507 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 1508 1509 String threadIdString = uri.getPathSegments().get(1); 1510 affectedRows = updateConversation(threadIdString, values, 1511 selection, selectionArgs, callerUid, callerPkg); 1512 break; 1513 1514 case URI_PENDING_MSG: 1515 if (selectionBySubIds == null) { 1516 // No subscriptions associated with user, return 0. 1517 return 0; 1518 } 1519 // In PendingMessages table, SUBSCRIPTION_ID column name is pending_sub_id. 1520 selectionBySubIds = "pending_" + selectionBySubIds; 1521 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 1522 1523 affectedRows = db.update(TABLE_PENDING_MSG, values, selection, null); 1524 break; 1525 1526 case URI_CANONICAL_ADDRESS: { 1527 if (selectionBySubIds == null) { 1528 // No subscriptions associated with user, return 0. 1529 return 0; 1530 } 1531 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 1532 1533 String extraSelection = "_id=" + uri.getPathSegments().get(1); 1534 String finalSelection = TextUtils.isEmpty(selection) 1535 ? extraSelection : extraSelection + " AND " + selection; 1536 1537 affectedRows = db.update(TABLE_CANONICAL_ADDRESSES, values, finalSelection, null); 1538 break; 1539 } 1540 1541 case URI_CONVERSATIONS: { 1542 if (selectionBySubIds == null) { 1543 // No subscriptions associated with user, return 0. 1544 return 0; 1545 } 1546 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 1547 1548 final ContentValues finalValues = new ContentValues(1); 1549 if (values.containsKey(Threads.ARCHIVED)) { 1550 // Only allow update archived 1551 finalValues.put(Threads.ARCHIVED, values.getAsBoolean(Threads.ARCHIVED)); 1552 } 1553 affectedRows = db.update(TABLE_THREADS, finalValues, selection, selectionArgs); 1554 break; 1555 } 1556 1557 default: 1558 throw new UnsupportedOperationException( 1559 NO_DELETES_INSERTS_OR_UPDATES + uri); 1560 } 1561 1562 if (affectedRows > 0) { 1563 getContext().getContentResolver().notifyChange( 1564 MmsSms.CONTENT_URI, null, true, UserHandle.USER_ALL); 1565 } 1566 return affectedRows; 1567 } 1568 updateConversation(String threadIdString, ContentValues values, String selection, String[] selectionArgs, int callerUid, String callerPkg)1569 private int updateConversation(String threadIdString, ContentValues values, String selection, 1570 String[] selectionArgs, int callerUid, String callerPkg) { 1571 try { 1572 Long.parseLong(threadIdString); 1573 } catch (NumberFormatException exception) { 1574 Log.e(LOG_TAG, "Thread ID must be a Long."); 1575 return 0; 1576 1577 } 1578 if (ProviderUtil.shouldRemoveCreator(values, callerUid)) { 1579 // CREATOR should not be changed by non-SYSTEM/PHONE apps 1580 Log.w(LOG_TAG, callerPkg + " tries to update CREATOR"); 1581 // Sms.CREATOR and Mms.CREATOR are same. But let's do this 1582 // twice in case the names may differ in the future 1583 values.remove(Sms.CREATOR); 1584 values.remove(Mms.CREATOR); 1585 } 1586 1587 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1588 String finalSelection = concatSelections(selection, "thread_id=" + threadIdString); 1589 return db.update(MmsProvider.TABLE_PDU, values, finalSelection, selectionArgs) 1590 + db.update("sms", values, finalSelection, selectionArgs); 1591 } 1592 1593 /** 1594 * Construct Sets of Strings containing exactly the columns 1595 * present in each table. We will use this when constructing 1596 * UNION queries across the MMS and SMS tables. 1597 */ initializeColumnSets()1598 private static void initializeColumnSets() { 1599 int commonColumnCount = MMS_SMS_COLUMNS.length; 1600 int mmsOnlyColumnCount = MMS_ONLY_COLUMNS.length; 1601 int smsOnlyColumnCount = SMS_ONLY_COLUMNS.length; 1602 Set<String> unionColumns = new HashSet<String>(); 1603 1604 for (int i = 0; i < commonColumnCount; i++) { 1605 MMS_COLUMNS.add(MMS_SMS_COLUMNS[i]); 1606 SMS_COLUMNS.add(MMS_SMS_COLUMNS[i]); 1607 unionColumns.add(MMS_SMS_COLUMNS[i]); 1608 } 1609 for (int i = 0; i < mmsOnlyColumnCount; i++) { 1610 MMS_COLUMNS.add(MMS_ONLY_COLUMNS[i]); 1611 unionColumns.add(MMS_ONLY_COLUMNS[i]); 1612 } 1613 for (int i = 0; i < smsOnlyColumnCount; i++) { 1614 SMS_COLUMNS.add(SMS_ONLY_COLUMNS[i]); 1615 unionColumns.add(SMS_ONLY_COLUMNS[i]); 1616 } 1617 1618 int i = 0; 1619 for (String columnName : unionColumns) { 1620 UNION_COLUMNS[i++] = columnName; 1621 } 1622 } 1623 1624 @Override dump(FileDescriptor fd, PrintWriter writer, String[] args)1625 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 1626 // Dump default SMS app 1627 String defaultSmsApp = Telephony.Sms.getDefaultSmsPackage(getContext()); 1628 if (TextUtils.isEmpty(defaultSmsApp)) { 1629 defaultSmsApp = "None"; 1630 } 1631 writer.println("Default SMS app: " + defaultSmsApp); 1632 } 1633 1634 @Override call(String method, String arg, Bundle extras)1635 public Bundle call(String method, String arg, Bundle extras) { 1636 if (ProviderUtil.isAccessRestricted( 1637 getContext(), getCallingPackage(), Binder.getCallingUid())) { 1638 return null; 1639 } 1640 if (METHOD_IS_RESTORING.equals(method)) { 1641 Bundle result = new Bundle(); 1642 result.putBoolean(IS_RESTORING_KEY, TelephonyBackupAgent.getIsRestoring()); 1643 return result; 1644 } else if (METHOD_GARBAGE_COLLECT.equals(method)) { 1645 Bundle result = new Bundle(); 1646 boolean doDelete = TextUtils.equals(DO_DELETE, arg); 1647 MmsPartsCleanup.cleanupDanglingParts(getContext(), doDelete, result); 1648 return result; 1649 } 1650 Log.w(LOG_TAG, "Ignored unsupported " + method + " call"); 1651 return null; 1652 } 1653 } 1654