1 /* 2 * Copyright (C) 2006 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.annotation.NonNull; 20 import android.app.AppOpsManager; 21 import android.content.BroadcastReceiver; 22 import android.content.ContentProvider; 23 import android.content.ContentResolver; 24 import android.content.ContentValues; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.UriMatcher; 29 import android.database.Cursor; 30 import android.database.DatabaseUtils; 31 import android.database.MatrixCursor; 32 import android.database.sqlite.SQLiteDatabase; 33 import android.database.sqlite.SQLiteOpenHelper; 34 import android.database.sqlite.SQLiteQueryBuilder; 35 import android.net.Uri; 36 import android.os.Binder; 37 import android.os.UserHandle; 38 import android.os.UserManager; 39 import android.provider.Contacts; 40 import android.provider.Telephony; 41 import android.provider.Telephony.MmsSms; 42 import android.provider.Telephony.Sms; 43 import android.provider.Telephony.Threads; 44 import android.telephony.SmsManager; 45 import android.telephony.SmsMessage; 46 import android.telephony.SubscriptionManager; 47 import android.text.TextUtils; 48 import android.util.Log; 49 50 import com.android.internal.annotations.VisibleForTesting; 51 import com.android.internal.telephony.TelephonyPermissions; 52 import com.android.internal.telephony.util.TelephonyUtils; 53 54 import java.util.HashMap; 55 import java.util.List; 56 57 public class SmsProvider extends ContentProvider { 58 /* No response constant from SmsResponse */ 59 static final int NO_ERROR_CODE = -1; 60 61 private static final Uri NOTIFICATION_URI = Uri.parse("content://sms"); 62 private static final Uri ICC_URI = Uri.parse("content://sms/icc"); 63 private static final Uri ICC_SUBID_URI = Uri.parse("content://sms/icc_subId"); 64 static final String TABLE_SMS = "sms"; 65 static final String TABLE_RAW = "raw"; 66 static final String TABLE_ATTACHMENTS = "attachments"; 67 static final String TABLE_CANONICAL_ADDRESSES = "canonical_addresses"; 68 static final String TABLE_SR_PENDING = "sr_pending"; 69 private static final String TABLE_WORDS = "words"; 70 static final String VIEW_SMS_RESTRICTED = "sms_restricted"; 71 72 private static final Integer ONE = Integer.valueOf(1); 73 74 private static final String[] CONTACT_QUERY_PROJECTION = 75 new String[] { Contacts.Phones.PERSON_ID }; 76 private static final int PERSON_ID_COLUMN = 0; 77 78 /** Delete any raw messages or message segments marked deleted that are older than an hour. */ 79 static final long RAW_MESSAGE_EXPIRE_AGE_MS = (long) (60 * 60 * 1000); 80 81 /** 82 * These are the columns that are available when reading SMS 83 * messages from the ICC. Columns whose names begin with "is_" 84 * have either "true" or "false" as their values. 85 */ 86 private final static String[] ICC_COLUMNS = new String[] { 87 // N.B.: These columns must appear in the same order as the 88 // calls to add appear in convertIccToSms. 89 "service_center_address", // getServiceCenterAddress 90 "address", // getDisplayOriginatingAddress or getRecipientAddress 91 "message_class", // getMessageClass 92 "body", // getDisplayMessageBody 93 "date", // getTimestampMillis 94 "status", // getStatusOnIcc 95 "index_on_icc", // getIndexOnIcc (1-based index) 96 "is_status_report", // isStatusReportMessage 97 "transport_type", // Always "sms". 98 "type", // depend on getStatusOnIcc 99 "locked", // Always 0 (false). 100 "error_code", // Always -1 (NO_ERROR_CODE), previously it was 0 always. 101 "_id" 102 }; 103 104 @Override onCreate()105 public boolean onCreate() { 106 setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS); 107 // So we have two database files. One in de, one in ce. Here only "raw" table is in 108 // mDeOpenHelper, other tables are all in mCeOpenHelper. 109 mDeOpenHelper = MmsSmsDatabaseHelper.getInstanceForDe(getContext()); 110 mCeOpenHelper = MmsSmsDatabaseHelper.getInstanceForCe(getContext()); 111 TelephonyBackupAgent.DeferredSmsMmsRestoreService.startIfFilesExist(getContext()); 112 113 // Creating intent broadcast receiver for user actions like Intent.ACTION_USER_REMOVED, 114 // where we would need to remove SMS related to removed user. 115 IntentFilter userIntentFilter = new IntentFilter(Intent.ACTION_USER_REMOVED); 116 getContext().registerReceiver(mUserIntentReceiver, userIntentFilter, 117 Context.RECEIVER_NOT_EXPORTED); 118 119 return true; 120 } 121 122 /** 123 * Return the proper view of "sms" table for the current access status. 124 * 125 * @param accessRestricted If the access is restricted 126 * @return the table/view name of the "sms" data 127 */ getSmsTable(boolean accessRestricted)128 public static String getSmsTable(boolean accessRestricted) { 129 return accessRestricted ? VIEW_SMS_RESTRICTED : TABLE_SMS; 130 } 131 132 @Override query(Uri url, String[] projectionIn, String selection, String[] selectionArgs, String sort)133 public Cursor query(Uri url, String[] projectionIn, String selection, 134 String[] selectionArgs, String sort) { 135 final int callingUid = Binder.getCallingUid(); 136 final UserHandle callerUserHandle = Binder.getCallingUserHandle(); 137 138 // First check if a restricted view of the "sms" table should be used based on the 139 // caller's identity. Only system, phone or the default sms app can have full access 140 // of sms data. For other apps, we present a restricted view which only contains sent 141 // or received messages. 142 final boolean accessRestricted = ProviderUtil.isAccessRestricted( 143 getContext(), getCallingPackage(), callingUid); 144 final String smsTable = getSmsTable(accessRestricted); 145 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 146 147 // If access is restricted, we don't allow subqueries in the query. 148 if (accessRestricted) { 149 try { 150 SqlQueryChecker.checkQueryParametersForSubqueries(projectionIn, selection, sort); 151 } catch (IllegalArgumentException e) { 152 Log.w(TAG, "Query rejected: " + e.getMessage()); 153 return null; 154 } 155 } 156 157 Cursor emptyCursor = new MatrixCursor((projectionIn == null) ? 158 (new String[] {}) : projectionIn); 159 160 // Generate the body of the query. 161 int match = sURLMatcher.match(url); 162 SQLiteDatabase db = getReadableDatabase(match); 163 switch (match) { 164 case SMS_ALL: 165 constructQueryForBox(qb, Sms.MESSAGE_TYPE_ALL, smsTable); 166 break; 167 168 case SMS_UNDELIVERED: 169 constructQueryForUndelivered(qb, smsTable); 170 break; 171 172 case SMS_FAILED: 173 constructQueryForBox(qb, Sms.MESSAGE_TYPE_FAILED, smsTable); 174 break; 175 176 case SMS_QUEUED: 177 constructQueryForBox(qb, Sms.MESSAGE_TYPE_QUEUED, smsTable); 178 break; 179 180 case SMS_INBOX: 181 constructQueryForBox(qb, Sms.MESSAGE_TYPE_INBOX, smsTable); 182 break; 183 184 case SMS_SENT: 185 constructQueryForBox(qb, Sms.MESSAGE_TYPE_SENT, smsTable); 186 break; 187 188 case SMS_DRAFT: 189 constructQueryForBox(qb, Sms.MESSAGE_TYPE_DRAFT, smsTable); 190 break; 191 192 case SMS_OUTBOX: 193 constructQueryForBox(qb, Sms.MESSAGE_TYPE_OUTBOX, smsTable); 194 break; 195 196 case SMS_ALL_ID: 197 qb.setTables(smsTable); 198 qb.appendWhere("(_id = " + url.getPathSegments().get(0) + ")"); 199 break; 200 201 case SMS_INBOX_ID: 202 case SMS_FAILED_ID: 203 case SMS_SENT_ID: 204 case SMS_DRAFT_ID: 205 case SMS_OUTBOX_ID: 206 qb.setTables(smsTable); 207 qb.appendWhere("(_id = " + url.getPathSegments().get(1) + ")"); 208 break; 209 210 case SMS_CONVERSATIONS_ID: 211 int threadID; 212 213 try { 214 threadID = Integer.parseInt(url.getPathSegments().get(1)); 215 if (Log.isLoggable(TAG, Log.VERBOSE)) { 216 Log.d(TAG, "query conversations: threadID=" + threadID); 217 } 218 } 219 catch (Exception ex) { 220 Log.e(TAG, 221 "Bad conversation thread id: " 222 + url.getPathSegments().get(1)); 223 return null; 224 } 225 226 qb.setTables(smsTable); 227 qb.appendWhere("thread_id = " + threadID); 228 break; 229 230 case SMS_CONVERSATIONS: 231 qb.setTables(smsTable + ", " 232 + "(SELECT thread_id AS group_thread_id, " 233 + "MAX(date) AS group_date, " 234 + "COUNT(*) AS msg_count " 235 + "FROM " + smsTable + " " 236 + "GROUP BY thread_id) AS groups"); 237 qb.appendWhere(smsTable + ".thread_id=groups.group_thread_id" 238 + " AND " + smsTable + ".date=groups.group_date"); 239 final HashMap<String, String> projectionMap = new HashMap<>(); 240 projectionMap.put(Sms.Conversations.SNIPPET, 241 smsTable + ".body AS snippet"); 242 projectionMap.put(Sms.Conversations.THREAD_ID, 243 smsTable + ".thread_id AS thread_id"); 244 projectionMap.put(Sms.Conversations.MESSAGE_COUNT, 245 "groups.msg_count AS msg_count"); 246 projectionMap.put("delta", null); 247 qb.setProjectionMap(projectionMap); 248 break; 249 250 case SMS_RAW_MESSAGE: 251 // before querying purge old entries with deleted = 1 252 purgeDeletedMessagesInRawTable(db); 253 qb.setTables("raw"); 254 break; 255 256 case SMS_STATUS_PENDING: 257 qb.setTables("sr_pending"); 258 break; 259 260 case SMS_ATTACHMENT: 261 qb.setTables("attachments"); 262 break; 263 264 case SMS_ATTACHMENT_ID: 265 qb.setTables("attachments"); 266 qb.appendWhere( 267 "(sms_id = " + url.getPathSegments().get(1) + ")"); 268 break; 269 270 case SMS_QUERY_THREAD_ID: 271 qb.setTables("canonical_addresses"); 272 if (projectionIn == null) { 273 projectionIn = sIDProjection; 274 } 275 break; 276 277 case SMS_STATUS_ID: 278 qb.setTables(smsTable); 279 qb.appendWhere("(_id = " + url.getPathSegments().get(1) + ")"); 280 break; 281 282 case SMS_ALL_ICC: 283 case SMS_ALL_ICC_SUBID: 284 { 285 int subId; 286 if (match == SMS_ALL_ICC) { 287 subId = SmsManager.getDefaultSmsSubscriptionId(); 288 } else { 289 try { 290 subId = Integer.parseInt(url.getPathSegments().get(1)); 291 } catch (NumberFormatException e) { 292 throw new IllegalArgumentException("Wrong path segements, uri= " + url); 293 } 294 } 295 296 if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(getContext(), 297 subId, callerUserHandle)) { 298 // If subId is not associated with user, return empty cursor. 299 return emptyCursor; 300 } 301 302 Cursor ret = getAllMessagesFromIcc(subId); 303 ret.setNotificationUri(getContext().getContentResolver(), 304 match == SMS_ALL_ICC ? ICC_URI : ICC_SUBID_URI); 305 return ret; 306 } 307 308 case SMS_ICC: 309 case SMS_ICC_SUBID: 310 { 311 int subId; 312 int messageIndex; 313 try { 314 if (match == SMS_ICC) { 315 subId = SmsManager.getDefaultSmsSubscriptionId(); 316 messageIndex = Integer.parseInt(url.getPathSegments().get(1)); 317 } else { 318 subId = Integer.parseInt(url.getPathSegments().get(1)); 319 messageIndex = Integer.parseInt(url.getPathSegments().get(2)); 320 } 321 } catch (NumberFormatException e) { 322 throw new IllegalArgumentException("Wrong path segements, uri= " + url); 323 } 324 325 if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(getContext(), 326 subId, callerUserHandle)) { 327 // If subId is not associated with user, return empty cursor. 328 return emptyCursor; 329 } 330 331 Cursor ret = getSingleMessageFromIcc(subId, messageIndex); 332 ret.setNotificationUri(getContext().getContentResolver(), 333 match == SMS_ICC ? ICC_URI : ICC_SUBID_URI); 334 return ret; 335 } 336 337 default: 338 Log.e(TAG, "Invalid request: " + url); 339 return null; 340 } 341 342 final long token = Binder.clearCallingIdentity(); 343 String selectionBySubIds = null; 344 String selectionByEmergencyNumbers = null; 345 try { 346 // Filter SMS based on subId and emergency numbers. 347 selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(), 348 callerUserHandle); 349 if (qb.getTables().equals(smsTable)) { 350 selectionByEmergencyNumbers = ProviderUtil 351 .getSelectionByEmergencyNumbers(getContext()); 352 } 353 } finally { 354 Binder.restoreCallingIdentity(token); 355 } 356 357 if (qb.getTables().equals(smsTable)) { 358 if (selectionBySubIds == null && selectionByEmergencyNumbers == null) { 359 // No subscriptions associated with user 360 // and no emergency numbers return empty cursor. 361 return emptyCursor; 362 } 363 } else { 364 if (selectionBySubIds == null) { 365 // No subscriptions associated with user return empty cursor. 366 return emptyCursor; 367 } 368 } 369 370 String filter = ""; 371 if (selectionBySubIds != null && selectionByEmergencyNumbers != null) { 372 filter = (selectionBySubIds + " OR " + selectionByEmergencyNumbers); 373 } else { 374 filter = selectionBySubIds == null ? 375 selectionByEmergencyNumbers : selectionBySubIds; 376 } 377 selection = DatabaseUtils.concatenateWhere(selection, filter); 378 379 String orderBy = null; 380 381 if (!TextUtils.isEmpty(sort)) { 382 orderBy = sort; 383 } else if (qb.getTables().equals(smsTable)) { 384 orderBy = Sms.DEFAULT_SORT_ORDER; 385 } 386 387 Cursor ret = qb.query(db, projectionIn, selection, selectionArgs, 388 null, null, orderBy); 389 // TODO: Since the URLs are a mess, always use content://sms 390 ret.setNotificationUri(getContext().getContentResolver(), 391 NOTIFICATION_URI); 392 return ret; 393 } 394 purgeDeletedMessagesInRawTable(SQLiteDatabase db)395 private void purgeDeletedMessagesInRawTable(SQLiteDatabase db) { 396 long oldTimestamp = System.currentTimeMillis() - RAW_MESSAGE_EXPIRE_AGE_MS; 397 int num = db.delete(TABLE_RAW, "deleted = 1 AND date < " + oldTimestamp, null); 398 if (Log.isLoggable(TAG, Log.VERBOSE)) { 399 Log.d(TAG, "purgeDeletedMessagesInRawTable: num rows older than " + oldTimestamp + 400 " purged: " + num); 401 } 402 } 403 getDBOpenHelper(int match)404 private SQLiteOpenHelper getDBOpenHelper(int match) { 405 // Raw table is stored on de database. Other tables are stored in ce database. 406 if (match == SMS_RAW_MESSAGE || match == SMS_RAW_MESSAGE_PERMANENT_DELETE) { 407 return mDeOpenHelper; 408 } 409 return mCeOpenHelper; 410 } 411 convertIccToSms(SmsMessage message, int id)412 private Object[] convertIccToSms(SmsMessage message, int id) { 413 int statusOnIcc = message.getStatusOnIcc(); 414 int type = Sms.MESSAGE_TYPE_ALL; 415 switch (statusOnIcc) { 416 case SmsManager.STATUS_ON_ICC_READ: 417 case SmsManager.STATUS_ON_ICC_UNREAD: 418 type = Sms.MESSAGE_TYPE_INBOX; 419 break; 420 case SmsManager.STATUS_ON_ICC_SENT: 421 type = Sms.MESSAGE_TYPE_SENT; 422 break; 423 case SmsManager.STATUS_ON_ICC_UNSENT: 424 type = Sms.MESSAGE_TYPE_OUTBOX; 425 break; 426 } 427 428 String address = (type == Sms.MESSAGE_TYPE_INBOX) 429 ? message.getDisplayOriginatingAddress() 430 : message.getRecipientAddress(); 431 432 int index = message.getIndexOnIcc(); 433 if (address == null) { 434 // The status byte of an EF_SMS record may not be correct. try to read other address 435 // type again. 436 Log.e(TAG, "convertIccToSms: EF_SMS(" + index + ")=> address=null, type=" + type 437 + ", status=" + statusOnIcc + "(may not be correct). fallback to other type."); 438 address = (type == Sms.MESSAGE_TYPE_INBOX) 439 ? message.getRecipientAddress() 440 : message.getDisplayOriginatingAddress(); 441 442 if (address != null) { 443 // Rely on actual PDU(address) to set type again. 444 type = (type == Sms.MESSAGE_TYPE_INBOX) 445 ? Sms.MESSAGE_TYPE_SENT 446 : Sms.MESSAGE_TYPE_INBOX; 447 Log.d(TAG, "convertIccToSms: new type=" + type + ", address=xxxxxx"); 448 } else { 449 Log.e(TAG, "convertIccToSms: no change"); 450 } 451 } 452 453 // N.B.: These calls must appear in the same order as the 454 // columns appear in ICC_COLUMNS. 455 Object[] row = new Object[13]; 456 row[0] = message.getServiceCenterAddress(); 457 row[1] = address; 458 row[2] = String.valueOf(message.getMessageClass()); 459 row[3] = message.getDisplayMessageBody(); 460 row[4] = message.getTimestampMillis(); 461 row[5] = statusOnIcc; 462 row[6] = index; 463 row[7] = message.isStatusReportMessage(); 464 row[8] = "sms"; 465 row[9] = type; 466 row[10] = 0; // locked 467 row[11] = NO_ERROR_CODE; 468 row[12] = id; 469 return row; 470 } 471 472 /** 473 * Gets single message from the ICC for a subscription ID. 474 * 475 * @param subId the subscription ID. 476 * @param messageIndex the message index of the messaage in the ICC (1-based index). 477 * @return a cursor containing just one message from the ICC for the subscription ID. 478 */ getSingleMessageFromIcc(int subId, int messageIndex)479 private Cursor getSingleMessageFromIcc(int subId, int messageIndex) { 480 if (!SubscriptionManager.isValidSubscriptionId(subId)) { 481 throw new IllegalArgumentException("Invalid Subscription ID " + subId); 482 } 483 SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId); 484 List<SmsMessage> messages; 485 486 // Use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call. 487 long token = Binder.clearCallingIdentity(); 488 try { 489 // getMessagesFromIcc() returns a zero-based list of valid messages in the ICC. 490 messages = smsManager.getMessagesFromIcc(); 491 } finally { 492 Binder.restoreCallingIdentity(token); 493 } 494 495 final int count = messages.size(); 496 for (int i = 0; i < count; i++) { 497 SmsMessage message = messages.get(i); 498 if (message != null && message.getIndexOnIcc() == messageIndex) { 499 MatrixCursor cursor = new MatrixCursor(ICC_COLUMNS, 1); 500 cursor.addRow(convertIccToSms(message, 0)); 501 return cursor; 502 } 503 } 504 505 throw new IllegalArgumentException( 506 "No message in index " + messageIndex + " for subId " + subId); 507 } 508 509 /** 510 * Gets all the messages in the ICC for a subscription ID. 511 * 512 * @param subId the subscription ID. 513 * @return a cursor listing all the message in the ICC for the subscription ID. 514 */ getAllMessagesFromIcc(int subId)515 private Cursor getAllMessagesFromIcc(int subId) { 516 if (!SubscriptionManager.isValidSubscriptionId(subId)) { 517 throw new IllegalArgumentException("Invalid Subscription ID " + subId); 518 } 519 SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId); 520 List<SmsMessage> messages; 521 522 // Use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call 523 long token = Binder.clearCallingIdentity(); 524 try { 525 // getMessagesFromIcc() returns a zero-based list of valid messages in the ICC. 526 messages = smsManager.getMessagesFromIcc(); 527 } finally { 528 Binder.restoreCallingIdentity(token); 529 } 530 531 final int count = messages.size(); 532 MatrixCursor cursor = new MatrixCursor(ICC_COLUMNS, count); 533 for (int i = 0; i < count; i++) { 534 SmsMessage message = messages.get(i); 535 if (message != null) { 536 cursor.addRow(convertIccToSms(message, i)); 537 } 538 } 539 return cursor; 540 } 541 constructQueryForBox(SQLiteQueryBuilder qb, int type, String smsTable)542 private void constructQueryForBox(SQLiteQueryBuilder qb, int type, String smsTable) { 543 qb.setTables(smsTable); 544 545 if (type != Sms.MESSAGE_TYPE_ALL) { 546 qb.appendWhere("type=" + type); 547 } 548 } 549 constructQueryForUndelivered(SQLiteQueryBuilder qb, String smsTable)550 private void constructQueryForUndelivered(SQLiteQueryBuilder qb, String smsTable) { 551 qb.setTables(smsTable); 552 553 qb.appendWhere("(type=" + Sms.MESSAGE_TYPE_OUTBOX + 554 " OR type=" + Sms.MESSAGE_TYPE_FAILED + 555 " OR type=" + Sms.MESSAGE_TYPE_QUEUED + ")"); 556 } 557 558 @Override getType(Uri url)559 public String getType(Uri url) { 560 switch (url.getPathSegments().size()) { 561 case 0: 562 return VND_ANDROID_DIR_SMS; 563 case 1: 564 try { 565 Integer.parseInt(url.getPathSegments().get(0)); 566 return VND_ANDROID_SMS; 567 } catch (NumberFormatException ex) { 568 return VND_ANDROID_DIR_SMS; 569 } 570 case 2: 571 // TODO: What about "threadID"? 572 if (url.getPathSegments().get(0).equals("conversations")) { 573 return VND_ANDROID_SMSCHAT; 574 } else { 575 return VND_ANDROID_SMS; 576 } 577 } 578 return null; 579 } 580 581 @Override bulkInsert(@onNull Uri url, @NonNull ContentValues[] values)582 public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] values) { 583 final int callerUid = Binder.getCallingUid(); 584 final UserHandle callerUserHandle = Binder.getCallingUserHandle(); 585 final String callerPkg = getCallingPackage(); 586 long token = Binder.clearCallingIdentity(); 587 try { 588 int messagesInserted = 0; 589 for (ContentValues initialValues : values) { 590 Uri insertUri = insertInner(url, initialValues, callerUid, callerPkg, 591 callerUserHandle); 592 if (insertUri != null) { 593 messagesInserted++; 594 } 595 } 596 597 // The raw table is used by the telephony layer for storing an sms before 598 // sending out a notification that an sms has arrived. We don't want to notify 599 // the default sms app of changes to this table. 600 final boolean notifyIfNotDefault = sURLMatcher.match(url) != SMS_RAW_MESSAGE; 601 notifyChange(notifyIfNotDefault, url, callerPkg); 602 return messagesInserted; 603 } finally { 604 Binder.restoreCallingIdentity(token); 605 } 606 } 607 608 @Override insert(Uri url, ContentValues initialValues)609 public Uri insert(Uri url, ContentValues initialValues) { 610 final int callerUid = Binder.getCallingUid(); 611 final UserHandle callerUserHandle = Binder.getCallingUserHandle(); 612 final String callerPkg = getCallingPackage(); 613 long token = Binder.clearCallingIdentity(); 614 try { 615 Uri insertUri = insertInner(url, initialValues, callerUid, callerPkg, callerUserHandle); 616 617 // Skip notifyChange() if insertUri is null 618 if (insertUri != null) { 619 int match = sURLMatcher.match(url); 620 // The raw table is used by the telephony layer for storing an sms before sending 621 // out a notification that an sms has arrived. We don't want to notify the default 622 // sms app of changes to this table. 623 final boolean notifyIfNotDefault = match != SMS_RAW_MESSAGE; 624 notifyChange(notifyIfNotDefault, insertUri, callerPkg); 625 } 626 return insertUri; 627 } finally { 628 Binder.restoreCallingIdentity(token); 629 } 630 } 631 insertInner(Uri url, ContentValues initialValues, int callerUid, String callerPkg, UserHandle callerUserHandle)632 private Uri insertInner(Uri url, ContentValues initialValues, int callerUid, String callerPkg, 633 UserHandle callerUserHandle) { 634 ContentValues values; 635 long rowID; 636 int type = Sms.MESSAGE_TYPE_ALL; 637 638 int match = sURLMatcher.match(url); 639 String table = TABLE_SMS; 640 641 switch (match) { 642 case SMS_ALL: 643 Integer typeObj = initialValues.getAsInteger(Sms.TYPE); 644 if (typeObj != null) { 645 type = typeObj.intValue(); 646 } else { 647 // default to inbox 648 type = Sms.MESSAGE_TYPE_INBOX; 649 } 650 break; 651 652 case SMS_INBOX: 653 type = Sms.MESSAGE_TYPE_INBOX; 654 break; 655 656 case SMS_FAILED: 657 type = Sms.MESSAGE_TYPE_FAILED; 658 break; 659 660 case SMS_QUEUED: 661 type = Sms.MESSAGE_TYPE_QUEUED; 662 break; 663 664 case SMS_SENT: 665 type = Sms.MESSAGE_TYPE_SENT; 666 break; 667 668 case SMS_DRAFT: 669 type = Sms.MESSAGE_TYPE_DRAFT; 670 break; 671 672 case SMS_OUTBOX: 673 type = Sms.MESSAGE_TYPE_OUTBOX; 674 break; 675 676 case SMS_RAW_MESSAGE: 677 table = "raw"; 678 break; 679 680 case SMS_STATUS_PENDING: 681 table = "sr_pending"; 682 break; 683 684 case SMS_ATTACHMENT: 685 table = "attachments"; 686 break; 687 688 case SMS_NEW_THREAD_ID: 689 table = "canonical_addresses"; 690 break; 691 692 case SMS_ALL_ICC: 693 case SMS_ALL_ICC_SUBID: 694 int subId; 695 if (match == SMS_ALL_ICC) { 696 subId = SmsManager.getDefaultSmsSubscriptionId(); 697 } else { 698 try { 699 subId = Integer.parseInt(url.getPathSegments().get(1)); 700 } catch (NumberFormatException e) { 701 throw new IllegalArgumentException( 702 "Wrong path segements for SMS_ALL_ICC_SUBID, uri= " + url); 703 } 704 } 705 706 if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(getContext(), subId, 707 callerUserHandle)) { 708 TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(getContext(), 709 subId, callerUid, callerPkg); 710 return null; 711 } 712 713 if (initialValues == null) { 714 throw new IllegalArgumentException("ContentValues is null"); 715 } 716 717 String scAddress = initialValues.getAsString(Sms.SERVICE_CENTER); 718 String address = initialValues.getAsString(Sms.ADDRESS); 719 String message = initialValues.getAsString(Sms.BODY); 720 boolean isRead = true; 721 Integer obj = initialValues.getAsInteger(Sms.TYPE); 722 723 if (obj == null || address == null || message == null) { 724 throw new IllegalArgumentException("Missing SMS data"); 725 } 726 727 type = obj.intValue(); 728 if (!isSupportedType(type)) { 729 throw new IllegalArgumentException("Unsupported message type= " + type); 730 } 731 obj = initialValues.getAsInteger(Sms.READ); // 0: Unread, 1: Read 732 if (obj != null && obj.intValue() == 0) { 733 isRead = false; 734 } 735 736 Long date = initialValues.getAsLong(Sms.DATE); 737 return insertMessageToIcc(subId, scAddress, address, message, type, isRead, 738 date != null ? date : 0) ? url : null; 739 740 default: 741 Log.e(TAG, "Invalid request: " + url); 742 return null; 743 } 744 745 SQLiteDatabase db = getWritableDatabase(match); 746 747 if (table.equals(TABLE_SMS)) { 748 boolean addDate = false; 749 boolean addType = false; 750 751 // Make sure that the date and type are set 752 if (initialValues == null) { 753 values = new ContentValues(1); 754 addDate = true; 755 addType = true; 756 } else { 757 values = new ContentValues(initialValues); 758 759 if (!initialValues.containsKey(Sms.DATE)) { 760 addDate = true; 761 } 762 763 if (!initialValues.containsKey(Sms.TYPE)) { 764 addType = true; 765 } 766 } 767 768 if (addDate) { 769 values.put(Sms.DATE, new Long(System.currentTimeMillis())); 770 } 771 772 if (addType && (type != Sms.MESSAGE_TYPE_ALL)) { 773 values.put(Sms.TYPE, Integer.valueOf(type)); 774 } 775 776 // thread_id 777 Long threadId = values.getAsLong(Sms.THREAD_ID); 778 String address = values.getAsString(Sms.ADDRESS); 779 780 if (((threadId == null) || (threadId == 0)) && (!TextUtils.isEmpty(address))) { 781 values.put(Sms.THREAD_ID, Threads.getOrCreateThreadId( 782 getContext(), address)); 783 } 784 785 // If this message is going in as a draft, it should replace any 786 // other draft messages in the thread. Just delete all draft 787 // messages with this thread ID. We could add an OR REPLACE to 788 // the insert below, but we'd have to query to find the old _id 789 // to produce a conflict anyway. 790 if (values.getAsInteger(Sms.TYPE) == Sms.MESSAGE_TYPE_DRAFT) { 791 db.delete(TABLE_SMS, "thread_id=? AND type=?", 792 new String[] { values.getAsString(Sms.THREAD_ID), 793 Integer.toString(Sms.MESSAGE_TYPE_DRAFT) }); 794 } 795 796 if (type == Sms.MESSAGE_TYPE_INBOX) { 797 // Look up the person if not already filled in. 798 if ((values.getAsLong(Sms.PERSON) == null) && (!TextUtils.isEmpty(address))) { 799 Cursor cursor = null; 800 Uri uri = Uri.withAppendedPath(Contacts.Phones.CONTENT_FILTER_URL, 801 Uri.encode(address)); 802 try { 803 cursor = getContext().getContentResolver().query( 804 uri, 805 CONTACT_QUERY_PROJECTION, 806 null, null, null); 807 808 if (cursor != null && cursor.moveToFirst()) { 809 Long id = Long.valueOf(cursor.getLong(PERSON_ID_COLUMN)); 810 values.put(Sms.PERSON, id); 811 } 812 } catch (Exception ex) { 813 Log.e(TAG, "insert: query contact uri " + uri + " caught ", ex); 814 } finally { 815 if (cursor != null) { 816 cursor.close(); 817 } 818 } 819 } 820 } else { 821 // Mark all non-inbox messages read. 822 values.put(Sms.READ, ONE); 823 } 824 if (ProviderUtil.shouldSetCreator(values, callerUid)) { 825 // Only SYSTEM or PHONE can set CREATOR 826 // If caller is not SYSTEM or PHONE, or SYSTEM or PHONE does not set CREATOR 827 // set CREATOR using the truth on caller. 828 // Note: Inferring package name from UID may include unrelated package names 829 values.put(Sms.CREATOR, callerPkg); 830 } 831 } else { 832 if (initialValues == null) { 833 values = new ContentValues(1); 834 } else { 835 values = initialValues; 836 } 837 } 838 839 // Insert subId value 840 int subId; 841 if (values.containsKey(Telephony.Sms.SUBSCRIPTION_ID)) { 842 subId = values.getAsInteger(Telephony.Sms.SUBSCRIPTION_ID); 843 } else { 844 // TODO (b/256992531): Currently, one sim card is set as default sms subId in work 845 // profile. Default sms subId should be updated based on user pref. 846 subId = SmsManager.getDefaultSmsSubscriptionId(); 847 if (SubscriptionManager.isValidSubscriptionId(subId)) { 848 values.put(Telephony.Sms.SUBSCRIPTION_ID, subId); 849 } 850 } 851 852 853 if (table.equals(TABLE_SMS)) { 854 // Get destination address from values 855 String address = ""; 856 if (values.containsKey(Sms.ADDRESS)) { 857 address = values.getAsString(Sms.ADDRESS); 858 } 859 860 if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(getContext(), subId, 861 callerUserHandle, address)) { 862 TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(getContext(), subId, 863 callerUid, callerPkg); 864 return null; 865 } 866 } 867 868 rowID = db.insert(table, "body", values); 869 870 // Don't use a trigger for updating the words table because of a bug 871 // in FTS3. The bug is such that the call to get the last inserted 872 // row is incorrect. 873 if (table == TABLE_SMS) { 874 // Update the words table with a corresponding row. The words table 875 // allows us to search for words quickly, without scanning the whole 876 // table; 877 ContentValues cv = new ContentValues(); 878 cv.put(Telephony.MmsSms.WordsTable.ID, rowID); 879 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("body")); 880 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowID); 881 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 1); 882 cv.put(MmsSms.WordsTable.SUBSCRIPTION_ID, subId); 883 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv); 884 } 885 if (rowID > 0) { 886 Uri uri = null; 887 if (table == TABLE_SMS) { 888 uri = Uri.withAppendedPath(Sms.CONTENT_URI, String.valueOf(rowID)); 889 } else { 890 uri = Uri.withAppendedPath(url, String.valueOf(rowID)); 891 } 892 if (Log.isLoggable(TAG, Log.VERBOSE)) { 893 Log.d(TAG, "insert " + uri + " succeeded"); 894 } 895 return uri; 896 } else { 897 Log.e(TAG, "insert: failed!"); 898 } 899 900 return null; 901 } 902 isSupportedType(int messageType)903 private boolean isSupportedType(int messageType) { 904 return (messageType == Sms.MESSAGE_TYPE_INBOX) 905 || (messageType == Sms.MESSAGE_TYPE_OUTBOX) 906 || (messageType == Sms.MESSAGE_TYPE_SENT); 907 } 908 getMessageStatusForIcc(int messageType, boolean isRead)909 private int getMessageStatusForIcc(int messageType, boolean isRead) { 910 if (messageType == Sms.MESSAGE_TYPE_SENT) { 911 return SmsManager.STATUS_ON_ICC_SENT; 912 } else if (messageType == Sms.MESSAGE_TYPE_OUTBOX) { 913 return SmsManager.STATUS_ON_ICC_UNSENT; 914 } else { // Sms.MESSAGE_BOX_INBOX 915 if (isRead) { 916 return SmsManager.STATUS_ON_ICC_READ; 917 } else { 918 return SmsManager.STATUS_ON_ICC_UNREAD; 919 } 920 } 921 } 922 923 /** 924 * Inserts new message to the ICC for a subscription ID. 925 * 926 * @param subId the subscription ID. 927 * @param scAddress the SMSC for this message. 928 * @param address destination or originating address. 929 * @param message the message text. 930 * @param messageType type of the message. 931 * @param isRead ture if the message has been read. Otherwise false. 932 * @param date the date the message was received. 933 * @return true for succeess. Otherwise false. 934 */ insertMessageToIcc(int subId, String scAddress, String address, String message, int messageType, boolean isRead, long date)935 private boolean insertMessageToIcc(int subId, String scAddress, String address, String message, 936 int messageType, boolean isRead, long date) { 937 if (!SubscriptionManager.isValidSubscriptionId(subId)) { 938 throw new IllegalArgumentException("Invalid Subscription ID " + subId); 939 } 940 SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId); 941 942 int status = getMessageStatusForIcc(messageType, isRead); 943 SmsMessage.SubmitPdu smsPdu = 944 SmsMessage.getSmsPdu(subId, status, scAddress, address, message, date); 945 946 if (smsPdu == null) { 947 throw new IllegalArgumentException("Failed to create SMS PDU"); 948 } 949 950 // Use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call. 951 long token = Binder.clearCallingIdentity(); 952 try { 953 return smsManager.copyMessageToIcc( 954 smsPdu.encodedScAddress, smsPdu.encodedMessage, status); 955 } finally { 956 Binder.restoreCallingIdentity(token); 957 } 958 } 959 960 @Override delete(Uri url, String where, String[] whereArgs)961 public int delete(Uri url, String where, String[] whereArgs) { 962 final UserHandle callerUserHandle = Binder.getCallingUserHandle(); 963 final int callerUid = Binder.getCallingUid(); 964 final long token = Binder.clearCallingIdentity(); 965 966 String selectionBySubIds = null; 967 String selectionByEmergencyNumbers = null; 968 try { 969 // Filter SMS based on subId and emergency numbers. 970 selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(), 971 callerUserHandle); 972 selectionByEmergencyNumbers = ProviderUtil 973 .getSelectionByEmergencyNumbers(getContext()); 974 } finally { 975 Binder.restoreCallingIdentity(token); 976 } 977 978 String filter = ""; 979 if (selectionBySubIds == null && selectionByEmergencyNumbers == null) { 980 // No subscriptions associated with user and no emergency numbers 981 filter = null; 982 } else if (selectionBySubIds != null && selectionByEmergencyNumbers != null) { 983 filter = (selectionBySubIds + " OR " + selectionByEmergencyNumbers); 984 } else { 985 filter = selectionBySubIds == null ? 986 selectionByEmergencyNumbers : selectionBySubIds; 987 } 988 989 int count; 990 int match = sURLMatcher.match(url); 991 SQLiteDatabase db = getWritableDatabase(match); 992 boolean notifyIfNotDefault = true; 993 switch (match) { 994 case SMS_ALL: 995 if (filter == null) { 996 // No subscriptions associated with user and no emergency numbers, return 0. 997 return 0; 998 } 999 where = DatabaseUtils.concatenateWhere(where, filter); 1000 count = db.delete(TABLE_SMS, where, whereArgs); 1001 if (count != 0) { 1002 // Don't update threads unless something changed. 1003 MmsSmsDatabaseHelper.updateThreads(db, where, whereArgs); 1004 } 1005 break; 1006 1007 case SMS_ALL_ID: 1008 try { 1009 int message_id = Integer.parseInt(url.getPathSegments().get(0)); 1010 count = MmsSmsDatabaseHelper.deleteOneSms(db, message_id); 1011 } catch (Exception e) { 1012 throw new IllegalArgumentException( 1013 "Bad message id: " + url.getPathSegments().get(0)); 1014 } 1015 break; 1016 1017 case SMS_CONVERSATIONS_ID: 1018 int threadID; 1019 1020 try { 1021 threadID = Integer.parseInt(url.getPathSegments().get(1)); 1022 } catch (Exception ex) { 1023 throw new IllegalArgumentException( 1024 "Bad conversation thread id: " 1025 + url.getPathSegments().get(1)); 1026 } 1027 1028 // delete the messages from the sms table 1029 where = DatabaseUtils.concatenateWhere("thread_id=" + threadID, where); 1030 if (filter == null) { 1031 // No subscriptions associated with user and no emergency numbers, return 0. 1032 return 0; 1033 } 1034 where = DatabaseUtils.concatenateWhere(where, filter); 1035 count = db.delete(TABLE_SMS, where, whereArgs); 1036 MmsSmsDatabaseHelper.updateThread(db, threadID); 1037 break; 1038 1039 case SMS_RAW_MESSAGE: 1040 ContentValues cv = new ContentValues(); 1041 cv.put("deleted", 1); 1042 count = db.update(TABLE_RAW, cv, where, whereArgs); 1043 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1044 Log.d(TAG, "delete: num rows marked deleted in raw table: " + count); 1045 } 1046 notifyIfNotDefault = false; 1047 break; 1048 1049 case SMS_RAW_MESSAGE_PERMANENT_DELETE: 1050 count = db.delete(TABLE_RAW, where, whereArgs); 1051 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1052 Log.d(TAG, "delete: num rows permanently deleted in raw table: " + count); 1053 } 1054 notifyIfNotDefault = false; 1055 break; 1056 1057 case SMS_STATUS_PENDING: 1058 if (selectionBySubIds == null) { 1059 // No subscriptions associated with user, return 0. 1060 return 0; 1061 } 1062 where = DatabaseUtils.concatenateWhere(where, selectionBySubIds); 1063 count = db.delete("sr_pending", where, whereArgs); 1064 break; 1065 1066 case SMS_ALL_ICC: 1067 case SMS_ALL_ICC_SUBID: 1068 { 1069 int subId; 1070 int deletedCnt; 1071 if (match == SMS_ALL_ICC) { 1072 subId = SmsManager.getDefaultSmsSubscriptionId(); 1073 } else { 1074 try { 1075 subId = Integer.parseInt(url.getPathSegments().get(1)); 1076 } catch (NumberFormatException e) { 1077 throw new IllegalArgumentException("Wrong path segements, uri= " + url); 1078 } 1079 } 1080 1081 if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(getContext(), 1082 subId, callerUserHandle)) { 1083 // If subId is not associated with user, return 0. 1084 return 0; 1085 } 1086 1087 deletedCnt = deleteAllMessagesFromIcc(subId); 1088 // Notify changes even failure case since there might be some changes should be 1089 // known. 1090 getContext() 1091 .getContentResolver() 1092 .notifyChange( 1093 match == SMS_ALL_ICC ? ICC_URI : ICC_SUBID_URI, 1094 null, 1095 true, 1096 UserHandle.USER_ALL); 1097 return deletedCnt; 1098 } 1099 1100 case SMS_ICC: 1101 case SMS_ICC_SUBID: 1102 { 1103 int subId; 1104 int messageIndex; 1105 boolean success; 1106 try { 1107 if (match == SMS_ICC) { 1108 subId = SmsManager.getDefaultSmsSubscriptionId(); 1109 messageIndex = Integer.parseInt(url.getPathSegments().get(1)); 1110 } else { 1111 subId = Integer.parseInt(url.getPathSegments().get(1)); 1112 messageIndex = Integer.parseInt(url.getPathSegments().get(2)); 1113 } 1114 } catch (NumberFormatException e) { 1115 throw new IllegalArgumentException("Wrong path segements, uri= " + url); 1116 } 1117 1118 if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(getContext(), 1119 subId, callerUserHandle)) { 1120 // If subId is not associated with user, return 0. 1121 return 0; 1122 } 1123 1124 success = deleteMessageFromIcc(subId, messageIndex); 1125 // Notify changes even failure case since there might be some changes should be 1126 // known. 1127 getContext() 1128 .getContentResolver() 1129 .notifyChange( 1130 match == SMS_ICC ? ICC_URI : ICC_SUBID_URI, 1131 null, 1132 true, 1133 UserHandle.USER_ALL); 1134 return success ? 1 : 0; // return deleted count 1135 } 1136 1137 default: 1138 throw new IllegalArgumentException("Unknown URL"); 1139 } 1140 1141 if (count > 0) { 1142 notifyChange(notifyIfNotDefault, url, getCallingPackage()); 1143 } 1144 return count; 1145 } 1146 1147 /** 1148 * Deletes the message at index from the ICC for a subscription ID. 1149 * 1150 * @param subId the subscription ID. 1151 * @param messageIndex the message index of the message in the ICC (1-based index). 1152 * @return true for succeess. Otherwise false. 1153 */ deleteMessageFromIcc(int subId, int messageIndex)1154 private boolean deleteMessageFromIcc(int subId, int messageIndex) { 1155 if (!SubscriptionManager.isValidSubscriptionId(subId)) { 1156 throw new IllegalArgumentException("Invalid Subscription ID " + subId); 1157 } 1158 SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId); 1159 1160 // Use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call. 1161 long token = Binder.clearCallingIdentity(); 1162 try { 1163 return smsManager.deleteMessageFromIcc(messageIndex); 1164 } finally { 1165 Binder.restoreCallingIdentity(token); 1166 } 1167 } 1168 1169 /** 1170 * Deletes all the messages from the ICC for a subscription ID. 1171 * 1172 * @param subId the subscription ID. 1173 * @return return deleted messaegs count. 1174 */ deleteAllMessagesFromIcc(int subId)1175 private int deleteAllMessagesFromIcc(int subId) { 1176 if (!SubscriptionManager.isValidSubscriptionId(subId)) { 1177 throw new IllegalArgumentException("Invalid Subscription ID " + subId); 1178 } 1179 SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId); 1180 1181 // Use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call. 1182 long token = Binder.clearCallingIdentity(); 1183 try { 1184 int deletedCnt = 0; 1185 int maxIndex = smsManager.getSmsCapacityOnIcc(); 1186 // messageIndex is 1-based index of the message in the ICC. 1187 for (int messageIndex = 1; messageIndex <= maxIndex; messageIndex++) { 1188 if (smsManager.deleteMessageFromIcc(messageIndex)) { 1189 deletedCnt++; 1190 } else { 1191 Log.e(TAG, "Fail to delete SMS at index " + messageIndex 1192 + " for subId " + subId); 1193 } 1194 } 1195 return deletedCnt; 1196 } finally { 1197 Binder.restoreCallingIdentity(token); 1198 } 1199 } 1200 1201 @Override update(Uri url, ContentValues values, String where, String[] whereArgs)1202 public int update(Uri url, ContentValues values, String where, String[] whereArgs) { 1203 final int callerUid = Binder.getCallingUid(); 1204 final UserHandle callerUserHandle = Binder.getCallingUserHandle(); 1205 final String callerPkg = getCallingPackage(); 1206 int count = 0; 1207 String table = TABLE_SMS; 1208 String extraWhere = null; 1209 boolean notifyIfNotDefault = true; 1210 int match = sURLMatcher.match(url); 1211 SQLiteDatabase db = getWritableDatabase(match); 1212 1213 switch (match) { 1214 case SMS_RAW_MESSAGE: 1215 table = TABLE_RAW; 1216 notifyIfNotDefault = false; 1217 break; 1218 1219 case SMS_STATUS_PENDING: 1220 table = TABLE_SR_PENDING; 1221 break; 1222 1223 case SMS_ALL: 1224 case SMS_FAILED: 1225 case SMS_QUEUED: 1226 case SMS_INBOX: 1227 case SMS_SENT: 1228 case SMS_DRAFT: 1229 case SMS_OUTBOX: 1230 case SMS_CONVERSATIONS: 1231 break; 1232 1233 case SMS_ALL_ID: 1234 extraWhere = "_id=" + url.getPathSegments().get(0); 1235 break; 1236 1237 case SMS_INBOX_ID: 1238 case SMS_FAILED_ID: 1239 case SMS_SENT_ID: 1240 case SMS_DRAFT_ID: 1241 case SMS_OUTBOX_ID: 1242 extraWhere = "_id=" + url.getPathSegments().get(1); 1243 break; 1244 1245 case SMS_CONVERSATIONS_ID: { 1246 String threadId = url.getPathSegments().get(1); 1247 1248 try { 1249 Integer.parseInt(threadId); 1250 } catch (Exception ex) { 1251 Log.e(TAG, "Bad conversation thread id: " + threadId); 1252 break; 1253 } 1254 1255 extraWhere = "thread_id=" + threadId; 1256 break; 1257 } 1258 1259 case SMS_STATUS_ID: 1260 extraWhere = "_id=" + url.getPathSegments().get(1); 1261 break; 1262 1263 default: 1264 throw new UnsupportedOperationException( 1265 "URI " + url + " not supported"); 1266 } 1267 1268 if (table.equals(TABLE_SMS) && ProviderUtil.shouldRemoveCreator(values, callerUid)) { 1269 // CREATOR should not be changed by non-SYSTEM/PHONE apps 1270 Log.w(TAG, callerPkg + " tries to update CREATOR"); 1271 values.remove(Sms.CREATOR); 1272 } 1273 1274 final long token = Binder.clearCallingIdentity(); 1275 String selectionBySubIds = null; 1276 String selectionByEmergencyNumbers = null; 1277 try { 1278 // Filter SMS based on subId and emergency numbers. 1279 selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(), 1280 callerUserHandle); 1281 if (table.equals(TABLE_SMS)) { 1282 selectionByEmergencyNumbers = ProviderUtil 1283 .getSelectionByEmergencyNumbers(getContext()); 1284 } 1285 } finally { 1286 Binder.restoreCallingIdentity(token); 1287 } 1288 1289 if (table.equals(TABLE_SMS)) { 1290 if (selectionBySubIds == null && selectionByEmergencyNumbers == null) { 1291 // No subscriptions associated with user and no emergency numbers, return 0. 1292 return 0; 1293 } 1294 } else { 1295 if (selectionBySubIds == null) { 1296 // No subscriptions associated with user, return 0. 1297 return 0; 1298 } 1299 } 1300 1301 1302 String filter = ""; 1303 if (selectionBySubIds != null && selectionByEmergencyNumbers != null) { 1304 filter = (selectionBySubIds + " OR " + selectionByEmergencyNumbers); 1305 } else { 1306 filter = selectionBySubIds == null ? 1307 selectionByEmergencyNumbers : selectionBySubIds; 1308 } 1309 where = DatabaseUtils.concatenateWhere(where, filter); 1310 1311 where = DatabaseUtils.concatenateWhere(where, extraWhere); 1312 count = db.update(table, values, where, whereArgs); 1313 1314 if (count > 0) { 1315 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1316 Log.d(TAG, "update " + url + " succeeded"); 1317 } 1318 notifyChange(notifyIfNotDefault, url, callerPkg); 1319 } 1320 return count; 1321 } 1322 notifyChange(boolean notifyIfNotDefault, Uri uri, final String callingPackage)1323 private void notifyChange(boolean notifyIfNotDefault, Uri uri, final String callingPackage) { 1324 final Context context = getContext(); 1325 ContentResolver cr = context.getContentResolver(); 1326 cr.notifyChange(uri, null, true, UserHandle.USER_ALL); 1327 cr.notifyChange(MmsSms.CONTENT_URI, null, true, UserHandle.USER_ALL); 1328 cr.notifyChange(Uri.parse("content://mms-sms/conversations/"), null, true, 1329 UserHandle.USER_ALL); 1330 if (notifyIfNotDefault) { 1331 ProviderUtil.notifyIfNotDefaultSmsApp(uri, callingPackage, context); 1332 } 1333 } 1334 1335 // Db open helper for tables stored in CE(Credential Encrypted) storage. 1336 @VisibleForTesting 1337 public SQLiteOpenHelper mCeOpenHelper; 1338 // Db open helper for tables stored in DE(Device Encrypted) storage. It's currently only used 1339 // to store raw table. 1340 @VisibleForTesting 1341 public SQLiteOpenHelper mDeOpenHelper; 1342 1343 private final static String TAG = "SmsProvider"; 1344 private final static String VND_ANDROID_SMS = "vnd.android.cursor.item/sms"; 1345 private final static String VND_ANDROID_SMSCHAT = 1346 "vnd.android.cursor.item/sms-chat"; 1347 private final static String VND_ANDROID_DIR_SMS = 1348 "vnd.android.cursor.dir/sms"; 1349 1350 private static final String[] sIDProjection = new String[] { "_id" }; 1351 1352 private static final int SMS_ALL = 0; 1353 private static final int SMS_ALL_ID = 1; 1354 private static final int SMS_INBOX = 2; 1355 private static final int SMS_INBOX_ID = 3; 1356 private static final int SMS_SENT = 4; 1357 private static final int SMS_SENT_ID = 5; 1358 private static final int SMS_DRAFT = 6; 1359 private static final int SMS_DRAFT_ID = 7; 1360 private static final int SMS_OUTBOX = 8; 1361 private static final int SMS_OUTBOX_ID = 9; 1362 private static final int SMS_CONVERSATIONS = 10; 1363 private static final int SMS_CONVERSATIONS_ID = 11; 1364 private static final int SMS_RAW_MESSAGE = 15; 1365 private static final int SMS_ATTACHMENT = 16; 1366 private static final int SMS_ATTACHMENT_ID = 17; 1367 private static final int SMS_NEW_THREAD_ID = 18; 1368 private static final int SMS_QUERY_THREAD_ID = 19; 1369 private static final int SMS_STATUS_ID = 20; 1370 private static final int SMS_STATUS_PENDING = 21; 1371 private static final int SMS_ALL_ICC = 22; 1372 private static final int SMS_ICC = 23; 1373 private static final int SMS_FAILED = 24; 1374 private static final int SMS_FAILED_ID = 25; 1375 private static final int SMS_QUEUED = 26; 1376 private static final int SMS_UNDELIVERED = 27; 1377 private static final int SMS_RAW_MESSAGE_PERMANENT_DELETE = 28; 1378 private static final int SMS_ALL_ICC_SUBID = 29; 1379 private static final int SMS_ICC_SUBID = 30; 1380 1381 private static final UriMatcher sURLMatcher = 1382 new UriMatcher(UriMatcher.NO_MATCH); 1383 1384 static { 1385 sURLMatcher.addURI("sms", null, SMS_ALL); 1386 sURLMatcher.addURI("sms", "#", SMS_ALL_ID); 1387 sURLMatcher.addURI("sms", "inbox", SMS_INBOX); 1388 sURLMatcher.addURI("sms", "inbox/#", SMS_INBOX_ID); 1389 sURLMatcher.addURI("sms", "sent", SMS_SENT); 1390 sURLMatcher.addURI("sms", "sent/#", SMS_SENT_ID); 1391 sURLMatcher.addURI("sms", "draft", SMS_DRAFT); 1392 sURLMatcher.addURI("sms", "draft/#", SMS_DRAFT_ID); 1393 sURLMatcher.addURI("sms", "outbox", SMS_OUTBOX); 1394 sURLMatcher.addURI("sms", "outbox/#", SMS_OUTBOX_ID); 1395 sURLMatcher.addURI("sms", "undelivered", SMS_UNDELIVERED); 1396 sURLMatcher.addURI("sms", "failed", SMS_FAILED); 1397 sURLMatcher.addURI("sms", "failed/#", SMS_FAILED_ID); 1398 sURLMatcher.addURI("sms", "queued", SMS_QUEUED); 1399 sURLMatcher.addURI("sms", "conversations", SMS_CONVERSATIONS); 1400 sURLMatcher.addURI("sms", "conversations/#", SMS_CONVERSATIONS_ID); 1401 sURLMatcher.addURI("sms", "raw", SMS_RAW_MESSAGE); 1402 sURLMatcher.addURI("sms", "raw/permanentDelete", SMS_RAW_MESSAGE_PERMANENT_DELETE); 1403 sURLMatcher.addURI("sms", "attachments", SMS_ATTACHMENT); 1404 sURLMatcher.addURI("sms", "attachments/#", SMS_ATTACHMENT_ID); 1405 sURLMatcher.addURI("sms", "threadID", SMS_NEW_THREAD_ID); 1406 sURLMatcher.addURI("sms", "threadID/#", SMS_QUERY_THREAD_ID); 1407 sURLMatcher.addURI("sms", "status/#", SMS_STATUS_ID); 1408 sURLMatcher.addURI("sms", "sr_pending", SMS_STATUS_PENDING); 1409 sURLMatcher.addURI("sms", "icc", SMS_ALL_ICC); 1410 sURLMatcher.addURI("sms", "icc/#", SMS_ICC); 1411 sURLMatcher.addURI("sms", "icc_subId/#", SMS_ALL_ICC_SUBID); 1412 sURLMatcher.addURI("sms", "icc_subId/#/#", SMS_ICC_SUBID); 1413 //we keep these for not breaking old applications 1414 sURLMatcher.addURI("sms", "sim", SMS_ALL_ICC); 1415 sURLMatcher.addURI("sms", "sim/#", SMS_ICC); 1416 } 1417 1418 /** 1419 * These methods can be overridden in a subclass for testing SmsProvider using an 1420 * in-memory database. 1421 */ getReadableDatabase(int match)1422 SQLiteDatabase getReadableDatabase(int match) { 1423 return getDBOpenHelper(match).getReadableDatabase(); 1424 } 1425 getWritableDatabase(int match)1426 SQLiteDatabase getWritableDatabase(int match) { 1427 return getDBOpenHelper(match).getWritableDatabase(); 1428 } 1429 1430 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() { 1431 @Override 1432 public void onReceive(Context context, Intent intent) { 1433 switch (intent.getAction()) { 1434 case Intent.ACTION_USER_REMOVED: 1435 UserHandle userToBeRemoved = intent.getParcelableExtra(Intent.EXTRA_USER, 1436 UserHandle.class); 1437 UserManager userManager = context.getSystemService(UserManager.class); 1438 if ((userToBeRemoved == null) || (userManager == null) || 1439 (!userManager.isManagedProfile(userToBeRemoved.getIdentifier()))) { 1440 // Do not delete SMS if removed profile is not managed profile. 1441 return; 1442 } 1443 Log.d(TAG, "Received ACTION_USER_REMOVED for managed profile - Deleting SMS."); 1444 1445 // Deleting SMS related to managed profile. 1446 Uri uri = Sms.CONTENT_URI; 1447 int match = sURLMatcher.match(uri); 1448 SQLiteDatabase db = getWritableDatabase(match); 1449 1450 final long token = Binder.clearCallingIdentity(); 1451 String selectionBySubIds; 1452 try { 1453 // Filter SMS based on subId. 1454 selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(), 1455 userToBeRemoved); 1456 } finally { 1457 Binder.restoreCallingIdentity(token); 1458 } 1459 if (selectionBySubIds == null) { 1460 // No subscriptions associated with user, return. 1461 return; 1462 } 1463 1464 int count = db.delete(TABLE_SMS, selectionBySubIds, null); 1465 if (count != 0) { 1466 // Don't update threads unless something changed. 1467 MmsSmsDatabaseHelper.updateThreads(db, selectionBySubIds, null); 1468 notifyChange(true, uri, getCallingPackage()); 1469 } 1470 break; 1471 } 1472 } 1473 }; 1474 } 1475