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