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