1 /* 2 * Copyright (C) 2007 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.content.ContentProvider; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.UriMatcher; 24 import android.database.Cursor; 25 import android.database.sqlite.SQLiteDatabase; 26 import android.database.sqlite.SQLiteOpenHelper; 27 import android.database.sqlite.SQLiteQueryBuilder; 28 import android.net.Uri; 29 import android.os.FileUtils; 30 import android.os.ParcelFileDescriptor; 31 import android.provider.BaseColumns; 32 import android.provider.Telephony; 33 import android.provider.Telephony.CanonicalAddressesColumns; 34 import android.provider.Telephony.Mms; 35 import android.provider.Telephony.MmsSms; 36 import android.provider.Telephony.Mms.Addr; 37 import android.provider.Telephony.Mms.Part; 38 import android.provider.Telephony.Mms.Rate; 39 import android.text.TextUtils; 40 import android.util.Log; 41 42 43 import com.google.android.mms.pdu.PduHeaders; 44 import com.google.android.mms.util.DownloadDrmHelper; 45 46 import java.io.File; 47 import java.io.FileNotFoundException; 48 import java.io.IOException; 49 import android.provider.Telephony.Threads; 50 51 /** 52 * The class to provide base facility to access MMS related content, 53 * which is stored in a SQLite database and in the file system. 54 */ 55 public class MmsProvider extends ContentProvider { 56 static final String TABLE_PDU = "pdu"; 57 static final String TABLE_ADDR = "addr"; 58 static final String TABLE_PART = "part"; 59 static final String TABLE_RATE = "rate"; 60 static final String TABLE_DRM = "drm"; 61 static final String TABLE_WORDS = "words"; 62 63 64 @Override 65 public boolean onCreate() { 66 mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext()); 67 return true; 68 } 69 70 @Override 71 public Cursor query(Uri uri, String[] projection, 72 String selection, String[] selectionArgs, String sortOrder) { 73 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 74 75 // Generate the body of the query. 76 int match = sURLMatcher.match(uri); 77 if (LOCAL_LOGV) { 78 Log.v(TAG, "Query uri=" + uri + ", match=" + match); 79 } 80 81 switch (match) { 82 case MMS_ALL: 83 constructQueryForBox(qb, Mms.MESSAGE_BOX_ALL); 84 break; 85 case MMS_INBOX: 86 constructQueryForBox(qb, Mms.MESSAGE_BOX_INBOX); 87 break; 88 case MMS_SENT: 89 constructQueryForBox(qb, Mms.MESSAGE_BOX_SENT); 90 break; 91 case MMS_DRAFTS: 92 constructQueryForBox(qb, Mms.MESSAGE_BOX_DRAFTS); 93 break; 94 case MMS_OUTBOX: 95 constructQueryForBox(qb, Mms.MESSAGE_BOX_OUTBOX); 96 break; 97 case MMS_ALL_ID: 98 qb.setTables(TABLE_PDU); 99 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(0)); 100 break; 101 case MMS_INBOX_ID: 102 case MMS_SENT_ID: 103 case MMS_DRAFTS_ID: 104 case MMS_OUTBOX_ID: 105 qb.setTables(TABLE_PDU); 106 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(1)); 107 qb.appendWhere(" AND " + Mms.MESSAGE_BOX + "=" 108 + getMessageBoxByMatch(match)); 109 break; 110 case MMS_ALL_PART: 111 qb.setTables(TABLE_PART); 112 break; 113 case MMS_MSG_PART: 114 qb.setTables(TABLE_PART); 115 qb.appendWhere(Part.MSG_ID + "=" + uri.getPathSegments().get(0)); 116 break; 117 case MMS_PART_ID: 118 qb.setTables(TABLE_PART); 119 qb.appendWhere(Part._ID + "=" + uri.getPathSegments().get(1)); 120 break; 121 case MMS_MSG_ADDR: 122 qb.setTables(TABLE_ADDR); 123 qb.appendWhere(Addr.MSG_ID + "=" + uri.getPathSegments().get(0)); 124 break; 125 case MMS_REPORT_STATUS: 126 /* 127 SELECT DISTINCT address, 128 T.delivery_status AS delivery_status, 129 T.read_status AS read_status 130 FROM addr 131 INNER JOIN (SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3, 132 ifnull(P2.st, 0) AS delivery_status, 133 ifnull(P3.read_status, 0) AS read_status 134 FROM pdu P1 135 INNER JOIN pdu P2 136 ON P1.m_id = P2.m_id AND P2.m_type = 134 137 LEFT JOIN pdu P3 138 ON P1.m_id = P3.m_id AND P3.m_type = 136 139 UNION 140 SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3, 141 ifnull(P2.st, 0) AS delivery_status, 142 ifnull(P3.read_status, 0) AS read_status 143 FROM pdu P1 144 INNER JOIN pdu P3 145 ON P1.m_id = P3.m_id AND P3.m_type = 136 146 LEFT JOIN pdu P2 147 ON P1.m_id = P2.m_id AND P2.m_type = 134) T 148 ON (msg_id = id2 AND type = 151) 149 OR (msg_id = id3 AND type = 137) 150 WHERE T.id1 = ?; 151 */ 152 qb.setTables("addr INNER JOIN (SELECT P1._id AS id1, P2._id" + 153 " AS id2, P3._id AS id3, ifnull(P2.st, 0) AS" + 154 " delivery_status, ifnull(P3.read_status, 0) AS" + 155 " read_status FROM pdu P1 INNER JOIN pdu P2 ON" + 156 " P1.m_id=P2.m_id AND P2.m_type=134 LEFT JOIN" + 157 " pdu P3 ON P1.m_id=P3.m_id AND P3.m_type=136" + 158 " UNION SELECT P1._id AS id1, P2._id AS id2, P3._id" + 159 " AS id3, ifnull(P2.st, 0) AS delivery_status," + 160 " ifnull(P3.read_status, 0) AS read_status FROM" + 161 " pdu P1 INNER JOIN pdu P3 ON P1.m_id=P3.m_id AND" + 162 " P3.m_type=136 LEFT JOIN pdu P2 ON P1.m_id=P2.m_id" + 163 " AND P2.m_type=134) T ON (msg_id=id2 AND type=151)" + 164 " OR (msg_id=id3 AND type=137)"); 165 qb.appendWhere("T.id1 = " + uri.getLastPathSegment()); 166 qb.setDistinct(true); 167 break; 168 case MMS_REPORT_REQUEST: 169 /* 170 SELECT address, d_rpt, rr 171 FROM addr join pdu on pdu._id = addr.msg_id 172 WHERE pdu._id = messageId AND addr.type = 151 173 */ 174 qb.setTables(TABLE_ADDR + " join " + 175 TABLE_PDU + " on pdu._id = addr.msg_id"); 176 qb.appendWhere("pdu._id = " + uri.getLastPathSegment()); 177 qb.appendWhere(" AND " + "addr.type = " + PduHeaders.TO); 178 break; 179 case MMS_SENDING_RATE: 180 qb.setTables(TABLE_RATE); 181 break; 182 case MMS_DRM_STORAGE_ID: 183 qb.setTables(TABLE_DRM); 184 qb.appendWhere(BaseColumns._ID + "=" + uri.getLastPathSegment()); 185 break; 186 case MMS_THREADS: 187 qb.setTables("pdu group by thread_id"); 188 break; 189 default: 190 Log.e(TAG, "query: invalid request: " + uri); 191 return null; 192 } 193 194 String finalSortOrder = null; 195 if (TextUtils.isEmpty(sortOrder)) { 196 if (qb.getTables().equals(TABLE_PDU)) { 197 finalSortOrder = Mms.DATE + " DESC"; 198 } else if (qb.getTables().equals(TABLE_PART)) { 199 finalSortOrder = Part.SEQ; 200 } 201 } else { 202 finalSortOrder = sortOrder; 203 } 204 205 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 206 Cursor ret = qb.query(db, projection, selection, 207 selectionArgs, null, null, finalSortOrder); 208 209 // TODO: Does this need to be a URI for this provider. 210 ret.setNotificationUri(getContext().getContentResolver(), uri); 211 return ret; 212 } 213 214 private void constructQueryForBox(SQLiteQueryBuilder qb, int msgBox) { 215 qb.setTables(TABLE_PDU); 216 217 if (msgBox != Mms.MESSAGE_BOX_ALL) { 218 qb.appendWhere(Mms.MESSAGE_BOX + "=" + msgBox); 219 } 220 } 221 222 @Override 223 public String getType(Uri uri) { 224 int match = sURLMatcher.match(uri); 225 switch (match) { 226 case MMS_ALL: 227 case MMS_INBOX: 228 case MMS_SENT: 229 case MMS_DRAFTS: 230 case MMS_OUTBOX: 231 return VND_ANDROID_DIR_MMS; 232 case MMS_ALL_ID: 233 case MMS_INBOX_ID: 234 case MMS_SENT_ID: 235 case MMS_DRAFTS_ID: 236 case MMS_OUTBOX_ID: 237 return VND_ANDROID_MMS; 238 case MMS_PART_ID: { 239 Cursor cursor = mOpenHelper.getReadableDatabase().query( 240 TABLE_PART, new String[] { Part.CONTENT_TYPE }, 241 Part._ID + " = ?", new String[] { uri.getLastPathSegment() }, 242 null, null, null); 243 if (cursor != null) { 244 try { 245 if ((cursor.getCount() == 1) && cursor.moveToFirst()) { 246 return cursor.getString(0); 247 } else { 248 Log.e(TAG, "cursor.count() != 1: " + uri); 249 } 250 } finally { 251 cursor.close(); 252 } 253 } else { 254 Log.e(TAG, "cursor == null: " + uri); 255 } 256 return "*/*"; 257 } 258 case MMS_ALL_PART: 259 case MMS_MSG_PART: 260 case MMS_MSG_ADDR: 261 default: 262 return "*/*"; 263 } 264 } 265 266 @Override 267 public Uri insert(Uri uri, ContentValues values) { 268 int msgBox = Mms.MESSAGE_BOX_ALL; 269 boolean notify = true; 270 271 int match = sURLMatcher.match(uri); 272 if (LOCAL_LOGV) { 273 Log.v(TAG, "Insert uri=" + uri + ", match=" + match); 274 } 275 276 String table = TABLE_PDU; 277 switch (match) { 278 case MMS_ALL: 279 Object msgBoxObj = values.getAsInteger(Mms.MESSAGE_BOX); 280 if (msgBoxObj != null) { 281 msgBox = (Integer) msgBoxObj; 282 } 283 else { 284 // default to inbox 285 msgBox = Mms.MESSAGE_BOX_INBOX; 286 } 287 break; 288 case MMS_INBOX: 289 msgBox = Mms.MESSAGE_BOX_INBOX; 290 break; 291 case MMS_SENT: 292 msgBox = Mms.MESSAGE_BOX_SENT; 293 break; 294 case MMS_DRAFTS: 295 msgBox = Mms.MESSAGE_BOX_DRAFTS; 296 break; 297 case MMS_OUTBOX: 298 msgBox = Mms.MESSAGE_BOX_OUTBOX; 299 break; 300 case MMS_MSG_PART: 301 notify = false; 302 table = TABLE_PART; 303 break; 304 case MMS_MSG_ADDR: 305 notify = false; 306 table = TABLE_ADDR; 307 break; 308 case MMS_SENDING_RATE: 309 notify = false; 310 table = TABLE_RATE; 311 break; 312 case MMS_DRM_STORAGE: 313 notify = false; 314 table = TABLE_DRM; 315 break; 316 default: 317 Log.e(TAG, "insert: invalid request: " + uri); 318 return null; 319 } 320 321 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 322 ContentValues finalValues; 323 Uri res = Mms.CONTENT_URI; 324 long rowId; 325 326 if (table.equals(TABLE_PDU)) { 327 boolean addDate = !values.containsKey(Mms.DATE); 328 boolean addMsgBox = !values.containsKey(Mms.MESSAGE_BOX); 329 330 // Filter keys we don't support yet. 331 filterUnsupportedKeys(values); 332 333 // TODO: Should initialValues be validated, e.g. if it 334 // missed some significant keys? 335 finalValues = new ContentValues(values); 336 337 long timeInMillis = System.currentTimeMillis(); 338 339 if (addDate) { 340 finalValues.put(Mms.DATE, timeInMillis / 1000L); 341 } 342 343 if (addMsgBox && (msgBox != Mms.MESSAGE_BOX_ALL)) { 344 finalValues.put(Mms.MESSAGE_BOX, msgBox); 345 } 346 347 if (msgBox != Mms.MESSAGE_BOX_INBOX) { 348 // Mark all non-inbox messages read. 349 finalValues.put(Mms.READ, 1); 350 } 351 352 // thread_id 353 Long threadId = values.getAsLong(Mms.THREAD_ID); 354 String address = values.getAsString(CanonicalAddressesColumns.ADDRESS); 355 356 if (((threadId == null) || (threadId == 0)) && (!TextUtils.isEmpty(address))) { 357 finalValues.put(Mms.THREAD_ID, Threads.getOrCreateThreadId(getContext(), address)); 358 } 359 360 if ((rowId = db.insert(table, null, finalValues)) <= 0) { 361 Log.e(TAG, "MmsProvider.insert: failed! " + finalValues); 362 return null; 363 } 364 365 res = Uri.parse(res + "/" + rowId); 366 367 } else if (table.equals(TABLE_ADDR)) { 368 finalValues = new ContentValues(values); 369 finalValues.put(Addr.MSG_ID, uri.getPathSegments().get(0)); 370 371 if ((rowId = db.insert(table, null, finalValues)) <= 0) { 372 Log.e(TAG, "Failed to insert address: " + finalValues); 373 return null; 374 } 375 376 res = Uri.parse(res + "/addr/" + rowId); 377 } else if (table.equals(TABLE_PART)) { 378 finalValues = new ContentValues(values); 379 380 if (match == MMS_MSG_PART) { 381 finalValues.put(Part.MSG_ID, uri.getPathSegments().get(0)); 382 } 383 384 String contentType = values.getAsString("ct"); 385 386 // text/plain and app application/smil store their "data" inline in the 387 // table so there's no need to create the file 388 boolean plainText = false; 389 boolean smilText = false; 390 if ("text/plain".equals(contentType)) { 391 plainText = true; 392 } else if ("application/smil".equals(contentType)) { 393 smilText = true; 394 } 395 if (!plainText && !smilText) { 396 // Use the filename if possible, otherwise use the current time as the name. 397 String contentLocation = values.getAsString("cl"); 398 if (!TextUtils.isEmpty(contentLocation)) { 399 File f = new File(contentLocation); 400 contentLocation = "_" + f.getName(); 401 } else { 402 contentLocation = ""; 403 } 404 405 // Generate the '_data' field of the part with default 406 // permission settings. 407 String path = getContext().getDir("parts", 0).getPath() 408 + "/PART_" + System.currentTimeMillis() + contentLocation; 409 410 if (DownloadDrmHelper.isDrmConvertNeeded(contentType)) { 411 // Adds the .fl extension to the filename if contentType is 412 // "application/vnd.oma.drm.message" 413 path = DownloadDrmHelper.modifyDrmFwLockFileExtension(path); 414 } 415 416 finalValues.put(Part._DATA, path); 417 418 File partFile = new File(path); 419 if (!partFile.exists()) { 420 try { 421 if (!partFile.createNewFile()) { 422 throw new IllegalStateException( 423 "Unable to create new partFile: " + path); 424 } 425 // Give everyone rw permission until we encrypt the file 426 // (in PduPersister.persistData). Once the file is encrypted, the 427 // permissions will be set to 0644. 428 int result = FileUtils.setPermissions(path, 0666, -1, -1); 429 if (LOCAL_LOGV) { 430 Log.d(TAG, "MmsProvider.insert setPermissions result: " + result); 431 } 432 } catch (IOException e) { 433 Log.e(TAG, "createNewFile", e); 434 throw new IllegalStateException( 435 "Unable to create new partFile: " + path); 436 } 437 } 438 } 439 440 if ((rowId = db.insert(table, null, finalValues)) <= 0) { 441 Log.e(TAG, "MmsProvider.insert: failed! " + finalValues); 442 return null; 443 } 444 445 res = Uri.parse(res + "/part/" + rowId); 446 447 // Don't use a trigger for updating the words table because of a bug 448 // in FTS3. The bug is such that the call to get the last inserted 449 // row is incorrect. 450 if (plainText) { 451 // Update the words table with a corresponding row. The words table 452 // allows us to search for words quickly, without scanning the whole 453 // table; 454 ContentValues cv = new ContentValues(); 455 456 // we're using the row id of the part table row but we're also using ids 457 // from the sms table so this divides the space into two large chunks. 458 // The row ids from the part table start at 2 << 32. 459 cv.put(Telephony.MmsSms.WordsTable.ID, (2 << 32) + rowId); 460 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("text")); 461 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowId); 462 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 2); 463 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv); 464 } 465 466 } else if (table.equals(TABLE_RATE)) { 467 long now = values.getAsLong(Rate.SENT_TIME); 468 long oneHourAgo = now - 1000 * 60 * 60; 469 // Delete all unused rows (time earlier than one hour ago). 470 db.delete(table, Rate.SENT_TIME + "<=" + oneHourAgo, null); 471 db.insert(table, null, values); 472 } else if (table.equals(TABLE_DRM)) { 473 String path = getContext().getDir("parts", 0).getPath() 474 + "/PART_" + System.currentTimeMillis(); 475 finalValues = new ContentValues(1); 476 finalValues.put("_data", path); 477 478 File partFile = new File(path); 479 if (!partFile.exists()) { 480 try { 481 if (!partFile.createNewFile()) { 482 throw new IllegalStateException( 483 "Unable to create new file: " + path); 484 } 485 } catch (IOException e) { 486 Log.e(TAG, "createNewFile", e); 487 throw new IllegalStateException( 488 "Unable to create new file: " + path); 489 } 490 } 491 492 if ((rowId = db.insert(table, null, finalValues)) <= 0) { 493 Log.e(TAG, "MmsProvider.insert: failed! " + finalValues); 494 return null; 495 } 496 res = Uri.parse(res + "/drm/" + rowId); 497 } else { 498 throw new AssertionError("Unknown table type: " + table); 499 } 500 501 if (notify) { 502 notifyChange(); 503 } 504 return res; 505 } 506 507 private int getMessageBoxByMatch(int match) { 508 switch (match) { 509 case MMS_INBOX_ID: 510 case MMS_INBOX: 511 return Mms.MESSAGE_BOX_INBOX; 512 case MMS_SENT_ID: 513 case MMS_SENT: 514 return Mms.MESSAGE_BOX_SENT; 515 case MMS_DRAFTS_ID: 516 case MMS_DRAFTS: 517 return Mms.MESSAGE_BOX_DRAFTS; 518 case MMS_OUTBOX_ID: 519 case MMS_OUTBOX: 520 return Mms.MESSAGE_BOX_OUTBOX; 521 default: 522 throw new IllegalArgumentException("bad Arg: " + match); 523 } 524 } 525 526 @Override 527 public int delete(Uri uri, String selection, 528 String[] selectionArgs) { 529 int match = sURLMatcher.match(uri); 530 if (LOCAL_LOGV) { 531 Log.v(TAG, "Delete uri=" + uri + ", match=" + match); 532 } 533 534 String table, extraSelection = null; 535 boolean notify = false; 536 537 switch (match) { 538 case MMS_ALL_ID: 539 case MMS_INBOX_ID: 540 case MMS_SENT_ID: 541 case MMS_DRAFTS_ID: 542 case MMS_OUTBOX_ID: 543 notify = true; 544 table = TABLE_PDU; 545 extraSelection = Mms._ID + "=" + uri.getLastPathSegment(); 546 break; 547 case MMS_ALL: 548 case MMS_INBOX: 549 case MMS_SENT: 550 case MMS_DRAFTS: 551 case MMS_OUTBOX: 552 notify = true; 553 table = TABLE_PDU; 554 if (match != MMS_ALL) { 555 int msgBox = getMessageBoxByMatch(match); 556 extraSelection = Mms.MESSAGE_BOX + "=" + msgBox; 557 } 558 break; 559 case MMS_ALL_PART: 560 table = TABLE_PART; 561 break; 562 case MMS_MSG_PART: 563 table = TABLE_PART; 564 extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0); 565 break; 566 case MMS_PART_ID: 567 table = TABLE_PART; 568 extraSelection = Part._ID + "=" + uri.getPathSegments().get(1); 569 break; 570 case MMS_MSG_ADDR: 571 table = TABLE_ADDR; 572 extraSelection = Addr.MSG_ID + "=" + uri.getPathSegments().get(0); 573 break; 574 case MMS_DRM_STORAGE: 575 table = TABLE_DRM; 576 break; 577 default: 578 Log.w(TAG, "No match for URI '" + uri + "'"); 579 return 0; 580 } 581 582 String finalSelection = concatSelections(selection, extraSelection); 583 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 584 int deletedRows = 0; 585 586 if (TABLE_PDU.equals(table)) { 587 deletedRows = deleteMessages(getContext(), db, finalSelection, 588 selectionArgs, uri); 589 } else if (TABLE_PART.equals(table)) { 590 deletedRows = deleteParts(db, finalSelection, selectionArgs); 591 } else if (TABLE_DRM.equals(table)) { 592 deletedRows = deleteTempDrmData(db, finalSelection, selectionArgs); 593 } else { 594 deletedRows = db.delete(table, finalSelection, selectionArgs); 595 } 596 597 if ((deletedRows > 0) && notify) { 598 notifyChange(); 599 } 600 return deletedRows; 601 } 602 603 static int deleteMessages(Context context, SQLiteDatabase db, 604 String selection, String[] selectionArgs, Uri uri) { 605 Cursor cursor = db.query(TABLE_PDU, new String[] { Mms._ID }, 606 selection, selectionArgs, null, null, null); 607 if (cursor == null) { 608 return 0; 609 } 610 611 try { 612 if (cursor.getCount() == 0) { 613 return 0; 614 } 615 616 while (cursor.moveToNext()) { 617 deleteParts(db, Part.MSG_ID + " = ?", 618 new String[] { String.valueOf(cursor.getLong(0)) }); 619 } 620 } finally { 621 cursor.close(); 622 } 623 624 int count = db.delete(TABLE_PDU, selection, selectionArgs); 625 if (count > 0) { 626 Intent intent = new Intent(Mms.Intents.CONTENT_CHANGED_ACTION); 627 intent.putExtra(Mms.Intents.DELETED_CONTENTS, uri); 628 if (LOCAL_LOGV) { 629 Log.v(TAG, "Broadcasting intent: " + intent); 630 } 631 context.sendBroadcast(intent); 632 } 633 return count; 634 } 635 636 private static int deleteParts(SQLiteDatabase db, String selection, 637 String[] selectionArgs) { 638 return deleteDataRows(db, TABLE_PART, selection, selectionArgs); 639 } 640 641 private static int deleteTempDrmData(SQLiteDatabase db, String selection, 642 String[] selectionArgs) { 643 return deleteDataRows(db, TABLE_DRM, selection, selectionArgs); 644 } 645 646 private static int deleteDataRows(SQLiteDatabase db, String table, 647 String selection, String[] selectionArgs) { 648 Cursor cursor = db.query(table, new String[] { "_data" }, 649 selection, selectionArgs, null, null, null); 650 if (cursor == null) { 651 // FIXME: This might be an error, ignore it may cause 652 // unpredictable result. 653 return 0; 654 } 655 656 try { 657 if (cursor.getCount() == 0) { 658 return 0; 659 } 660 661 while (cursor.moveToNext()) { 662 try { 663 // Delete the associated files saved on file-system. 664 String path = cursor.getString(0); 665 if (path != null) { 666 new File(path).delete(); 667 } 668 } catch (Throwable ex) { 669 Log.e(TAG, ex.getMessage(), ex); 670 } 671 } 672 } finally { 673 cursor.close(); 674 } 675 676 return db.delete(table, selection, selectionArgs); 677 } 678 679 @Override 680 public int update(Uri uri, ContentValues values, 681 String selection, String[] selectionArgs) { 682 int match = sURLMatcher.match(uri); 683 if (LOCAL_LOGV) { 684 Log.v(TAG, "Update uri=" + uri + ", match=" + match); 685 } 686 687 boolean notify = false; 688 String msgId = null; 689 String table; 690 691 switch (match) { 692 case MMS_ALL_ID: 693 case MMS_INBOX_ID: 694 case MMS_SENT_ID: 695 case MMS_DRAFTS_ID: 696 case MMS_OUTBOX_ID: 697 msgId = uri.getLastPathSegment(); 698 // fall-through 699 case MMS_ALL: 700 case MMS_INBOX: 701 case MMS_SENT: 702 case MMS_DRAFTS: 703 case MMS_OUTBOX: 704 notify = true; 705 table = TABLE_PDU; 706 break; 707 708 case MMS_MSG_PART: 709 case MMS_PART_ID: 710 table = TABLE_PART; 711 break; 712 713 case MMS_PART_RESET_FILE_PERMISSION: 714 String path = getContext().getDir("parts", 0).getPath() + '/' + 715 uri.getPathSegments().get(1); 716 // Reset the file permission back to read for everyone but me. 717 int result = FileUtils.setPermissions(path, 0644, -1, -1); 718 if (LOCAL_LOGV) { 719 Log.d(TAG, "MmsProvider.update setPermissions result: " + result + 720 " for path: " + path); 721 } 722 return 0; 723 724 default: 725 Log.w(TAG, "Update operation for '" + uri + "' not implemented."); 726 return 0; 727 } 728 729 String extraSelection = null; 730 ContentValues finalValues; 731 if (table.equals(TABLE_PDU)) { 732 // Filter keys that we don't support yet. 733 filterUnsupportedKeys(values); 734 finalValues = new ContentValues(values); 735 736 if (msgId != null) { 737 extraSelection = Mms._ID + "=" + msgId; 738 } 739 } else if (table.equals(TABLE_PART)) { 740 finalValues = new ContentValues(values); 741 742 switch (match) { 743 case MMS_MSG_PART: 744 extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0); 745 break; 746 case MMS_PART_ID: 747 extraSelection = Part._ID + "=" + uri.getPathSegments().get(1); 748 break; 749 default: 750 break; 751 } 752 } else { 753 return 0; 754 } 755 756 String finalSelection = concatSelections(selection, extraSelection); 757 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 758 int count = db.update(table, finalValues, finalSelection, selectionArgs); 759 if (notify && (count > 0)) { 760 notifyChange(); 761 } 762 return count; 763 } 764 765 @Override 766 public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { 767 // TODO do we even need this anymore? 768 ParcelFileDescriptor fd; 769 int match = sURLMatcher.match(uri); 770 771 if (Log.isLoggable(TAG, Log.VERBOSE)) { 772 Log.d(TAG, "openFile: uri=" + uri + ", mode=" + mode); 773 } 774 775 switch (match) { 776 default: 777 fd = openFileHelper(uri, mode); 778 } 779 780 return fd; 781 } 782 783 private void filterUnsupportedKeys(ContentValues values) { 784 // Some columns are unsupported. They should therefore 785 // neither be inserted nor updated. Filter them out. 786 values.remove(Mms.DELIVERY_TIME_TOKEN); 787 values.remove(Mms.SENDER_VISIBILITY); 788 values.remove(Mms.REPLY_CHARGING); 789 values.remove(Mms.REPLY_CHARGING_DEADLINE_TOKEN); 790 values.remove(Mms.REPLY_CHARGING_DEADLINE); 791 values.remove(Mms.REPLY_CHARGING_ID); 792 values.remove(Mms.REPLY_CHARGING_SIZE); 793 values.remove(Mms.PREVIOUSLY_SENT_BY); 794 values.remove(Mms.PREVIOUSLY_SENT_DATE); 795 values.remove(Mms.STORE); 796 values.remove(Mms.MM_STATE); 797 values.remove(Mms.MM_FLAGS_TOKEN); 798 values.remove(Mms.MM_FLAGS); 799 values.remove(Mms.STORE_STATUS); 800 values.remove(Mms.STORE_STATUS_TEXT); 801 values.remove(Mms.STORED); 802 values.remove(Mms.TOTALS); 803 values.remove(Mms.MBOX_TOTALS); 804 values.remove(Mms.MBOX_TOTALS_TOKEN); 805 values.remove(Mms.QUOTAS); 806 values.remove(Mms.MBOX_QUOTAS); 807 values.remove(Mms.MBOX_QUOTAS_TOKEN); 808 values.remove(Mms.MESSAGE_COUNT); 809 values.remove(Mms.START); 810 values.remove(Mms.DISTRIBUTION_INDICATOR); 811 values.remove(Mms.ELEMENT_DESCRIPTOR); 812 values.remove(Mms.LIMIT); 813 values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE); 814 values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE_TEXT); 815 values.remove(Mms.STATUS_TEXT); 816 values.remove(Mms.APPLIC_ID); 817 values.remove(Mms.REPLY_APPLIC_ID); 818 values.remove(Mms.AUX_APPLIC_ID); 819 values.remove(Mms.DRM_CONTENT); 820 values.remove(Mms.ADAPTATION_ALLOWED); 821 values.remove(Mms.REPLACE_ID); 822 values.remove(Mms.CANCEL_ID); 823 values.remove(Mms.CANCEL_STATUS); 824 825 // Keys shouldn't be inserted or updated. 826 values.remove(Mms._ID); 827 } 828 829 private void notifyChange() { 830 getContext().getContentResolver().notifyChange( 831 MmsSms.CONTENT_URI, null); 832 } 833 834 private final static String TAG = "MmsProvider"; 835 private final static String VND_ANDROID_MMS = "vnd.android/mms"; 836 private final static String VND_ANDROID_DIR_MMS = "vnd.android-dir/mms"; 837 private final static boolean DEBUG = false; 838 private final static boolean LOCAL_LOGV = false; 839 840 private static final int MMS_ALL = 0; 841 private static final int MMS_ALL_ID = 1; 842 private static final int MMS_INBOX = 2; 843 private static final int MMS_INBOX_ID = 3; 844 private static final int MMS_SENT = 4; 845 private static final int MMS_SENT_ID = 5; 846 private static final int MMS_DRAFTS = 6; 847 private static final int MMS_DRAFTS_ID = 7; 848 private static final int MMS_OUTBOX = 8; 849 private static final int MMS_OUTBOX_ID = 9; 850 private static final int MMS_ALL_PART = 10; 851 private static final int MMS_MSG_PART = 11; 852 private static final int MMS_PART_ID = 12; 853 private static final int MMS_MSG_ADDR = 13; 854 private static final int MMS_SENDING_RATE = 14; 855 private static final int MMS_REPORT_STATUS = 15; 856 private static final int MMS_REPORT_REQUEST = 16; 857 private static final int MMS_DRM_STORAGE = 17; 858 private static final int MMS_DRM_STORAGE_ID = 18; 859 private static final int MMS_THREADS = 19; 860 private static final int MMS_PART_RESET_FILE_PERMISSION = 20; 861 862 private static final UriMatcher 863 sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH); 864 865 static { 866 sURLMatcher.addURI("mms", null, MMS_ALL); 867 sURLMatcher.addURI("mms", "#", MMS_ALL_ID); 868 sURLMatcher.addURI("mms", "inbox", MMS_INBOX); 869 sURLMatcher.addURI("mms", "inbox/#", MMS_INBOX_ID); 870 sURLMatcher.addURI("mms", "sent", MMS_SENT); 871 sURLMatcher.addURI("mms", "sent/#", MMS_SENT_ID); 872 sURLMatcher.addURI("mms", "drafts", MMS_DRAFTS); 873 sURLMatcher.addURI("mms", "drafts/#", MMS_DRAFTS_ID); 874 sURLMatcher.addURI("mms", "outbox", MMS_OUTBOX); 875 sURLMatcher.addURI("mms", "outbox/#", MMS_OUTBOX_ID); 876 sURLMatcher.addURI("mms", "part", MMS_ALL_PART); 877 sURLMatcher.addURI("mms", "#/part", MMS_MSG_PART); 878 sURLMatcher.addURI("mms", "part/#", MMS_PART_ID); 879 sURLMatcher.addURI("mms", "#/addr", MMS_MSG_ADDR); 880 sURLMatcher.addURI("mms", "rate", MMS_SENDING_RATE); 881 sURLMatcher.addURI("mms", "report-status/#", MMS_REPORT_STATUS); 882 sURLMatcher.addURI("mms", "report-request/#", MMS_REPORT_REQUEST); 883 sURLMatcher.addURI("mms", "drm", MMS_DRM_STORAGE); 884 sURLMatcher.addURI("mms", "drm/#", MMS_DRM_STORAGE_ID); 885 sURLMatcher.addURI("mms", "threads", MMS_THREADS); 886 sURLMatcher.addURI("mms", "resetFilePerm/*", MMS_PART_RESET_FILE_PERMISSION); 887 } 888 889 private SQLiteOpenHelper mOpenHelper; 890 891 private static String concatSelections(String selection1, String selection2) { 892 if (TextUtils.isEmpty(selection1)) { 893 return selection2; 894 } else if (TextUtils.isEmpty(selection2)) { 895 return selection1; 896 } else { 897 return selection1 + " AND " + selection2; 898 } 899 } 900 } 901 902