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.app.AppOpsManager; 20 import android.content.ContentProvider; 21 import android.content.ContentResolver; 22 import android.content.ContentValues; 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.UserHandle; 33 import android.provider.Contacts; 34 import android.provider.Telephony; 35 import android.provider.Telephony.MmsSms; 36 import android.provider.Telephony.Sms; 37 import android.provider.Telephony.TextBasedSmsColumns; 38 import android.provider.Telephony.Threads; 39 import android.telephony.SmsManager; 40 import android.telephony.SmsMessage; 41 import android.text.TextUtils; 42 import android.util.Log; 43 44 import java.util.ArrayList; 45 import java.util.HashMap; 46 47 public class SmsProvider extends ContentProvider { 48 private static final Uri NOTIFICATION_URI = Uri.parse("content://sms"); 49 private static final Uri ICC_URI = Uri.parse("content://sms/icc"); 50 static final String TABLE_SMS = "sms"; 51 static final String TABLE_RAW = "raw"; 52 private static final String TABLE_SR_PENDING = "sr_pending"; 53 private static final String TABLE_WORDS = "words"; 54 55 private static final Integer ONE = Integer.valueOf(1); 56 57 private static final String[] CONTACT_QUERY_PROJECTION = 58 new String[] { Contacts.Phones.PERSON_ID }; 59 private static final int PERSON_ID_COLUMN = 0; 60 61 /** 62 * These are the columns that are available when reading SMS 63 * messages from the ICC. Columns whose names begin with "is_" 64 * have either "true" or "false" as their values. 65 */ 66 private final static String[] ICC_COLUMNS = new String[] { 67 // N.B.: These columns must appear in the same order as the 68 // calls to add appear in convertIccToSms. 69 "service_center_address", // getServiceCenterAddress 70 "address", // getDisplayOriginatingAddress 71 "message_class", // getMessageClass 72 "body", // getDisplayMessageBody 73 "date", // getTimestampMillis 74 "status", // getStatusOnIcc 75 "index_on_icc", // getIndexOnIcc 76 "is_status_report", // isStatusReportMessage 77 "transport_type", // Always "sms". 78 "type", // Always MESSAGE_TYPE_ALL. 79 "locked", // Always 0 (false). 80 "error_code", // Always 0 81 "_id" 82 }; 83 84 @Override 85 public boolean onCreate() { 86 setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS); 87 mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext()); 88 return true; 89 } 90 91 @Override 92 public Cursor query(Uri url, String[] projectionIn, String selection, 93 String[] selectionArgs, String sort) { 94 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 95 96 // Generate the body of the query. 97 int match = sURLMatcher.match(url); 98 switch (match) { 99 case SMS_ALL: 100 constructQueryForBox(qb, Sms.MESSAGE_TYPE_ALL); 101 break; 102 103 case SMS_UNDELIVERED: 104 constructQueryForUndelivered(qb); 105 break; 106 107 case SMS_FAILED: 108 constructQueryForBox(qb, Sms.MESSAGE_TYPE_FAILED); 109 break; 110 111 case SMS_QUEUED: 112 constructQueryForBox(qb, Sms.MESSAGE_TYPE_QUEUED); 113 break; 114 115 case SMS_INBOX: 116 constructQueryForBox(qb, Sms.MESSAGE_TYPE_INBOX); 117 break; 118 119 case SMS_SENT: 120 constructQueryForBox(qb, Sms.MESSAGE_TYPE_SENT); 121 break; 122 123 case SMS_DRAFT: 124 constructQueryForBox(qb, Sms.MESSAGE_TYPE_DRAFT); 125 break; 126 127 case SMS_OUTBOX: 128 constructQueryForBox(qb, Sms.MESSAGE_TYPE_OUTBOX); 129 break; 130 131 case SMS_ALL_ID: 132 qb.setTables(TABLE_SMS); 133 qb.appendWhere("(_id = " + url.getPathSegments().get(0) + ")"); 134 break; 135 136 case SMS_INBOX_ID: 137 case SMS_FAILED_ID: 138 case SMS_SENT_ID: 139 case SMS_DRAFT_ID: 140 case SMS_OUTBOX_ID: 141 qb.setTables(TABLE_SMS); 142 qb.appendWhere("(_id = " + url.getPathSegments().get(1) + ")"); 143 break; 144 145 case SMS_CONVERSATIONS_ID: 146 int threadID; 147 148 try { 149 threadID = Integer.parseInt(url.getPathSegments().get(1)); 150 if (Log.isLoggable(TAG, Log.VERBOSE)) { 151 Log.d(TAG, "query conversations: threadID=" + threadID); 152 } 153 } 154 catch (Exception ex) { 155 Log.e(TAG, 156 "Bad conversation thread id: " 157 + url.getPathSegments().get(1)); 158 return null; 159 } 160 161 qb.setTables(TABLE_SMS); 162 qb.appendWhere("thread_id = " + threadID); 163 break; 164 165 case SMS_CONVERSATIONS: 166 qb.setTables("sms, (SELECT thread_id AS group_thread_id, MAX(date)AS group_date," 167 + "COUNT(*) AS msg_count FROM sms GROUP BY thread_id) AS groups"); 168 qb.appendWhere("sms.thread_id = groups.group_thread_id AND sms.date =" 169 + "groups.group_date"); 170 qb.setProjectionMap(sConversationProjectionMap); 171 break; 172 173 case SMS_RAW_MESSAGE: 174 qb.setTables("raw"); 175 break; 176 177 case SMS_STATUS_PENDING: 178 qb.setTables("sr_pending"); 179 break; 180 181 case SMS_ATTACHMENT: 182 qb.setTables("attachments"); 183 break; 184 185 case SMS_ATTACHMENT_ID: 186 qb.setTables("attachments"); 187 qb.appendWhere( 188 "(sms_id = " + url.getPathSegments().get(1) + ")"); 189 break; 190 191 case SMS_QUERY_THREAD_ID: 192 qb.setTables("canonical_addresses"); 193 if (projectionIn == null) { 194 projectionIn = sIDProjection; 195 } 196 break; 197 198 case SMS_STATUS_ID: 199 qb.setTables(TABLE_SMS); 200 qb.appendWhere("(_id = " + url.getPathSegments().get(1) + ")"); 201 break; 202 203 case SMS_ALL_ICC: 204 return getAllMessagesFromIcc(); 205 206 case SMS_ICC: 207 String messageIndexString = url.getPathSegments().get(1); 208 209 return getSingleMessageFromIcc(messageIndexString); 210 211 default: 212 Log.e(TAG, "Invalid request: " + url); 213 return null; 214 } 215 216 String orderBy = null; 217 218 if (!TextUtils.isEmpty(sort)) { 219 orderBy = sort; 220 } else if (qb.getTables().equals(TABLE_SMS)) { 221 orderBy = Sms.DEFAULT_SORT_ORDER; 222 } 223 224 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 225 Cursor ret = qb.query(db, projectionIn, selection, selectionArgs, 226 null, null, orderBy); 227 228 // TODO: Since the URLs are a mess, always use content://sms 229 ret.setNotificationUri(getContext().getContentResolver(), 230 NOTIFICATION_URI); 231 return ret; 232 } 233 234 private Object[] convertIccToSms(SmsMessage message, int id) { 235 // N.B.: These calls must appear in the same order as the 236 // columns appear in ICC_COLUMNS. 237 Object[] row = new Object[13]; 238 row[0] = message.getServiceCenterAddress(); 239 row[1] = message.getDisplayOriginatingAddress(); 240 row[2] = String.valueOf(message.getMessageClass()); 241 row[3] = message.getDisplayMessageBody(); 242 row[4] = message.getTimestampMillis(); 243 row[5] = Sms.STATUS_NONE; 244 row[6] = message.getIndexOnIcc(); 245 row[7] = message.isStatusReportMessage(); 246 row[8] = "sms"; 247 row[9] = TextBasedSmsColumns.MESSAGE_TYPE_ALL; 248 row[10] = 0; // locked 249 row[11] = 0; // error_code 250 row[12] = id; 251 return row; 252 } 253 254 /** 255 * Return a Cursor containing just one message from the ICC. 256 */ 257 private Cursor getSingleMessageFromIcc(String messageIndexString) { 258 int messageIndex = -1; 259 try { 260 Integer.parseInt(messageIndexString); 261 } catch (NumberFormatException exception) { 262 throw new IllegalArgumentException("Bad SMS ICC ID: " + messageIndexString); 263 } 264 ArrayList<SmsMessage> messages; 265 final SmsManager smsManager = SmsManager.getDefault(); 266 // Use phone id to avoid AppOps uid mismatch in telephony 267 long token = Binder.clearCallingIdentity(); 268 try { 269 messages = smsManager.getAllMessagesFromIcc(); 270 } finally { 271 Binder.restoreCallingIdentity(token); 272 } 273 if (messages == null) { 274 throw new IllegalArgumentException("ICC message not retrieved"); 275 } 276 final SmsMessage message = messages.get(messageIndex); 277 if (message == null) { 278 throw new IllegalArgumentException( 279 "Message not retrieved. ID: " + messageIndexString); 280 } 281 MatrixCursor cursor = new MatrixCursor(ICC_COLUMNS, 1); 282 cursor.addRow(convertIccToSms(message, 0)); 283 return withIccNotificationUri(cursor); 284 } 285 286 /** 287 * Return a Cursor listing all the messages stored on the ICC. 288 */ 289 private Cursor getAllMessagesFromIcc() { 290 SmsManager smsManager = SmsManager.getDefault(); 291 ArrayList<SmsMessage> messages; 292 293 // use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call 294 long token = Binder.clearCallingIdentity(); 295 try { 296 messages = smsManager.getAllMessagesFromIcc(); 297 } finally { 298 Binder.restoreCallingIdentity(token); 299 } 300 301 final int count = messages.size(); 302 MatrixCursor cursor = new MatrixCursor(ICC_COLUMNS, count); 303 for (int i = 0; i < count; i++) { 304 SmsMessage message = messages.get(i); 305 if (message != null) { 306 cursor.addRow(convertIccToSms(message, i)); 307 } 308 } 309 return withIccNotificationUri(cursor); 310 } 311 312 private Cursor withIccNotificationUri(Cursor cursor) { 313 cursor.setNotificationUri(getContext().getContentResolver(), ICC_URI); 314 return cursor; 315 } 316 317 private void constructQueryForBox(SQLiteQueryBuilder qb, int type) { 318 qb.setTables(TABLE_SMS); 319 320 if (type != Sms.MESSAGE_TYPE_ALL) { 321 qb.appendWhere("type=" + type); 322 } 323 } 324 325 private void constructQueryForUndelivered(SQLiteQueryBuilder qb) { 326 qb.setTables(TABLE_SMS); 327 328 qb.appendWhere("(type=" + Sms.MESSAGE_TYPE_OUTBOX + 329 " OR type=" + Sms.MESSAGE_TYPE_FAILED + 330 " OR type=" + Sms.MESSAGE_TYPE_QUEUED + ")"); 331 } 332 333 @Override 334 public String getType(Uri url) { 335 switch (url.getPathSegments().size()) { 336 case 0: 337 return VND_ANDROID_DIR_SMS; 338 case 1: 339 try { 340 Integer.parseInt(url.getPathSegments().get(0)); 341 return VND_ANDROID_SMS; 342 } catch (NumberFormatException ex) { 343 return VND_ANDROID_DIR_SMS; 344 } 345 case 2: 346 // TODO: What about "threadID"? 347 if (url.getPathSegments().get(0).equals("conversations")) { 348 return VND_ANDROID_SMSCHAT; 349 } else { 350 return VND_ANDROID_SMS; 351 } 352 } 353 return null; 354 } 355 356 @Override 357 public Uri insert(Uri url, ContentValues initialValues) { 358 final int callerUid = Binder.getCallingUid(); 359 long token = Binder.clearCallingIdentity(); 360 try { 361 return insertInner(url, initialValues, callerUid); 362 } finally { 363 Binder.restoreCallingIdentity(token); 364 } 365 } 366 367 private Uri insertInner(Uri url, ContentValues initialValues, int callerUid) { 368 ContentValues values; 369 long rowID; 370 int type = Sms.MESSAGE_TYPE_ALL; 371 372 int match = sURLMatcher.match(url); 373 String table = TABLE_SMS; 374 375 switch (match) { 376 case SMS_ALL: 377 Integer typeObj = initialValues.getAsInteger(Sms.TYPE); 378 if (typeObj != null) { 379 type = typeObj.intValue(); 380 } else { 381 // default to inbox 382 type = Sms.MESSAGE_TYPE_INBOX; 383 } 384 break; 385 386 case SMS_INBOX: 387 type = Sms.MESSAGE_TYPE_INBOX; 388 break; 389 390 case SMS_FAILED: 391 type = Sms.MESSAGE_TYPE_FAILED; 392 break; 393 394 case SMS_QUEUED: 395 type = Sms.MESSAGE_TYPE_QUEUED; 396 break; 397 398 case SMS_SENT: 399 type = Sms.MESSAGE_TYPE_SENT; 400 break; 401 402 case SMS_DRAFT: 403 type = Sms.MESSAGE_TYPE_DRAFT; 404 break; 405 406 case SMS_OUTBOX: 407 type = Sms.MESSAGE_TYPE_OUTBOX; 408 break; 409 410 case SMS_RAW_MESSAGE: 411 table = "raw"; 412 break; 413 414 case SMS_STATUS_PENDING: 415 table = "sr_pending"; 416 break; 417 418 case SMS_ATTACHMENT: 419 table = "attachments"; 420 break; 421 422 case SMS_NEW_THREAD_ID: 423 table = "canonical_addresses"; 424 break; 425 426 default: 427 Log.e(TAG, "Invalid request: " + url); 428 return null; 429 } 430 431 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 432 433 if (table.equals(TABLE_SMS)) { 434 boolean addDate = false; 435 boolean addType = false; 436 437 // Make sure that the date and type are set 438 if (initialValues == null) { 439 values = new ContentValues(1); 440 addDate = true; 441 addType = true; 442 } else { 443 values = new ContentValues(initialValues); 444 445 if (!initialValues.containsKey(Sms.DATE)) { 446 addDate = true; 447 } 448 449 if (!initialValues.containsKey(Sms.TYPE)) { 450 addType = true; 451 } 452 } 453 454 if (addDate) { 455 values.put(Sms.DATE, new Long(System.currentTimeMillis())); 456 } 457 458 if (addType && (type != Sms.MESSAGE_TYPE_ALL)) { 459 values.put(Sms.TYPE, Integer.valueOf(type)); 460 } 461 462 // thread_id 463 Long threadId = values.getAsLong(Sms.THREAD_ID); 464 String address = values.getAsString(Sms.ADDRESS); 465 466 if (((threadId == null) || (threadId == 0)) && (!TextUtils.isEmpty(address))) { 467 values.put(Sms.THREAD_ID, Threads.getOrCreateThreadId( 468 getContext(), address)); 469 } 470 471 // If this message is going in as a draft, it should replace any 472 // other draft messages in the thread. Just delete all draft 473 // messages with this thread ID. We could add an OR REPLACE to 474 // the insert below, but we'd have to query to find the old _id 475 // to produce a conflict anyway. 476 if (values.getAsInteger(Sms.TYPE) == Sms.MESSAGE_TYPE_DRAFT) { 477 db.delete(TABLE_SMS, "thread_id=? AND type=?", 478 new String[] { values.getAsString(Sms.THREAD_ID), 479 Integer.toString(Sms.MESSAGE_TYPE_DRAFT) }); 480 } 481 482 if (type == Sms.MESSAGE_TYPE_INBOX) { 483 // Look up the person if not already filled in. 484 if ((values.getAsLong(Sms.PERSON) == null) && (!TextUtils.isEmpty(address))) { 485 Cursor cursor = null; 486 Uri uri = Uri.withAppendedPath(Contacts.Phones.CONTENT_FILTER_URL, 487 Uri.encode(address)); 488 try { 489 cursor = getContext().getContentResolver().query( 490 uri, 491 CONTACT_QUERY_PROJECTION, 492 null, null, null); 493 494 if (cursor.moveToFirst()) { 495 Long id = Long.valueOf(cursor.getLong(PERSON_ID_COLUMN)); 496 values.put(Sms.PERSON, id); 497 } 498 } catch (Exception ex) { 499 Log.e(TAG, "insert: query contact uri " + uri + " caught ", ex); 500 } finally { 501 if (cursor != null) { 502 cursor.close(); 503 } 504 } 505 } 506 } else { 507 // Mark all non-inbox messages read. 508 values.put(Sms.READ, ONE); 509 } 510 if (ProviderUtil.shouldSetCreator(values, callerUid)) { 511 // Only SYSTEM or PHONE can set CREATOR 512 // If caller is not SYSTEM or PHONE, or SYSTEM or PHONE does not set CREATOR 513 // set CREATOR using the truth on caller. 514 // Note: Inferring package name from UID may include unrelated package names 515 values.put(Sms.CREATOR, ProviderUtil.getPackageNamesByUid(getContext(), callerUid)); 516 } 517 } else { 518 if (initialValues == null) { 519 values = new ContentValues(1); 520 } else { 521 values = initialValues; 522 } 523 } 524 525 rowID = db.insert(table, "body", values); 526 527 // Don't use a trigger for updating the words table because of a bug 528 // in FTS3. The bug is such that the call to get the last inserted 529 // row is incorrect. 530 if (table == TABLE_SMS) { 531 // Update the words table with a corresponding row. The words table 532 // allows us to search for words quickly, without scanning the whole 533 // table; 534 ContentValues cv = new ContentValues(); 535 cv.put(Telephony.MmsSms.WordsTable.ID, rowID); 536 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("body")); 537 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowID); 538 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 1); 539 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv); 540 } 541 if (rowID > 0) { 542 Uri uri = Uri.parse("content://" + table + "/" + rowID); 543 544 if (Log.isLoggable(TAG, Log.VERBOSE)) { 545 Log.d(TAG, "insert " + uri + " succeeded"); 546 } 547 notifyChange(uri); 548 return uri; 549 } else { 550 Log.e(TAG,"insert: failed!"); 551 } 552 553 return null; 554 } 555 556 @Override 557 public int delete(Uri url, String where, String[] whereArgs) { 558 int count; 559 int match = sURLMatcher.match(url); 560 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 561 switch (match) { 562 case SMS_ALL: 563 count = db.delete(TABLE_SMS, where, whereArgs); 564 if (count != 0) { 565 // Don't update threads unless something changed. 566 MmsSmsDatabaseHelper.updateAllThreads(db, where, whereArgs); 567 } 568 break; 569 570 case SMS_ALL_ID: 571 try { 572 int message_id = Integer.parseInt(url.getPathSegments().get(0)); 573 count = MmsSmsDatabaseHelper.deleteOneSms(db, message_id); 574 } catch (Exception e) { 575 throw new IllegalArgumentException( 576 "Bad message id: " + url.getPathSegments().get(0)); 577 } 578 break; 579 580 case SMS_CONVERSATIONS_ID: 581 int threadID; 582 583 try { 584 threadID = Integer.parseInt(url.getPathSegments().get(1)); 585 } catch (Exception ex) { 586 throw new IllegalArgumentException( 587 "Bad conversation thread id: " 588 + url.getPathSegments().get(1)); 589 } 590 591 // delete the messages from the sms table 592 where = DatabaseUtils.concatenateWhere("thread_id=" + threadID, where); 593 count = db.delete(TABLE_SMS, where, whereArgs); 594 MmsSmsDatabaseHelper.updateThread(db, threadID); 595 break; 596 597 case SMS_RAW_MESSAGE: 598 count = db.delete("raw", where, whereArgs); 599 break; 600 601 case SMS_STATUS_PENDING: 602 count = db.delete("sr_pending", where, whereArgs); 603 break; 604 605 case SMS_ICC: 606 String messageIndexString = url.getPathSegments().get(1); 607 608 return deleteMessageFromIcc(messageIndexString); 609 610 default: 611 throw new IllegalArgumentException("Unknown URL"); 612 } 613 614 if (count > 0) { 615 notifyChange(url); 616 } 617 return count; 618 } 619 620 /** 621 * Delete the message at index from ICC. Return true iff 622 * successful. 623 */ 624 private int deleteMessageFromIcc(String messageIndexString) { 625 SmsManager smsManager = SmsManager.getDefault(); 626 // Use phone id to avoid AppOps uid mismatch in telephony 627 long token = Binder.clearCallingIdentity(); 628 try { 629 return smsManager.deleteMessageFromIcc( 630 Integer.parseInt(messageIndexString)) 631 ? 1 : 0; 632 } catch (NumberFormatException exception) { 633 throw new IllegalArgumentException( 634 "Bad SMS ICC ID: " + messageIndexString); 635 } finally { 636 ContentResolver cr = getContext().getContentResolver(); 637 cr.notifyChange(ICC_URI, null, true, UserHandle.USER_ALL); 638 639 Binder.restoreCallingIdentity(token); 640 } 641 } 642 643 @Override 644 public int update(Uri url, ContentValues values, String where, String[] whereArgs) { 645 final int callerUid = Binder.getCallingUid(); 646 int count = 0; 647 String table = TABLE_SMS; 648 String extraWhere = null; 649 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 650 651 switch (sURLMatcher.match(url)) { 652 case SMS_RAW_MESSAGE: 653 table = TABLE_RAW; 654 break; 655 656 case SMS_STATUS_PENDING: 657 table = TABLE_SR_PENDING; 658 break; 659 660 case SMS_ALL: 661 case SMS_FAILED: 662 case SMS_QUEUED: 663 case SMS_INBOX: 664 case SMS_SENT: 665 case SMS_DRAFT: 666 case SMS_OUTBOX: 667 case SMS_CONVERSATIONS: 668 break; 669 670 case SMS_ALL_ID: 671 extraWhere = "_id=" + url.getPathSegments().get(0); 672 break; 673 674 case SMS_INBOX_ID: 675 case SMS_FAILED_ID: 676 case SMS_SENT_ID: 677 case SMS_DRAFT_ID: 678 case SMS_OUTBOX_ID: 679 extraWhere = "_id=" + url.getPathSegments().get(1); 680 break; 681 682 case SMS_CONVERSATIONS_ID: { 683 String threadId = url.getPathSegments().get(1); 684 685 try { 686 Integer.parseInt(threadId); 687 } catch (Exception ex) { 688 Log.e(TAG, "Bad conversation thread id: " + threadId); 689 break; 690 } 691 692 extraWhere = "thread_id=" + threadId; 693 break; 694 } 695 696 case SMS_STATUS_ID: 697 extraWhere = "_id=" + url.getPathSegments().get(1); 698 break; 699 700 default: 701 throw new UnsupportedOperationException( 702 "URI " + url + " not supported"); 703 } 704 705 if (table.equals(TABLE_SMS) && ProviderUtil.shouldRemoveCreator(values, callerUid)) { 706 // CREATOR should not be changed by non-SYSTEM/PHONE apps 707 Log.w(TAG, ProviderUtil.getPackageNamesByUid(getContext(), callerUid) + 708 " tries to update CREATOR"); 709 values.remove(Sms.CREATOR); 710 } 711 712 where = DatabaseUtils.concatenateWhere(where, extraWhere); 713 count = db.update(table, values, where, whereArgs); 714 715 if (count > 0) { 716 if (Log.isLoggable(TAG, Log.VERBOSE)) { 717 Log.d(TAG, "update " + url + " succeeded"); 718 } 719 notifyChange(url); 720 } 721 return count; 722 } 723 724 private void notifyChange(Uri uri) { 725 ContentResolver cr = getContext().getContentResolver(); 726 cr.notifyChange(uri, null, true, UserHandle.USER_ALL); 727 cr.notifyChange(MmsSms.CONTENT_URI, null, true, UserHandle.USER_ALL); 728 cr.notifyChange(Uri.parse("content://mms-sms/conversations/"), null, true, 729 UserHandle.USER_ALL); 730 } 731 732 private SQLiteOpenHelper mOpenHelper; 733 734 private final static String TAG = "SmsProvider"; 735 private final static String VND_ANDROID_SMS = "vnd.android.cursor.item/sms"; 736 private final static String VND_ANDROID_SMSCHAT = 737 "vnd.android.cursor.item/sms-chat"; 738 private final static String VND_ANDROID_DIR_SMS = 739 "vnd.android.cursor.dir/sms"; 740 741 private static final HashMap<String, String> sConversationProjectionMap = 742 new HashMap<String, String>(); 743 private static final String[] sIDProjection = new String[] { "_id" }; 744 745 private static final int SMS_ALL = 0; 746 private static final int SMS_ALL_ID = 1; 747 private static final int SMS_INBOX = 2; 748 private static final int SMS_INBOX_ID = 3; 749 private static final int SMS_SENT = 4; 750 private static final int SMS_SENT_ID = 5; 751 private static final int SMS_DRAFT = 6; 752 private static final int SMS_DRAFT_ID = 7; 753 private static final int SMS_OUTBOX = 8; 754 private static final int SMS_OUTBOX_ID = 9; 755 private static final int SMS_CONVERSATIONS = 10; 756 private static final int SMS_CONVERSATIONS_ID = 11; 757 private static final int SMS_RAW_MESSAGE = 15; 758 private static final int SMS_ATTACHMENT = 16; 759 private static final int SMS_ATTACHMENT_ID = 17; 760 private static final int SMS_NEW_THREAD_ID = 18; 761 private static final int SMS_QUERY_THREAD_ID = 19; 762 private static final int SMS_STATUS_ID = 20; 763 private static final int SMS_STATUS_PENDING = 21; 764 private static final int SMS_ALL_ICC = 22; 765 private static final int SMS_ICC = 23; 766 private static final int SMS_FAILED = 24; 767 private static final int SMS_FAILED_ID = 25; 768 private static final int SMS_QUEUED = 26; 769 private static final int SMS_UNDELIVERED = 27; 770 771 private static final UriMatcher sURLMatcher = 772 new UriMatcher(UriMatcher.NO_MATCH); 773 774 static { 775 sURLMatcher.addURI("sms", null, SMS_ALL); 776 sURLMatcher.addURI("sms", "#", SMS_ALL_ID); 777 sURLMatcher.addURI("sms", "inbox", SMS_INBOX); 778 sURLMatcher.addURI("sms", "inbox/#", SMS_INBOX_ID); 779 sURLMatcher.addURI("sms", "sent", SMS_SENT); 780 sURLMatcher.addURI("sms", "sent/#", SMS_SENT_ID); 781 sURLMatcher.addURI("sms", "draft", SMS_DRAFT); 782 sURLMatcher.addURI("sms", "draft/#", SMS_DRAFT_ID); 783 sURLMatcher.addURI("sms", "outbox", SMS_OUTBOX); 784 sURLMatcher.addURI("sms", "outbox/#", SMS_OUTBOX_ID); 785 sURLMatcher.addURI("sms", "undelivered", SMS_UNDELIVERED); 786 sURLMatcher.addURI("sms", "failed", SMS_FAILED); 787 sURLMatcher.addURI("sms", "failed/#", SMS_FAILED_ID); 788 sURLMatcher.addURI("sms", "queued", SMS_QUEUED); 789 sURLMatcher.addURI("sms", "conversations", SMS_CONVERSATIONS); 790 sURLMatcher.addURI("sms", "conversations/*", SMS_CONVERSATIONS_ID); 791 sURLMatcher.addURI("sms", "raw", SMS_RAW_MESSAGE); 792 sURLMatcher.addURI("sms", "attachments", SMS_ATTACHMENT); 793 sURLMatcher.addURI("sms", "attachments/#", SMS_ATTACHMENT_ID); 794 sURLMatcher.addURI("sms", "threadID", SMS_NEW_THREAD_ID); 795 sURLMatcher.addURI("sms", "threadID/*", SMS_QUERY_THREAD_ID); 796 sURLMatcher.addURI("sms", "status/#", SMS_STATUS_ID); 797 sURLMatcher.addURI("sms", "sr_pending", SMS_STATUS_PENDING); 798 sURLMatcher.addURI("sms", "icc", SMS_ALL_ICC); 799 sURLMatcher.addURI("sms", "icc/#", SMS_ICC); 800 //we keep these for not breaking old applications 801 sURLMatcher.addURI("sms", "sim", SMS_ALL_ICC); 802 sURLMatcher.addURI("sms", "sim/#", SMS_ICC); 803 804 sConversationProjectionMap.put(Sms.Conversations.SNIPPET, 805 "sms.body AS snippet"); 806 sConversationProjectionMap.put(Sms.Conversations.THREAD_ID, 807 "sms.thread_id AS thread_id"); 808 sConversationProjectionMap.put(Sms.Conversations.MESSAGE_COUNT, 809 "groups.msg_count AS msg_count"); 810 sConversationProjectionMap.put("delta", null); 811 } 812 } 813