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.annotation.NonNull; 20 import android.app.AppOpsManager; 21 import android.content.BroadcastReceiver; 22 import android.content.ContentProvider; 23 import android.content.ContentUris; 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.SQLiteException; 34 import android.database.sqlite.SQLiteOpenHelper; 35 import android.database.sqlite.SQLiteQueryBuilder; 36 import android.net.Uri; 37 import android.os.Binder; 38 import android.os.ParcelFileDescriptor; 39 import android.os.UserHandle; 40 import android.os.UserManager; 41 import android.provider.BaseColumns; 42 import android.provider.Telephony; 43 import android.provider.Telephony.CanonicalAddressesColumns; 44 import android.provider.Telephony.Mms; 45 import android.provider.Telephony.Mms.Addr; 46 import android.provider.Telephony.Mms.Part; 47 import android.provider.Telephony.Mms.Rate; 48 import android.provider.Telephony.MmsSms; 49 import android.provider.Telephony.Threads; 50 import android.system.ErrnoException; 51 import android.system.Os; 52 import android.telephony.SmsManager; 53 import android.telephony.SubscriptionManager; 54 import android.text.TextUtils; 55 import android.util.EventLog; 56 import android.util.Log; 57 58 import com.android.internal.annotations.VisibleForTesting; 59 import com.android.internal.telephony.util.TelephonyUtils; 60 61 import com.google.android.mms.pdu.PduHeaders; 62 import com.google.android.mms.util.DownloadDrmHelper; 63 64 import java.io.File; 65 import java.io.FileNotFoundException; 66 import java.io.IOException; 67 68 /** 69 * The class to provide base facility to access MMS related content, 70 * which is stored in a SQLite database and in the file system. 71 */ 72 public class MmsProvider extends ContentProvider { 73 static final String TABLE_PDU = "pdu"; 74 static final String TABLE_ADDR = "addr"; 75 static final String TABLE_PART = "part"; 76 static final String TABLE_RATE = "rate"; 77 static final String TABLE_DRM = "drm"; 78 static final String TABLE_WORDS = "words"; 79 static final String VIEW_PDU_RESTRICTED = "pdu_restricted"; 80 81 // The name of parts directory. The full dir is "app_parts". 82 static final String PARTS_DIR_NAME = "parts"; 83 84 private ProviderUtilWrapper providerUtilWrapper = new ProviderUtilWrapper(); 85 86 @VisibleForTesting setProviderUtilWrapper(ProviderUtilWrapper providerUtilWrapper)87 public void setProviderUtilWrapper(ProviderUtilWrapper providerUtilWrapper) { 88 this.providerUtilWrapper = providerUtilWrapper; 89 } 90 91 @Override onCreate()92 public boolean onCreate() { 93 setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS); 94 mOpenHelper = MmsSmsDatabaseHelper.getInstanceForCe(getContext()); 95 TelephonyBackupAgent.DeferredSmsMmsRestoreService.startIfFilesExist(getContext()); 96 97 // Creating intent broadcast receiver for user actions like Intent.ACTION_USER_REMOVED, 98 // where we would need to remove MMS related to removed user. 99 IntentFilter userIntentFilter = new IntentFilter(Intent.ACTION_USER_REMOVED); 100 getContext().registerReceiver(mUserIntentReceiver, userIntentFilter, 101 Context.RECEIVER_NOT_EXPORTED); 102 103 return true; 104 } 105 106 // wrapper class to allow easier mocking of the static ProviderUtil in tests 107 @VisibleForTesting 108 public static class ProviderUtilWrapper { isAccessRestricted(Context context, String packageName, int uid)109 public boolean isAccessRestricted(Context context, String packageName, int uid) { 110 return ProviderUtil.isAccessRestricted(context, packageName, uid); 111 } 112 } 113 114 /** 115 * Return the proper view of "pdu" table for the current access status. 116 * 117 * @param accessRestricted If the access is restricted 118 * @return the table/view name of the mms data 119 */ getPduTable(boolean accessRestricted)120 public static String getPduTable(boolean accessRestricted) { 121 return accessRestricted ? VIEW_PDU_RESTRICTED : TABLE_PDU; 122 } 123 124 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)125 public Cursor query(Uri uri, String[] projection, 126 String selection, String[] selectionArgs, String sortOrder) { 127 final int callerUid = Binder.getCallingUid(); 128 final UserHandle callerUserHandle = Binder.getCallingUserHandle(); 129 String callingPackage = getCallingPackage(); 130 // First check if a restricted view of the "pdu" table should be used based on the 131 // caller's identity. Only system, phone or the default sms app can have full access 132 // of mms data. For other apps, we present a restricted view which only contains sent 133 // or received messages, without wap pushes. 134 final boolean accessRestricted = ProviderUtil.isAccessRestricted( 135 getContext(), getCallingPackage(), callerUid); 136 137 // If access is restricted, we don't allow subqueries in the query. 138 Log.v(TAG, "accessRestricted=" + accessRestricted); 139 if (accessRestricted) { 140 SqlQueryChecker.checkQueryParametersForSubqueries(projection, selection, sortOrder); 141 } 142 143 final String pduTable = getPduTable(accessRestricted); 144 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 145 146 // Generate the body of the query. 147 int match = sURLMatcher.match(uri); 148 if (LOCAL_LOGV) { 149 Log.v(TAG, "Query uri=" + uri + ", match=" + match); 150 } 151 152 switch (match) { 153 case MMS_ALL: 154 constructQueryForBox(qb, Mms.MESSAGE_BOX_ALL, pduTable); 155 break; 156 case MMS_INBOX: 157 constructQueryForBox(qb, Mms.MESSAGE_BOX_INBOX, pduTable); 158 break; 159 case MMS_SENT: 160 constructQueryForBox(qb, Mms.MESSAGE_BOX_SENT, pduTable); 161 break; 162 case MMS_DRAFTS: 163 constructQueryForBox(qb, Mms.MESSAGE_BOX_DRAFTS, pduTable); 164 break; 165 case MMS_OUTBOX: 166 constructQueryForBox(qb, Mms.MESSAGE_BOX_OUTBOX, pduTable); 167 break; 168 case MMS_ALL_ID: 169 qb.setTables(pduTable); 170 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(0)); 171 break; 172 case MMS_INBOX_ID: 173 case MMS_SENT_ID: 174 case MMS_DRAFTS_ID: 175 case MMS_OUTBOX_ID: 176 qb.setTables(pduTable); 177 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(1)); 178 qb.appendWhere(" AND " + Mms.MESSAGE_BOX + "=" 179 + getMessageBoxByMatch(match)); 180 break; 181 case MMS_ALL_PART: 182 qb.setTables(TABLE_PART); 183 break; 184 case MMS_MSG_PART: 185 qb.setTables(TABLE_PART); 186 qb.appendWhere(Part.MSG_ID + "=" + uri.getPathSegments().get(0)); 187 break; 188 case MMS_PART_ID: 189 qb.setTables(TABLE_PART); 190 qb.appendWhere(Part._ID + "=" + uri.getPathSegments().get(1)); 191 break; 192 case MMS_MSG_ADDR: 193 qb.setTables(TABLE_ADDR); 194 qb.appendWhere(Addr.MSG_ID + "=" + uri.getPathSegments().get(0)); 195 break; 196 case MMS_REPORT_STATUS: 197 /* 198 SELECT DISTINCT address, 199 T.delivery_status AS delivery_status, 200 T.read_status AS read_status 201 FROM addr 202 INNER JOIN (SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3, 203 ifnull(P2.st, 0) AS delivery_status, 204 ifnull(P3.read_status, 0) AS read_status 205 FROM pdu P1 206 INNER JOIN pdu P2 207 ON P1.m_id = P2.m_id AND P2.m_type = 134 208 LEFT JOIN pdu P3 209 ON P1.m_id = P3.m_id AND P3.m_type = 136 210 UNION 211 SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3, 212 ifnull(P2.st, 0) AS delivery_status, 213 ifnull(P3.read_status, 0) AS read_status 214 FROM pdu P1 215 INNER JOIN pdu P3 216 ON P1.m_id = P3.m_id AND P3.m_type = 136 217 LEFT JOIN pdu P2 218 ON P1.m_id = P2.m_id AND P2.m_type = 134) T 219 ON (msg_id = id2 AND type = 151) 220 OR (msg_id = id3 AND type = 137) 221 WHERE T.id1 = ?; 222 */ 223 qb.setTables(TABLE_ADDR + " INNER JOIN " 224 + "(SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3, " 225 + "ifnull(P2.st, 0) AS delivery_status, " 226 + "ifnull(P3.read_status, 0) AS read_status " 227 + "FROM " + pduTable + " P1 INNER JOIN " + pduTable + " P2 " 228 + "ON P1.m_id=P2.m_id AND P2.m_type=134 " 229 + "LEFT JOIN " + pduTable + " P3 " 230 + "ON P1.m_id=P3.m_id AND P3.m_type=136 " 231 + "UNION " 232 + "SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3, " 233 + "ifnull(P2.st, 0) AS delivery_status, " 234 + "ifnull(P3.read_status, 0) AS read_status " 235 + "FROM " + pduTable + " P1 INNER JOIN " + pduTable + " P3 " 236 + "ON P1.m_id=P3.m_id AND P3.m_type=136 " 237 + "LEFT JOIN " + pduTable + " P2 " 238 + "ON P1.m_id=P2.m_id AND P2.m_type=134) T " 239 + "ON (msg_id=id2 AND type=151) OR (msg_id=id3 AND type=137)"); 240 qb.appendWhere("T.id1 = " + uri.getLastPathSegment()); 241 qb.setDistinct(true); 242 break; 243 case MMS_REPORT_REQUEST: 244 /* 245 SELECT address, d_rpt, rr 246 FROM addr join pdu on pdu._id = addr.msg_id 247 WHERE pdu._id = messageId AND addr.type = 151 248 */ 249 qb.setTables(TABLE_ADDR + " join " + 250 pduTable + " on " + pduTable + "._id = addr.msg_id"); 251 qb.appendWhere(pduTable + "._id = " + uri.getLastPathSegment()); 252 qb.appendWhere(" AND " + TABLE_ADDR + ".type = " + PduHeaders.TO); 253 break; 254 case MMS_SENDING_RATE: 255 qb.setTables(TABLE_RATE); 256 break; 257 case MMS_DRM_STORAGE_ID: 258 qb.setTables(TABLE_DRM); 259 qb.appendWhere(BaseColumns._ID + "=" + uri.getLastPathSegment()); 260 break; 261 case MMS_THREADS: 262 qb.setTables(pduTable + " group by thread_id"); 263 break; 264 default: 265 Log.e(TAG, "query: invalid request: " + uri); 266 return null; 267 } 268 269 String selectionBySubIds; 270 final long token = Binder.clearCallingIdentity(); 271 try { 272 // Filter MMS based on subId. 273 selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(), 274 callerUserHandle); 275 } finally { 276 Binder.restoreCallingIdentity(token); 277 } 278 if (selectionBySubIds == null) { 279 // No subscriptions associated with user, return empty cursor. 280 return new MatrixCursor((projection == null) ? (new String[] {}) : projection); 281 } 282 selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds); 283 284 String finalSortOrder = null; 285 if (TextUtils.isEmpty(sortOrder)) { 286 if (qb.getTables().equals(pduTable)) { 287 finalSortOrder = Mms.DATE + " DESC"; 288 } else if (qb.getTables().equals(TABLE_PART)) { 289 finalSortOrder = Part.SEQ; 290 } 291 } else { 292 finalSortOrder = sortOrder; 293 } 294 295 Cursor ret; 296 try { 297 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 298 if (mOpenHelper instanceof MmsSmsDatabaseHelper) { 299 ((MmsSmsDatabaseHelper) mOpenHelper).addDatabaseOpeningDebugLog( 300 callingPackage + ";MmsProvider.query;" + uri, true); 301 } 302 ret = qb.query(db, projection, selection, 303 selectionArgs, null, null, finalSortOrder); 304 } catch (SQLiteException e) { 305 Log.e(TAG, "returning NULL cursor, query: " + uri, e); 306 if (mOpenHelper instanceof MmsSmsDatabaseHelper) { 307 ((MmsSmsDatabaseHelper) mOpenHelper).printDatabaseOpeningDebugLog(); 308 } 309 return null; 310 } 311 312 // TODO: Does this need to be a URI for this provider. 313 ret.setNotificationUri(getContext().getContentResolver(), uri); 314 return ret; 315 } 316 constructQueryForBox(SQLiteQueryBuilder qb, int msgBox, String pduTable)317 private void constructQueryForBox(SQLiteQueryBuilder qb, int msgBox, String pduTable) { 318 qb.setTables(pduTable); 319 320 if (msgBox != Mms.MESSAGE_BOX_ALL) { 321 qb.appendWhere(Mms.MESSAGE_BOX + "=" + msgBox); 322 } 323 } 324 325 @Override getType(Uri uri)326 public String getType(Uri uri) { 327 int match = sURLMatcher.match(uri); 328 switch (match) { 329 case MMS_ALL: 330 case MMS_INBOX: 331 case MMS_SENT: 332 case MMS_DRAFTS: 333 case MMS_OUTBOX: 334 return VND_ANDROID_DIR_MMS; 335 case MMS_ALL_ID: 336 case MMS_INBOX_ID: 337 case MMS_SENT_ID: 338 case MMS_DRAFTS_ID: 339 case MMS_OUTBOX_ID: 340 return VND_ANDROID_MMS; 341 case MMS_PART_ID: { 342 Cursor cursor = mOpenHelper.getReadableDatabase().query( 343 TABLE_PART, new String[] { Part.CONTENT_TYPE }, 344 Part._ID + " = ?", new String[] { uri.getLastPathSegment() }, 345 null, null, null); 346 if (cursor != null) { 347 try { 348 if ((cursor.getCount() == 1) && cursor.moveToFirst()) { 349 return cursor.getString(0); 350 } else { 351 Log.e(TAG, "cursor.count() != 1: " + uri); 352 } 353 } finally { 354 cursor.close(); 355 } 356 } else { 357 Log.e(TAG, "cursor == null: " + uri); 358 } 359 return "*/*"; 360 } 361 case MMS_ALL_PART: 362 case MMS_MSG_PART: 363 case MMS_MSG_ADDR: 364 default: 365 return "*/*"; 366 } 367 } 368 369 @Override insert(Uri uri, ContentValues values)370 public Uri insert(Uri uri, ContentValues values) { 371 final int callerUid = Binder.getCallingUid(); 372 final UserHandle callerUserHandle = Binder.getCallingUserHandle(); 373 final String callerPkg = getCallingPackage(); 374 int msgBox = Mms.MESSAGE_BOX_ALL; 375 boolean notify = true; 376 377 boolean forceNoNotify = values.containsKey(TelephonyBackupAgent.NOTIFY) 378 && !values.getAsBoolean(TelephonyBackupAgent.NOTIFY); 379 values.remove(TelephonyBackupAgent.NOTIFY); 380 // check isAccessRestricted to prevent third parties from setting NOTIFY = false maliciously 381 if (forceNoNotify && !providerUtilWrapper.isAccessRestricted( 382 getContext(), getCallingPackage(), callerUid)) { 383 notify = false; 384 } 385 386 int match = sURLMatcher.match(uri); 387 if (LOCAL_LOGV) { 388 Log.v(TAG, "Insert uri=" + uri + ", match=" + match); 389 } 390 391 String table = TABLE_PDU; 392 switch (match) { 393 case MMS_ALL: 394 Object msgBoxObj = values.getAsInteger(Mms.MESSAGE_BOX); 395 if (msgBoxObj != null) { 396 msgBox = (Integer) msgBoxObj; 397 } 398 else { 399 // default to inbox 400 msgBox = Mms.MESSAGE_BOX_INBOX; 401 } 402 break; 403 case MMS_INBOX: 404 msgBox = Mms.MESSAGE_BOX_INBOX; 405 break; 406 case MMS_SENT: 407 msgBox = Mms.MESSAGE_BOX_SENT; 408 break; 409 case MMS_DRAFTS: 410 msgBox = Mms.MESSAGE_BOX_DRAFTS; 411 break; 412 case MMS_OUTBOX: 413 msgBox = Mms.MESSAGE_BOX_OUTBOX; 414 break; 415 case MMS_MSG_PART: 416 notify = false; 417 table = TABLE_PART; 418 break; 419 case MMS_MSG_ADDR: 420 notify = false; 421 table = TABLE_ADDR; 422 break; 423 case MMS_SENDING_RATE: 424 notify = false; 425 table = TABLE_RATE; 426 break; 427 case MMS_DRM_STORAGE: 428 notify = false; 429 table = TABLE_DRM; 430 break; 431 default: 432 Log.e(TAG, "insert: invalid request: " + uri); 433 return null; 434 } 435 436 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 437 if (mOpenHelper instanceof MmsSmsDatabaseHelper) { 438 ((MmsSmsDatabaseHelper) mOpenHelper).addDatabaseOpeningDebugLog( 439 callerPkg + ";MmsProvider.insert;" + uri, false); 440 } 441 ContentValues finalValues; 442 Uri res = Mms.CONTENT_URI; 443 Uri caseSpecificUri = null; 444 long rowId; 445 446 int subId; 447 if (values.containsKey(Telephony.Sms.SUBSCRIPTION_ID)) { 448 subId = values.getAsInteger(Telephony.Sms.SUBSCRIPTION_ID); 449 } else { 450 // TODO (b/256992531): Currently, one sim card is set as default sms subId in work 451 // profile. Default sms subId should be updated based on user pref. 452 subId = SmsManager.getDefaultSmsSubscriptionId(); 453 if (SubscriptionManager.isValidSubscriptionId(subId)) { 454 values.put(Telephony.Sms.SUBSCRIPTION_ID, subId); 455 } 456 } 457 458 if (table.equals(TABLE_PDU)) { 459 if (!ProviderUtil.allowInteractingWithEntryOfSubscription(getContext(), subId, 460 callerUserHandle)) { 461 TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(getContext(), subId, 462 callerUid, callerPkg); 463 return null; 464 } 465 466 boolean addDate = !values.containsKey(Mms.DATE); 467 boolean addMsgBox = !values.containsKey(Mms.MESSAGE_BOX); 468 469 // Filter keys we don't support yet. 470 filterUnsupportedKeys(values); 471 472 // TODO: Should initialValues be validated, e.g. if it 473 // missed some significant keys? 474 finalValues = new ContentValues(values); 475 476 long timeInMillis = System.currentTimeMillis(); 477 478 if (addDate) { 479 finalValues.put(Mms.DATE, timeInMillis / 1000L); 480 } 481 482 if (addMsgBox && (msgBox != Mms.MESSAGE_BOX_ALL)) { 483 finalValues.put(Mms.MESSAGE_BOX, msgBox); 484 } 485 486 if (msgBox != Mms.MESSAGE_BOX_INBOX) { 487 // Mark all non-inbox messages read. 488 finalValues.put(Mms.READ, 1); 489 } 490 491 // thread_id 492 Long threadId = values.getAsLong(Mms.THREAD_ID); 493 String address = values.getAsString(CanonicalAddressesColumns.ADDRESS); 494 495 if (((threadId == null) || (threadId == 0)) && (!TextUtils.isEmpty(address))) { 496 finalValues.put(Mms.THREAD_ID, Threads.getOrCreateThreadId(getContext(), address)); 497 } 498 499 if (ProviderUtil.shouldSetCreator(finalValues, callerUid)) { 500 // Only SYSTEM or PHONE can set CREATOR 501 // If caller is not SYSTEM or PHONE, or SYSTEM or PHONE does not set CREATOR 502 // set CREATOR using the truth on caller. 503 // Note: Inferring package name from UID may include unrelated package names 504 finalValues.put(Telephony.Mms.CREATOR, callerPkg); 505 } 506 507 if ((rowId = db.insert(table, null, finalValues)) <= 0) { 508 Log.e(TAG, "MmsProvider.insert: failed!"); 509 if (mOpenHelper instanceof MmsSmsDatabaseHelper) { 510 ((MmsSmsDatabaseHelper) mOpenHelper).printDatabaseOpeningDebugLog(); 511 } 512 return null; 513 } 514 515 // Notify change when an MMS is received. 516 if (msgBox == Mms.MESSAGE_BOX_INBOX) { 517 caseSpecificUri = ContentUris.withAppendedId(Mms.Inbox.CONTENT_URI, rowId); 518 } 519 520 res = Uri.parse(res + "/" + rowId); 521 } else if (table.equals(TABLE_ADDR)) { 522 finalValues = new ContentValues(values); 523 finalValues.put(Addr.MSG_ID, uri.getPathSegments().get(0)); 524 525 if ((rowId = db.insert(table, null, finalValues)) <= 0) { 526 Log.e(TAG, "Failed to insert address"); 527 if (mOpenHelper instanceof MmsSmsDatabaseHelper) { 528 ((MmsSmsDatabaseHelper) mOpenHelper).printDatabaseOpeningDebugLog(); 529 } 530 return null; 531 } 532 533 res = Uri.parse(res + "/addr/" + rowId); 534 } else if (table.equals(TABLE_PART)) { 535 boolean containsDataPath = values != null && values.containsKey(Part._DATA); 536 finalValues = new ContentValues(values); 537 538 if (match == MMS_MSG_PART) { 539 finalValues.put(Part.MSG_ID, uri.getPathSegments().get(0)); 540 } 541 542 String contentType = values.getAsString("ct"); 543 544 // text/plain and app application/smil store their "data" inline in the 545 // table so there's no need to create the file 546 boolean plainText = false; 547 boolean smilText = false; 548 if ("text/plain".equals(contentType)) { 549 if (containsDataPath) { 550 Log.e(TAG, "insert: can't insert text/plain with _data"); 551 return null; 552 } 553 plainText = true; 554 } else if ("application/smil".equals(contentType)) { 555 if (containsDataPath) { 556 Log.e(TAG, "insert: can't insert application/smil with _data"); 557 return null; 558 } 559 smilText = true; 560 } 561 if (!plainText && !smilText) { 562 String path; 563 if (containsDataPath) { 564 // The _data column is filled internally in MmsProvider or from the 565 // TelephonyBackupAgent, so this check is just to avoid it from being 566 // inadvertently set. This is not supposed to be a protection against malicious 567 // attack, since sql injection could still be attempted to bypass the check. 568 // On the other hand, the MmsProvider does verify that the _data column has an 569 // allowed value before opening any uri/files. 570 if (!"com.android.providers.telephony".equals(callerPkg)) { 571 Log.e(TAG, "insert: can't insert _data"); 572 return null; 573 } 574 try { 575 path = values.getAsString(Part._DATA); 576 final String partsDirPath = getContext() 577 .getDir(PARTS_DIR_NAME, 0).getCanonicalPath(); 578 if (!new File(path).getCanonicalPath().startsWith(partsDirPath)) { 579 Log.e(TAG, "insert: path " 580 + path 581 + " does not start with " 582 + partsDirPath); 583 // Don't care return value 584 return null; 585 } 586 } catch (IOException e) { 587 Log.e(TAG, "insert part: create path failed " + e, e); 588 return null; 589 } 590 } else { 591 // Use the filename if possible, otherwise use the current time as the name. 592 String contentLocation = values.getAsString("cl"); 593 if (!TextUtils.isEmpty(contentLocation)) { 594 File f = new File(contentLocation); 595 contentLocation = "_" + f.getName(); 596 } else { 597 contentLocation = ""; 598 } 599 600 // Generate the '_data' field of the part with default 601 // permission settings. 602 path = getContext().getDir(PARTS_DIR_NAME, 0).getPath() 603 + "/PART_" + System.currentTimeMillis() + contentLocation; 604 605 if (DownloadDrmHelper.isDrmConvertNeeded(contentType)) { 606 // Adds the .fl extension to the filename if contentType is 607 // "application/vnd.oma.drm.message" 608 path = DownloadDrmHelper.modifyDrmFwLockFileExtension(path); 609 } 610 } 611 612 finalValues.put(Part._DATA, path); 613 614 File partFile = new File(path); 615 if (!partFile.exists()) { 616 try { 617 if (!partFile.createNewFile()) { 618 throw new IllegalStateException( 619 "Unable to create new partFile: " + path); 620 } 621 // Give everyone rw permission until we encrypt the file 622 // (in PduPersister.persistData). Once the file is encrypted, the 623 // permissions will be set to 0644. 624 try { 625 Os.chmod(path, 0666); 626 if (LOCAL_LOGV) { 627 Log.d(TAG, "MmsProvider.insert chmod is successful"); 628 } 629 } catch (ErrnoException e) { 630 Log.e(TAG, "Exception in chmod: " + e); 631 } 632 } catch (IOException e) { 633 Log.e(TAG, "createNewFile", e); 634 throw new IllegalStateException( 635 "Unable to create new partFile: " + path); 636 } 637 } 638 } 639 640 if ((rowId = db.insert(table, null, finalValues)) <= 0) { 641 Log.e(TAG, "MmsProvider.insert: failed!"); 642 if (mOpenHelper instanceof MmsSmsDatabaseHelper) { 643 ((MmsSmsDatabaseHelper) mOpenHelper).printDatabaseOpeningDebugLog(); 644 } 645 return null; 646 } 647 648 res = Uri.parse(res + "/part/" + rowId); 649 650 // Don't use a trigger for updating the words table because of a bug 651 // in FTS3. The bug is such that the call to get the last inserted 652 // row is incorrect. 653 if (plainText) { 654 // Update the words table with a corresponding row. The words table 655 // allows us to search for words quickly, without scanning the whole 656 // table; 657 ContentValues cv = new ContentValues(); 658 659 // we're using the row id of the part table row but we're also using ids 660 // from the sms table so this divides the space into two large chunks. 661 // The row ids from the part table start at 2 << 32. 662 cv.put(Telephony.MmsSms.WordsTable.ID, (2L << 32) + rowId); 663 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("text")); 664 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowId); 665 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 2); 666 cv.put(MmsSms.WordsTable.SUBSCRIPTION_ID, subId); 667 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv); 668 } 669 670 } else if (table.equals(TABLE_RATE)) { 671 long now = values.getAsLong(Rate.SENT_TIME); 672 long oneHourAgo = now - 1000 * 60 * 60; 673 // Delete all unused rows (time earlier than one hour ago). 674 db.delete(table, Rate.SENT_TIME + "<=" + oneHourAgo, null); 675 db.insert(table, null, values); 676 } else if (table.equals(TABLE_DRM)) { 677 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath() 678 + "/PART_" + System.currentTimeMillis(); 679 finalValues = new ContentValues(1); 680 finalValues.put("_data", path); 681 finalValues.put("sub_id", subId); 682 683 File partFile = new File(path); 684 if (!partFile.exists()) { 685 try { 686 if (!partFile.createNewFile()) { 687 throw new IllegalStateException( 688 "Unable to create new file: " + path); 689 } 690 } catch (IOException e) { 691 Log.e(TAG, "createNewFile", e); 692 throw new IllegalStateException( 693 "Unable to create new file: " + path); 694 } 695 } 696 697 if ((rowId = db.insert(table, null, finalValues)) <= 0) { 698 Log.e(TAG, "MmsProvider.insert: failed!"); 699 if (mOpenHelper instanceof MmsSmsDatabaseHelper) { 700 ((MmsSmsDatabaseHelper) mOpenHelper).printDatabaseOpeningDebugLog(); 701 } 702 return null; 703 } 704 res = Uri.parse(res + "/drm/" + rowId); 705 } else { 706 throw new AssertionError("Unknown table type: " + table); 707 } 708 709 if (notify) { 710 notifyChange(res, caseSpecificUri); 711 } 712 return res; 713 } 714 getMessageBoxByMatch(int match)715 private int getMessageBoxByMatch(int match) { 716 switch (match) { 717 case MMS_INBOX_ID: 718 case MMS_INBOX: 719 return Mms.MESSAGE_BOX_INBOX; 720 case MMS_SENT_ID: 721 case MMS_SENT: 722 return Mms.MESSAGE_BOX_SENT; 723 case MMS_DRAFTS_ID: 724 case MMS_DRAFTS: 725 return Mms.MESSAGE_BOX_DRAFTS; 726 case MMS_OUTBOX_ID: 727 case MMS_OUTBOX: 728 return Mms.MESSAGE_BOX_OUTBOX; 729 default: 730 throw new IllegalArgumentException("bad Arg: " + match); 731 } 732 } 733 734 @Override delete(Uri uri, String selection, String[] selectionArgs)735 public int delete(Uri uri, String selection, 736 String[] selectionArgs) { 737 final UserHandle callerUserHandle = Binder.getCallingUserHandle(); 738 int match = sURLMatcher.match(uri); 739 if (LOCAL_LOGV) { 740 Log.v(TAG, "Delete uri=" + uri + ", match=" + match); 741 } 742 743 String table, extraSelection = null; 744 boolean notify = false; 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 notify = true; 753 table = TABLE_PDU; 754 extraSelection = Mms._ID + "=" + uri.getLastPathSegment(); 755 break; 756 case MMS_ALL: 757 case MMS_INBOX: 758 case MMS_SENT: 759 case MMS_DRAFTS: 760 case MMS_OUTBOX: 761 notify = true; 762 table = TABLE_PDU; 763 if (match != MMS_ALL) { 764 int msgBox = getMessageBoxByMatch(match); 765 extraSelection = Mms.MESSAGE_BOX + "=" + msgBox; 766 } 767 break; 768 case MMS_ALL_PART: 769 table = TABLE_PART; 770 break; 771 case MMS_MSG_PART: 772 table = TABLE_PART; 773 extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0); 774 break; 775 case MMS_PART_ID: 776 table = TABLE_PART; 777 extraSelection = Part._ID + "=" + uri.getPathSegments().get(1); 778 break; 779 case MMS_MSG_ADDR: 780 table = TABLE_ADDR; 781 extraSelection = Addr.MSG_ID + "=" + uri.getPathSegments().get(0); 782 break; 783 case MMS_DRM_STORAGE: 784 table = TABLE_DRM; 785 break; 786 default: 787 Log.w(TAG, "No match for URI '" + uri + "'"); 788 return 0; 789 } 790 791 String finalSelection = concatSelections(selection, extraSelection); 792 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 793 String debugMessage = getCallingPackage() + ";MmsProvider.delete;" + uri; 794 // Always log delete for debug purpose, as delete is a critical but non-frequent operation. 795 Log.d(TAG, debugMessage); 796 if (mOpenHelper instanceof MmsSmsDatabaseHelper) { 797 ((MmsSmsDatabaseHelper) mOpenHelper).addDatabaseOpeningDebugLog( 798 debugMessage, false); 799 } 800 int deletedRows = 0; 801 802 final long token = Binder.clearCallingIdentity(); 803 String selectionBySubIds; 804 try { 805 // Filter SMS based on subId. 806 selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(), 807 callerUserHandle); 808 } finally { 809 Binder.restoreCallingIdentity(token); 810 } 811 if (selectionBySubIds == null) { 812 // No subscriptions associated with user, return 0. 813 return 0; 814 } 815 finalSelection = DatabaseUtils.concatenateWhere(finalSelection, selectionBySubIds); 816 817 if (TABLE_PDU.equals(table)) { 818 deletedRows = deleteMessages(getContext(), db, finalSelection, 819 selectionArgs, uri); 820 } else if (TABLE_PART.equals(table)) { 821 deletedRows = deleteParts(db, finalSelection, selectionArgs); 822 } else if (TABLE_DRM.equals(table)) { 823 deletedRows = deleteTempDrmData(db, finalSelection, selectionArgs); 824 } else { 825 deletedRows = db.delete(table, finalSelection, selectionArgs); 826 } 827 828 if ((deletedRows > 0) && notify) { 829 notifyChange(uri, null); 830 } 831 return deletedRows; 832 } 833 deleteMessages(Context context, SQLiteDatabase db, String selection, String[] selectionArgs, Uri uri)834 static int deleteMessages(Context context, SQLiteDatabase db, 835 String selection, String[] selectionArgs, Uri uri) { 836 Cursor cursor = db.query(TABLE_PDU, new String[] { Mms._ID }, 837 selection, selectionArgs, null, null, null); 838 if (cursor == null) { 839 return 0; 840 } 841 842 try { 843 if (cursor.getCount() == 0) { 844 return 0; 845 } 846 847 while (cursor.moveToNext()) { 848 deleteParts(db, Part.MSG_ID + " = ?", 849 new String[] { String.valueOf(cursor.getLong(0)) }); 850 } 851 } finally { 852 cursor.close(); 853 } 854 855 int count = db.delete(TABLE_PDU, selection, selectionArgs); 856 if (count > 0) { 857 Intent intent = new Intent(Mms.Intents.CONTENT_CHANGED_ACTION); 858 intent.putExtra(Mms.Intents.DELETED_CONTENTS, uri); 859 if (LOCAL_LOGV) { 860 Log.v(TAG, "Broadcasting intent: " + intent); 861 } 862 context.sendBroadcast(intent); 863 } 864 return count; 865 } 866 deleteParts(SQLiteDatabase db, String selection, String[] selectionArgs)867 private static int deleteParts(SQLiteDatabase db, String selection, 868 String[] selectionArgs) { 869 return deleteDataRows(db, TABLE_PART, selection, selectionArgs); 870 } 871 deleteTempDrmData(SQLiteDatabase db, String selection, String[] selectionArgs)872 private static int deleteTempDrmData(SQLiteDatabase db, String selection, 873 String[] selectionArgs) { 874 return deleteDataRows(db, TABLE_DRM, selection, selectionArgs); 875 } 876 deleteDataRows(SQLiteDatabase db, String table, String selection, String[] selectionArgs)877 private static int deleteDataRows(SQLiteDatabase db, String table, 878 String selection, String[] selectionArgs) { 879 Cursor cursor = db.query(table, new String[] { "_data" }, 880 selection, selectionArgs, null, null, null); 881 if (cursor == null) { 882 // FIXME: This might be an error, ignore it may cause 883 // unpredictable result. 884 return 0; 885 } 886 887 try { 888 if (cursor.getCount() == 0) { 889 return 0; 890 } 891 892 while (cursor.moveToNext()) { 893 try { 894 // Delete the associated files saved on file-system. 895 String path = cursor.getString(0); 896 if (path != null) { 897 new File(path).delete(); 898 } 899 } catch (Throwable ex) { 900 Log.e(TAG, ex.getMessage(), ex); 901 } 902 } 903 } finally { 904 cursor.close(); 905 } 906 907 return db.delete(table, selection, selectionArgs); 908 } 909 910 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)911 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 912 // The _data column is filled internally in MmsProvider, so this check is just to avoid 913 // it from being inadvertently set. This is not supposed to be a protection against 914 // malicious attack, since sql injection could still be attempted to bypass the check. On 915 // the other hand, the MmsProvider does verify that the _data column has an allowed value 916 // before opening any uri/files. 917 if (values != null && values.containsKey(Part._DATA)) { 918 return 0; 919 } 920 final int callerUid = Binder.getCallingUid(); 921 final UserHandle callerUserHandle = Binder.getCallingUserHandle(); 922 final String callerPkg = getCallingPackage(); 923 int match = sURLMatcher.match(uri); 924 if (LOCAL_LOGV) { 925 Log.v(TAG, "Update uri=" + uri + ", match=" + match); 926 } 927 928 boolean notify = false; 929 String msgId = null; 930 String table; 931 932 switch (match) { 933 case MMS_ALL_ID: 934 case MMS_INBOX_ID: 935 case MMS_SENT_ID: 936 case MMS_DRAFTS_ID: 937 case MMS_OUTBOX_ID: 938 msgId = uri.getLastPathSegment(); 939 // fall-through 940 case MMS_ALL: 941 case MMS_INBOX: 942 case MMS_SENT: 943 case MMS_DRAFTS: 944 case MMS_OUTBOX: 945 notify = true; 946 table = TABLE_PDU; 947 break; 948 949 case MMS_MSG_PART: 950 case MMS_PART_ID: 951 table = TABLE_PART; 952 break; 953 954 case MMS_PART_RESET_FILE_PERMISSION: 955 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath() + '/' + 956 uri.getPathSegments().get(1); 957 958 try { 959 File canonicalFile = new File(path).getCanonicalFile(); 960 String partsDirPath = getContext().getDir(PARTS_DIR_NAME, 0).getCanonicalPath(); 961 if (!canonicalFile.getPath().startsWith(partsDirPath + '/')) { 962 EventLog.writeEvent(0x534e4554, "240685104", 963 callerUid, (TAG + " update: path " + path + 964 " does not start with " + partsDirPath)); 965 return 0; 966 } 967 // Reset the file permission back to read for everyone but me. 968 Os.chmod(canonicalFile.getPath(), 0644); 969 if (LOCAL_LOGV) { 970 Log.d(TAG, "MmsProvider.update chmod is successful for path: " + path); 971 } 972 } catch (ErrnoException | IOException e) { 973 Log.e(TAG, "Exception in chmod: " + e); 974 } 975 return 0; 976 977 default: 978 Log.w(TAG, "Update operation for '" + uri + "' not implemented."); 979 return 0; 980 } 981 982 String extraSelection = null; 983 final long token = Binder.clearCallingIdentity(); 984 String selectionBySubIds; 985 try { 986 // Filter MMS based on subId. 987 selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(), 988 callerUserHandle); 989 } finally { 990 Binder.restoreCallingIdentity(token); 991 } 992 if (selectionBySubIds == null) { 993 // No subscriptions associated with user, return 0. 994 return 0; 995 } 996 extraSelection = DatabaseUtils.concatenateWhere(extraSelection, selectionBySubIds); 997 998 ContentValues finalValues; 999 if (table.equals(TABLE_PDU)) { 1000 // Filter keys that we don't support yet. 1001 filterUnsupportedKeys(values); 1002 if (ProviderUtil.shouldRemoveCreator(values, callerUid)) { 1003 // CREATOR should not be changed by non-SYSTEM/PHONE apps 1004 Log.w(TAG, callerPkg + " tries to update CREATOR"); 1005 values.remove(Mms.CREATOR); 1006 } 1007 finalValues = new ContentValues(values); 1008 1009 if (msgId != null) { 1010 extraSelection = Mms._ID + "=" + msgId; 1011 } 1012 } else if (table.equals(TABLE_PART)) { 1013 finalValues = new ContentValues(values); 1014 1015 switch (match) { 1016 case MMS_MSG_PART: 1017 extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0); 1018 break; 1019 case MMS_PART_ID: 1020 extraSelection = Part._ID + "=" + uri.getPathSegments().get(1); 1021 break; 1022 default: 1023 break; 1024 } 1025 } else { 1026 return 0; 1027 } 1028 1029 String finalSelection = concatSelections(selection, extraSelection); 1030 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1031 if (mOpenHelper instanceof MmsSmsDatabaseHelper) { 1032 ((MmsSmsDatabaseHelper) mOpenHelper).addDatabaseOpeningDebugLog( 1033 callerPkg + ";MmsProvider.update;" + uri, false); 1034 } 1035 int count = db.update(table, finalValues, finalSelection, selectionArgs); 1036 if (notify && (count > 0)) { 1037 notifyChange(uri, null); 1038 } 1039 return count; 1040 } 1041 1042 @Override openFile(Uri uri, String mode)1043 public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { 1044 int match = sURLMatcher.match(uri); 1045 1046 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1047 Log.d(TAG, "openFile: uri=" + uri + ", mode=" + mode + ", match=" + match); 1048 } 1049 1050 if (match != MMS_PART_ID) { 1051 return null; 1052 } 1053 1054 return safeOpenFileHelper(uri, mode); 1055 } 1056 1057 @NonNull safeOpenFileHelper( @onNull Uri uri, @NonNull String mode)1058 private ParcelFileDescriptor safeOpenFileHelper( 1059 @NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { 1060 Cursor c = query(uri, new String[]{"_data"}, null, null, null); 1061 int count = (c != null) ? c.getCount() : 0; 1062 if (count != 1) { 1063 // If there is not exactly one result, throw an appropriate 1064 // exception. 1065 if (c != null) { 1066 c.close(); 1067 } 1068 if (count == 0) { 1069 throw new FileNotFoundException("No entry for " + uri); 1070 } 1071 throw new FileNotFoundException("Multiple items at " + uri); 1072 } 1073 1074 c.moveToFirst(); 1075 int i = c.getColumnIndex("_data"); 1076 String path = (i >= 0 ? c.getString(i) : null); 1077 c.close(); 1078 1079 if (path == null) { 1080 throw new FileNotFoundException("Column _data not found."); 1081 } 1082 1083 File filePath = new File(path); 1084 try { 1085 // The MmsProvider shouldn't open a file that isn't MMS data, so we verify that the 1086 // _data path actually points to MMS data. That safeguards ourselves from callers who 1087 // inserted or updated a URI (more specifically the _data column) with disallowed paths. 1088 // TODO(afurtado): provide a more robust mechanism to avoid disallowed _data paths to 1089 // be inserted/updated in the first place, including via SQL injection. 1090 if (!filePath.getCanonicalPath() 1091 .startsWith(getContext().getDir(PARTS_DIR_NAME, 0).getCanonicalPath())) { 1092 Log.e(TAG, "openFile: path " 1093 + filePath.getCanonicalPath() 1094 + " does not start with " 1095 + getContext().getDir(PARTS_DIR_NAME, 0).getCanonicalPath()); 1096 // Don't care return value 1097 return null; 1098 } 1099 } catch (IOException e) { 1100 Log.e(TAG, "openFile: create path failed " + e, e); 1101 return null; 1102 } 1103 1104 int modeBits = ParcelFileDescriptor.parseMode(mode); 1105 return ParcelFileDescriptor.open(filePath, modeBits); 1106 } 1107 filterUnsupportedKeys(ContentValues values)1108 private void filterUnsupportedKeys(ContentValues values) { 1109 // Some columns are unsupported. They should therefore 1110 // neither be inserted nor updated. Filter them out. 1111 values.remove(Mms.DELIVERY_TIME_TOKEN); 1112 values.remove(Mms.SENDER_VISIBILITY); 1113 values.remove(Mms.REPLY_CHARGING); 1114 values.remove(Mms.REPLY_CHARGING_DEADLINE_TOKEN); 1115 values.remove(Mms.REPLY_CHARGING_DEADLINE); 1116 values.remove(Mms.REPLY_CHARGING_ID); 1117 values.remove(Mms.REPLY_CHARGING_SIZE); 1118 values.remove(Mms.PREVIOUSLY_SENT_BY); 1119 values.remove(Mms.PREVIOUSLY_SENT_DATE); 1120 values.remove(Mms.STORE); 1121 values.remove(Mms.MM_STATE); 1122 values.remove(Mms.MM_FLAGS_TOKEN); 1123 values.remove(Mms.MM_FLAGS); 1124 values.remove(Mms.STORE_STATUS); 1125 values.remove(Mms.STORE_STATUS_TEXT); 1126 values.remove(Mms.STORED); 1127 values.remove(Mms.TOTALS); 1128 values.remove(Mms.MBOX_TOTALS); 1129 values.remove(Mms.MBOX_TOTALS_TOKEN); 1130 values.remove(Mms.QUOTAS); 1131 values.remove(Mms.MBOX_QUOTAS); 1132 values.remove(Mms.MBOX_QUOTAS_TOKEN); 1133 values.remove(Mms.MESSAGE_COUNT); 1134 values.remove(Mms.START); 1135 values.remove(Mms.DISTRIBUTION_INDICATOR); 1136 values.remove(Mms.ELEMENT_DESCRIPTOR); 1137 values.remove(Mms.LIMIT); 1138 values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE); 1139 values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE_TEXT); 1140 values.remove(Mms.STATUS_TEXT); 1141 values.remove(Mms.APPLIC_ID); 1142 values.remove(Mms.REPLY_APPLIC_ID); 1143 values.remove(Mms.AUX_APPLIC_ID); 1144 values.remove(Mms.DRM_CONTENT); 1145 values.remove(Mms.ADAPTATION_ALLOWED); 1146 values.remove(Mms.REPLACE_ID); 1147 values.remove(Mms.CANCEL_ID); 1148 values.remove(Mms.CANCEL_STATUS); 1149 1150 // Keys shouldn't be inserted or updated. 1151 values.remove(Mms._ID); 1152 } 1153 notifyChange(final Uri uri, final Uri caseSpecificUri)1154 private void notifyChange(final Uri uri, final Uri caseSpecificUri) { 1155 final Context context = getContext(); 1156 if (caseSpecificUri != null) { 1157 context.getContentResolver().notifyChange( 1158 caseSpecificUri, null, true, UserHandle.USER_ALL); 1159 } 1160 context.getContentResolver().notifyChange( 1161 MmsSms.CONTENT_URI, null, true, UserHandle.USER_ALL); 1162 ProviderUtil.notifyIfNotDefaultSmsApp(caseSpecificUri == null ? uri : caseSpecificUri, 1163 getCallingPackage(), context); 1164 } 1165 1166 private final static String TAG = "MmsProvider"; 1167 private final static String VND_ANDROID_MMS = "vnd.android/mms"; 1168 private final static String VND_ANDROID_DIR_MMS = "vnd.android-dir/mms"; 1169 private final static boolean DEBUG = false; 1170 private final static boolean LOCAL_LOGV = false; 1171 1172 private static final int MMS_ALL = 0; 1173 private static final int MMS_ALL_ID = 1; 1174 private static final int MMS_INBOX = 2; 1175 private static final int MMS_INBOX_ID = 3; 1176 private static final int MMS_SENT = 4; 1177 private static final int MMS_SENT_ID = 5; 1178 private static final int MMS_DRAFTS = 6; 1179 private static final int MMS_DRAFTS_ID = 7; 1180 private static final int MMS_OUTBOX = 8; 1181 private static final int MMS_OUTBOX_ID = 9; 1182 private static final int MMS_ALL_PART = 10; 1183 private static final int MMS_MSG_PART = 11; 1184 private static final int MMS_PART_ID = 12; 1185 private static final int MMS_MSG_ADDR = 13; 1186 private static final int MMS_SENDING_RATE = 14; 1187 private static final int MMS_REPORT_STATUS = 15; 1188 private static final int MMS_REPORT_REQUEST = 16; 1189 private static final int MMS_DRM_STORAGE = 17; 1190 private static final int MMS_DRM_STORAGE_ID = 18; 1191 private static final int MMS_THREADS = 19; 1192 private static final int MMS_PART_RESET_FILE_PERMISSION = 20; 1193 1194 private static final UriMatcher 1195 sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH); 1196 1197 static { 1198 sURLMatcher.addURI("mms", null, MMS_ALL); 1199 sURLMatcher.addURI("mms", "#", MMS_ALL_ID); 1200 sURLMatcher.addURI("mms", "inbox", MMS_INBOX); 1201 sURLMatcher.addURI("mms", "inbox/#", MMS_INBOX_ID); 1202 sURLMatcher.addURI("mms", "sent", MMS_SENT); 1203 sURLMatcher.addURI("mms", "sent/#", MMS_SENT_ID); 1204 sURLMatcher.addURI("mms", "drafts", MMS_DRAFTS); 1205 sURLMatcher.addURI("mms", "drafts/#", MMS_DRAFTS_ID); 1206 sURLMatcher.addURI("mms", "outbox", MMS_OUTBOX); 1207 sURLMatcher.addURI("mms", "outbox/#", MMS_OUTBOX_ID); 1208 sURLMatcher.addURI("mms", "part", MMS_ALL_PART); 1209 sURLMatcher.addURI("mms", "#/part", MMS_MSG_PART); 1210 sURLMatcher.addURI("mms", "part/#", MMS_PART_ID); 1211 sURLMatcher.addURI("mms", "#/addr", MMS_MSG_ADDR); 1212 sURLMatcher.addURI("mms", "rate", MMS_SENDING_RATE); 1213 sURLMatcher.addURI("mms", "report-status/#", MMS_REPORT_STATUS); 1214 sURLMatcher.addURI("mms", "report-request/#", MMS_REPORT_REQUEST); 1215 sURLMatcher.addURI("mms", "drm", MMS_DRM_STORAGE); 1216 sURLMatcher.addURI("mms", "drm/#", MMS_DRM_STORAGE_ID); 1217 sURLMatcher.addURI("mms", "threads", MMS_THREADS); 1218 sURLMatcher.addURI("mms", "resetFilePerm/*", MMS_PART_RESET_FILE_PERMISSION); 1219 } 1220 1221 @VisibleForTesting 1222 public SQLiteOpenHelper mOpenHelper; 1223 concatSelections(String selection1, String selection2)1224 private static String concatSelections(String selection1, String selection2) { 1225 if (TextUtils.isEmpty(selection1)) { 1226 return selection2; 1227 } else if (TextUtils.isEmpty(selection2)) { 1228 return selection1; 1229 } else { 1230 return selection1 + " AND " + selection2; 1231 } 1232 } 1233 1234 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() { 1235 @Override 1236 public void onReceive(Context context, Intent intent) { 1237 switch (intent.getAction()) { 1238 case Intent.ACTION_USER_REMOVED: 1239 UserHandle userToBeRemoved = intent.getParcelableExtra(Intent.EXTRA_USER, 1240 UserHandle.class); 1241 UserManager userManager = context.getSystemService(UserManager.class); 1242 if ((userToBeRemoved == null) || (userManager == null) || 1243 (!userManager.isManagedProfile(userToBeRemoved.getIdentifier()))) { 1244 // Do not delete MMS if removed profile is not managed profile. 1245 return; 1246 } 1247 Log.d(TAG, "Received ACTION_USER_REMOVED for managed profile - Deleting MMS."); 1248 1249 // Deleting MMS related to managed profile. 1250 Uri uri = Telephony.Mms.CONTENT_URI; 1251 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1252 1253 final long token = Binder.clearCallingIdentity(); 1254 String selectionBySubIds; 1255 try { 1256 // Filter MMS based on subId. 1257 selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(), 1258 userToBeRemoved); 1259 } finally { 1260 Binder.restoreCallingIdentity(token); 1261 } 1262 if (selectionBySubIds == null) { 1263 // No subscriptions associated with user, return. 1264 return; 1265 } 1266 1267 int deletedRows = deleteMessages(getContext(), db, selectionBySubIds, 1268 null, uri); 1269 if (deletedRows > 0) { 1270 // Don't update threads unless something changed. 1271 MmsSmsDatabaseHelper.updateThreads(db, selectionBySubIds, null); 1272 notifyChange(uri, null); 1273 } 1274 break; 1275 } 1276 } 1277 }; 1278 } 1279