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