From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aishwarya Mallampati <amallampati@google.com>
Date: Wed, 23 Aug 2023 18:30:46 +0000
Subject: [PATCH] DO NOT MERGE Block access to sms/mms db from work profile.

Bug: 289242655
Test: Manually verified work profile cannot access personal sms by
following steps mentioned in b/289242655#comment26
- atest SmsProviderTest
- atest MmsProviderTest
- atest SmsBackupRestoreTest
- QA performed regression testing and confirmed fix is working as intended here: b/294459052#comment30
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:950a7e5a4bf1b38e846fe00642105479efded57d)
Merged-In: Ib1c9ec75f77e8412b53df50f5414caa0e5aaa277
Change-Id: Ib1c9ec75f77e8412b53df50f5414caa0e5aaa277

Change-Id: Ic6aca29053bf3208f5b9e5573293bb9e17103776
---
 .../providers/telephony/MmsProvider.java      |  41 ++++-
 .../telephony/MmsSmsDatabaseHelper.java       | 157 +++++++++-------
 .../providers/telephony/MmsSmsProvider.java   |  42 ++++-
 .../providers/telephony/SmsProvider.java      |  35 ++++
 .../providers/telephony/MmsProviderTest.java  | 173 ++++++++++++++++++
 .../telephony/MmsProviderTestable.java        |  77 ++++++++
 .../providers/telephony/SmsProviderTest.java  |  55 ++++++
 7 files changed, 508 insertions(+), 72 deletions(-)
 create mode 100644 tests/src/com/android/providers/telephony/MmsProviderTest.java
 create mode 100644 tests/src/com/android/providers/telephony/MmsProviderTestable.java

diff --git a/src/com/android/providers/telephony/MmsProvider.java b/src/com/android/providers/telephony/MmsProvider.java
index 7546c246..9f58fc33 100644
--- a/src/com/android/providers/telephony/MmsProvider.java
+++ b/src/com/android/providers/telephony/MmsProvider.java
@@ -25,6 +25,7 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.UriMatcher;
 import android.database.Cursor;
+import android.database.MatrixCursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteOpenHelper;
@@ -34,6 +35,7 @@ import android.os.Binder;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.BaseColumns;
 import android.provider.Telephony;
 import android.provider.Telephony.CanonicalAddressesColumns;
@@ -50,6 +52,8 @@ import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import com.google.android.mms.pdu.PduHeaders;
 import com.google.android.mms.util.DownloadDrmHelper;
 
@@ -94,6 +98,16 @@ public class MmsProvider extends ContentProvider {
     @Override
     public Cursor query(Uri uri, String[] projection,
             String selection, String[] selectionArgs, String sortOrder) {
+        Cursor emptyCursor = new MatrixCursor((projection == null) ?
+                (new String[] {}) : projection);
+        UserManager userManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+        if ((userManager != null) && (userManager.isManagedProfile(
+                Binder.getCallingUserHandle().getIdentifier()))) {
+            // If work profile is trying to query mms, return empty cursor.
+            Log.e(TAG, "Managed profile is not allowed to query MMS.");
+            return emptyCursor;
+        }
+
         // First check if a restricted view of the "pdu" table should be used based on the
         // caller's identity. Only system, phone or the default sms app can have full access
         // of mms data. For other apps, we present a restricted view which only contains sent
@@ -307,6 +321,14 @@ public class MmsProvider extends ContentProvider {
 
     @Override
     public Uri insert(Uri uri, ContentValues values) {
+        UserManager userManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+        if ((userManager != null) && (userManager.isManagedProfile(
+                Binder.getCallingUserHandle().getIdentifier()))) {
+            // If work profile is trying to insert mms, return null.
+            Log.e(TAG, "Managed profile is not allowed to insert MMS.");
+            return null;
+        }
+
         final int callerUid = Binder.getCallingUid();
         final String callerPkg = getCallingPackage();
         int msgBox = Mms.MESSAGE_BOX_ALL;
@@ -622,6 +644,14 @@ public class MmsProvider extends ContentProvider {
     @Override
     public int delete(Uri uri, String selection,
             String[] selectionArgs) {
+        UserManager userManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+        if ((userManager != null) && (userManager.isManagedProfile(
+                Binder.getCallingUserHandle().getIdentifier()))) {
+            // If work profile is trying to delete mms, return 0.
+            Log.e(TAG, "Managed profile is not allowed to delete MMS.");
+            return 0;
+        }
+
         int match = sURLMatcher.match(uri);
         if (LOCAL_LOGV) {
             Log.v(TAG, "Delete uri=" + uri + ", match=" + match);
@@ -774,6 +804,14 @@ public class MmsProvider extends ContentProvider {
 
     @Override
     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        UserManager userManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+        if ((userManager != null) && (userManager.isManagedProfile(
+                Binder.getCallingUserHandle().getIdentifier()))) {
+            // If work profile is trying to update mms, return 0.
+            Log.e(TAG, "Managed profile is not allowed to update MMS.");
+            return 0;
+        }
+
         // The _data column is filled internally in MmsProvider, so this check is just to avoid
         // it from being inadvertently set. This is not supposed to be a protection against
         // malicious attack, since sql injection could still be attempted to bypass the check. On
@@ -1062,7 +1100,8 @@ public class MmsProvider extends ContentProvider {
         sURLMatcher.addURI("mms", "resetFilePerm/*",    MMS_PART_RESET_FILE_PERMISSION);
     }
 
-    private SQLiteOpenHelper mOpenHelper;
+    @VisibleForTesting
+    public SQLiteOpenHelper mOpenHelper;
 
     private static String concatSelections(String selection1, String selection2) {
         if (TextUtils.isEmpty(selection1)) {
diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
index 738963ed..41ab4dbd 100644
--- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -708,78 +708,97 @@ public class MmsSmsDatabaseHelper extends SQLiteOpenHelper {
         }
     }
 
+    @VisibleForTesting
+    public static String CREATE_ADDR_TABLE_STR =
+            "CREATE TABLE " + MmsProvider.TABLE_ADDR + " (" +
+                    Addr._ID + " INTEGER PRIMARY KEY," +
+                    Addr.MSG_ID + " INTEGER," +
+                    Addr.CONTACT_ID + " INTEGER," +
+                    Addr.ADDRESS + " TEXT," +
+                    Addr.TYPE + " INTEGER," +
+                    Addr.CHARSET + " INTEGER);";
+
+    @VisibleForTesting
+    public static String CREATE_PART_TABLE_STR =
+            "CREATE TABLE " + MmsProvider.TABLE_PART + " (" +
+                    Part._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    Part.MSG_ID + " INTEGER," +
+                    Part.SEQ + " INTEGER DEFAULT 0," +
+                    Part.CONTENT_TYPE + " TEXT," +
+                    Part.NAME + " TEXT," +
+                    Part.CHARSET + " INTEGER," +
+                    Part.CONTENT_DISPOSITION + " TEXT," +
+                    Part.FILENAME + " TEXT," +
+                    Part.CONTENT_ID + " TEXT," +
+                    Part.CONTENT_LOCATION + " TEXT," +
+                    Part.CT_START + " INTEGER," +
+                    Part.CT_TYPE + " TEXT," +
+                    Part._DATA + " TEXT," +
+                    Part.TEXT + " TEXT);";
+
+    public static String CREATE_PDU_TABLE_STR =
+            "CREATE TABLE " + MmsProvider.TABLE_PDU + " (" +
+                    Mms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    Mms.THREAD_ID + " INTEGER," +
+                    Mms.DATE + " INTEGER," +
+                    Mms.DATE_SENT + " INTEGER DEFAULT 0," +
+                    Mms.MESSAGE_BOX + " INTEGER," +
+                    Mms.READ + " INTEGER DEFAULT 0," +
+                    Mms.MESSAGE_ID + " TEXT," +
+                    Mms.SUBJECT + " TEXT," +
+                    Mms.SUBJECT_CHARSET + " INTEGER," +
+                    Mms.CONTENT_TYPE + " TEXT," +
+                    Mms.CONTENT_LOCATION + " TEXT," +
+                    Mms.EXPIRY + " INTEGER," +
+                    Mms.MESSAGE_CLASS + " TEXT," +
+                    Mms.MESSAGE_TYPE + " INTEGER," +
+                    Mms.MMS_VERSION + " INTEGER," +
+                    Mms.MESSAGE_SIZE + " INTEGER," +
+                    Mms.PRIORITY + " INTEGER," +
+                    Mms.READ_REPORT + " INTEGER," +
+                    Mms.REPORT_ALLOWED + " INTEGER," +
+                    Mms.RESPONSE_STATUS + " INTEGER," +
+                    Mms.STATUS + " INTEGER," +
+                    Mms.TRANSACTION_ID + " TEXT," +
+                    Mms.RETRIEVE_STATUS + " INTEGER," +
+                    Mms.RETRIEVE_TEXT + " TEXT," +
+                    Mms.RETRIEVE_TEXT_CHARSET + " INTEGER," +
+                    Mms.READ_STATUS + " INTEGER," +
+                    Mms.CONTENT_CLASS + " INTEGER," +
+                    Mms.RESPONSE_TEXT + " TEXT," +
+                    Mms.DELIVERY_TIME + " INTEGER," +
+                    Mms.DELIVERY_REPORT + " INTEGER," +
+                    Mms.LOCKED + " INTEGER DEFAULT 0," +
+                    Mms.SUBSCRIPTION_ID + " INTEGER DEFAULT "
+                    + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " +
+                    Mms.SEEN + " INTEGER DEFAULT 0," +
+                    Mms.CREATOR + " TEXT," +
+                    Mms.TEXT_ONLY + " INTEGER DEFAULT 0);";
+
+    @VisibleForTesting
+    public static String CREATE_RATE_TABLE_STR =
+            "CREATE TABLE " + MmsProvider.TABLE_RATE + " (" +
+                    Rate.SENT_TIME + " INTEGER);";
+
+    @VisibleForTesting
+    public static String CREATE_DRM_TABLE_STR =
+            "CREATE TABLE " + MmsProvider.TABLE_DRM + " (" +
+                    BaseColumns._ID + " INTEGER PRIMARY KEY," +
+                    "_data TEXT);";
+
+    @VisibleForTesting
     private void createMmsTables(SQLiteDatabase db) {
         // N.B.: Whenever the columns here are changed, the columns in
         // {@ref MmsSmsProvider} must be changed to match.
-        db.execSQL("CREATE TABLE " + MmsProvider.TABLE_PDU + " (" +
-                   Mms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
-                   Mms.THREAD_ID + " INTEGER," +
-                   Mms.DATE + " INTEGER," +
-                   Mms.DATE_SENT + " INTEGER DEFAULT 0," +
-                   Mms.MESSAGE_BOX + " INTEGER," +
-                   Mms.READ + " INTEGER DEFAULT 0," +
-                   Mms.MESSAGE_ID + " TEXT," +
-                   Mms.SUBJECT + " TEXT," +
-                   Mms.SUBJECT_CHARSET + " INTEGER," +
-                   Mms.CONTENT_TYPE + " TEXT," +
-                   Mms.CONTENT_LOCATION + " TEXT," +
-                   Mms.EXPIRY + " INTEGER," +
-                   Mms.MESSAGE_CLASS + " TEXT," +
-                   Mms.MESSAGE_TYPE + " INTEGER," +
-                   Mms.MMS_VERSION + " INTEGER," +
-                   Mms.MESSAGE_SIZE + " INTEGER," +
-                   Mms.PRIORITY + " INTEGER," +
-                   Mms.READ_REPORT + " INTEGER," +
-                   Mms.REPORT_ALLOWED + " INTEGER," +
-                   Mms.RESPONSE_STATUS + " INTEGER," +
-                   Mms.STATUS + " INTEGER," +
-                   Mms.TRANSACTION_ID + " TEXT," +
-                   Mms.RETRIEVE_STATUS + " INTEGER," +
-                   Mms.RETRIEVE_TEXT + " TEXT," +
-                   Mms.RETRIEVE_TEXT_CHARSET + " INTEGER," +
-                   Mms.READ_STATUS + " INTEGER," +
-                   Mms.CONTENT_CLASS + " INTEGER," +
-                   Mms.RESPONSE_TEXT + " TEXT," +
-                   Mms.DELIVERY_TIME + " INTEGER," +
-                   Mms.DELIVERY_REPORT + " INTEGER," +
-                   Mms.LOCKED + " INTEGER DEFAULT 0," +
-                   Mms.SUBSCRIPTION_ID + " INTEGER DEFAULT "
-                           + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " +
-                   Mms.SEEN + " INTEGER DEFAULT 0," +
-                   Mms.CREATOR + " TEXT," +
-                   Mms.TEXT_ONLY + " INTEGER DEFAULT 0" +
-                   ");");
-
-        db.execSQL("CREATE TABLE " + MmsProvider.TABLE_ADDR + " (" +
-                   Addr._ID + " INTEGER PRIMARY KEY," +
-                   Addr.MSG_ID + " INTEGER," +
-                   Addr.CONTACT_ID + " INTEGER," +
-                   Addr.ADDRESS + " TEXT," +
-                   Addr.TYPE + " INTEGER," +
-                   Addr.CHARSET + " INTEGER);");
-
-        db.execSQL("CREATE TABLE " + MmsProvider.TABLE_PART + " (" +
-                   Part._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
-                   Part.MSG_ID + " INTEGER," +
-                   Part.SEQ + " INTEGER DEFAULT 0," +
-                   Part.CONTENT_TYPE + " TEXT," +
-                   Part.NAME + " TEXT," +
-                   Part.CHARSET + " INTEGER," +
-                   Part.CONTENT_DISPOSITION + " TEXT," +
-                   Part.FILENAME + " TEXT," +
-                   Part.CONTENT_ID + " TEXT," +
-                   Part.CONTENT_LOCATION + " TEXT," +
-                   Part.CT_START + " INTEGER," +
-                   Part.CT_TYPE + " TEXT," +
-                   Part._DATA + " TEXT," +
-                   Part.TEXT + " TEXT);");
-
-        db.execSQL("CREATE TABLE " + MmsProvider.TABLE_RATE + " (" +
-                   Rate.SENT_TIME + " INTEGER);");
-
-        db.execSQL("CREATE TABLE " + MmsProvider.TABLE_DRM + " (" +
-                   BaseColumns._ID + " INTEGER PRIMARY KEY," +
-                   "_data TEXT);");
+        db.execSQL(CREATE_PDU_TABLE_STR);
+
+        db.execSQL(CREATE_ADDR_TABLE_STR);
+
+        db.execSQL(CREATE_PART_TABLE_STR);
+
+        db.execSQL(CREATE_RATE_TABLE_STR);
+
+        db.execSQL(CREATE_DRM_TABLE_STR);
 
         // Restricted view of pdu table, only sent/received messages without wap pushes
         db.execSQL("CREATE VIEW " + MmsProvider.VIEW_PDU_RESTRICTED + " AS " +
diff --git a/src/com/android/providers/telephony/MmsSmsProvider.java b/src/com/android/providers/telephony/MmsSmsProvider.java
index 1653cd98..a311eb4d 100644
--- a/src/com/android/providers/telephony/MmsSmsProvider.java
+++ b/src/com/android/providers/telephony/MmsSmsProvider.java
@@ -23,6 +23,7 @@ import android.content.Context;
 import android.content.UriMatcher;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
+import android.database.MatrixCursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
@@ -30,6 +31,7 @@ import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.BaseColumns;
 import android.provider.Telephony;
 import android.provider.Telephony.CanonicalAddressesColumns;
@@ -323,6 +325,16 @@ public class MmsSmsProvider extends ContentProvider {
     @Override
     public Cursor query(Uri uri, String[] projection,
             String selection, String[] selectionArgs, String sortOrder) {
+        Cursor emptyCursor = new MatrixCursor((projection == null) ?
+                (new String[] {}) : projection);
+        UserManager userManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+        if ((userManager != null) && (userManager.isManagedProfile(
+                Binder.getCallingUserHandle().getIdentifier()))) {
+            // If work profile is trying to query mms/sms, return empty cursor.
+            Log.e(LOG_TAG, "Managed profile is not allowed to query MMS/SMS.");
+            return emptyCursor;
+        }
+
         // First check if restricted views of the "sms" and "pdu" tables should be used based on the
         // caller's identity. Only system, phone or the default sms app can have full access
         // of sms/mms data. For other apps, we present a restricted view which only contains sent
@@ -1216,6 +1228,14 @@ public class MmsSmsProvider extends ContentProvider {
     @Override
     public int delete(Uri uri, String selection,
             String[] selectionArgs) {
+        UserManager userManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+        if ((userManager != null) && (userManager.isManagedProfile(
+                Binder.getCallingUserHandle().getIdentifier()))) {
+            // If work profile is trying to delete mms/sms, return 0.
+            Log.e(LOG_TAG, "Managed profile is not allowed to delete MMS/SMS.");
+            return 0;
+        }
+
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         Context context = getContext();
         int affectedRows = 0;
@@ -1272,8 +1292,18 @@ public class MmsSmsProvider extends ContentProvider {
 
     @Override
     public Uri insert(Uri uri, ContentValues values) {
-        if (URI_MATCHER.match(uri) == URI_PENDING_MSG) {
-            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        UserManager userManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+        if ((userManager != null) && (userManager.isManagedProfile(
+                Binder.getCallingUserHandle().getIdentifier()))) {
+            // If work profile is trying to insert mms/sms, return null.
+            Log.e(LOG_TAG, "Managed profile is not allowed to insert MMS/SMS.");
+            return null;
+        }
+
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        int matchIndex = URI_MATCHER.match(uri);
+
+        if (matchIndex == URI_PENDING_MSG) {
             long rowId = db.insert(TABLE_PENDING_MSG, null, values);
             return Uri.parse(uri + "/" + rowId);
         }
@@ -1283,6 +1313,14 @@ public class MmsSmsProvider extends ContentProvider {
     @Override
     public int update(Uri uri, ContentValues values,
             String selection, String[] selectionArgs) {
+        UserManager userManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+        if ((userManager != null) && (userManager.isManagedProfile(
+                Binder.getCallingUserHandle().getIdentifier()))) {
+            // If work profile is trying to update mms/sms, return 0.
+            Log.e(LOG_TAG, "Managed profile is not allowed to update MMS/SMS.");
+            return 0;
+        }
+
         final int callerUid = Binder.getCallingUid();
         final String callerPkg = getCallingPackage();
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
diff --git a/src/com/android/providers/telephony/SmsProvider.java b/src/com/android/providers/telephony/SmsProvider.java
index 2b40d7eb..986c93a1 100644
--- a/src/com/android/providers/telephony/SmsProvider.java
+++ b/src/com/android/providers/telephony/SmsProvider.java
@@ -32,6 +32,7 @@ import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Contacts;
 import android.provider.Telephony;
 import android.provider.Telephony.MmsSms;
@@ -113,6 +114,16 @@ public class SmsProvider extends ContentProvider {
     @Override
     public Cursor query(Uri url, String[] projectionIn, String selection,
             String[] selectionArgs, String sort) {
+        Cursor emptyCursor = new MatrixCursor((projectionIn == null) ?
+                (new String[] {}) : projectionIn);
+        UserManager userManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+        if ((userManager != null) && (userManager.isManagedProfile(
+                Binder.getCallingUserHandle().getIdentifier()))) {
+            // If work profile is trying to query sms, return empty cursor.
+            Log.e(TAG, "Managed profile is not allowed to query SMS.");
+            return emptyCursor;
+        }
+
         // First check if a restricted view of the "sms" table should be used based on the
         // caller's identity. Only system, phone or the default sms app can have full access
         // of sms data. For other apps, we present a restricted view which only contains sent
@@ -458,6 +469,14 @@ public class SmsProvider extends ContentProvider {
     }
 
     private Uri insertInner(Uri url, ContentValues initialValues, int callerUid, String callerPkg) {
+        UserManager userManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+        if ((userManager != null) && (userManager.isManagedProfile(
+                Binder.getCallingUserHandle().getIdentifier()))) {
+            // If work profile is trying to insert sms, return null.
+            Log.e(TAG, "Managed profile is not allowed to insert SMS.");
+            return null;
+        }
+
         ContentValues values;
         long rowID;
         int type = Sms.MESSAGE_TYPE_ALL;
@@ -651,6 +670,14 @@ public class SmsProvider extends ContentProvider {
 
     @Override
     public int delete(Uri url, String where, String[] whereArgs) {
+        UserManager userManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+        if ((userManager != null) && (userManager.isManagedProfile(
+                Binder.getCallingUserHandle().getIdentifier()))) {
+            // If work profile is trying to delete sms, return 0.
+            Log.e(TAG, "Managed profile is not allowed to delete SMS.");
+            return 0;
+        }
+
         int count;
         int match = sURLMatcher.match(url);
         SQLiteDatabase db = getWritableDatabase(match);
@@ -753,6 +780,14 @@ public class SmsProvider extends ContentProvider {
 
     @Override
     public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
+        UserManager userManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+        if ((userManager != null) && (userManager.isManagedProfile(
+                Binder.getCallingUserHandle().getIdentifier()))) {
+            // If work profile is trying to update sms, return 0.
+            Log.e(TAG, "Managed profile is not allowed to update SMS.");
+            return 0;
+        }
+
         final int callerUid = Binder.getCallingUid();
         final String callerPkg = getCallingPackage();
         int count = 0;
diff --git a/tests/src/com/android/providers/telephony/MmsProviderTest.java b/tests/src/com/android/providers/telephony/MmsProviderTest.java
new file mode 100644
index 00000000..e1010e01
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/MmsProviderTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.telephony;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.UserManager;
+import android.provider.Telephony;
+import android.telephony.TelephonyManager;
+import android.test.mock.MockContentResolver;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class MmsProviderTest extends TestCase {
+    private static final String TAG = "MmsProviderTest";
+
+    @Mock private Context mContext;
+    private MockContentResolver mContentResolver;
+    private MmsProviderTestable mMmsProviderTestable;
+    @Mock private PackageManager mPackageManager;
+
+    private int notifyChangeCount;
+    private UserManager mUserManager;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        mMmsProviderTestable = new MmsProviderTestable();
+        mUserManager = mock(UserManager.class);
+
+        // setup mocks
+        when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE)))
+                .thenReturn(mock(AppOpsManager.class));
+        when(mContext.getSystemService(eq(Context.TELEPHONY_SERVICE)))
+                .thenReturn(mock(TelephonyManager.class));
+        when(mContext.getSystemService(eq(Context.USER_SERVICE)))
+                .thenReturn(mUserManager);
+
+        when(mContext.checkCallingOrSelfPermission(anyString()))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mContext.getUserId()).thenReturn(0);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+
+        /**
+         * This is used to give the MmsProviderTest a mocked context which takes a
+         * SmsProvider and attaches it to the ContentResolver with telephony authority.
+         * The mocked context also gives WRITE_APN_SETTINGS permissions
+         */
+        mContentResolver = new MockContentResolver() {
+            @Override
+            public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork,
+                    int userHandle) {
+                notifyChangeCount++;
+            }
+        };
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+
+        // Add authority="mms" to given mmsProvider
+        ProviderInfo providerInfo = new ProviderInfo();
+        providerInfo.authority = "mms";
+
+        // Add context to given mmsProvider
+        mMmsProviderTestable.attachInfoForTesting(mContext, providerInfo);
+        Log.d(TAG, "MockContextWithProvider: mmsProvider.getContext(): "
+                + mMmsProviderTestable.getContext());
+
+        // Add given MmsProvider to mResolver with authority="mms" so that
+        // mResolver can send queries to mMmsProvider
+        mContentResolver.addProvider("mms", mMmsProviderTestable);
+        Log.d(TAG, "MockContextWithProvider: Add MmsProvider to mResolver");
+        notifyChangeCount = 0;
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mMmsProviderTestable.closeDatabase();
+    }
+
+    @Test
+    public void testInsertMms() {
+        final ContentValues values = new ContentValues();
+        values.put(Telephony.Mms.READ, 1);
+        values.put(Telephony.Mms.SEEN, 1);
+        values.put(Telephony.Mms.SUBSCRIPTION_ID, 1);
+        values.put(Telephony.Mms.MESSAGE_BOX, Telephony.Mms.MESSAGE_BOX_ALL);
+        values.put(Telephony.Mms.TEXT_ONLY, 1);
+        values.put(Telephony.Mms.THREAD_ID, 1);
+
+        Uri expected = Uri.parse("content://mms/1");
+        Uri actual = mContentResolver.insert(Telephony.Mms.CONTENT_URI, values);
+
+        assertEquals(expected, actual);
+        assertEquals(1, notifyChangeCount);
+    }
+
+    @Test
+    public void testInsertUsingManagedProfile() {
+        when(mUserManager.isManagedProfile(anyInt())).thenReturn(true);
+
+        try {
+            assertNull(mContentResolver.insert(Telephony.Mms.CONTENT_URI, null));
+        } catch (Exception e) {
+            Log.d(TAG, "Error inserting mms: " + e);
+        }
+    }
+
+    @Test
+    public void testQueryUsingManagedProfile() {
+        when(mUserManager.isManagedProfile(anyInt())).thenReturn(true);
+
+        try (Cursor cursor = mContentResolver.query(Telephony.Mms.CONTENT_URI,
+                null, null, null, null)) {
+            assertEquals(0, cursor.getCount());
+        } catch (Exception e) {
+            Log.d(TAG, "Exception in getting count: " + e);
+        }
+    }
+
+    @Test
+    public void testUpdateUsingManagedProfile() {
+        when(mUserManager.isManagedProfile(anyInt())).thenReturn(true);
+
+        try {
+            assertEquals(0, mContentResolver.update(Telephony.Mms.CONTENT_URI, null, null, null));
+        } catch (Exception e) {
+            Log.d(TAG, "Exception in updating mms: " + e);
+        }
+    }
+
+    @Test
+    public void testDeleteUsingManagedProfile() {
+        when(mUserManager.isManagedProfile(anyInt())).thenReturn(true);
+
+        try {
+            assertEquals(0, mContentResolver.delete(Telephony.Mms.CONTENT_URI, null, null));
+        } catch (Exception e) {
+            Log.d(TAG, "Exception in deleting mms: " + e);
+        }
+    }
+}
diff --git a/tests/src/com/android/providers/telephony/MmsProviderTestable.java b/tests/src/com/android/providers/telephony/MmsProviderTestable.java
new file mode 100644
index 00000000..cea411be
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/MmsProviderTestable.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.telephony;
+
+import static com.android.providers.telephony.MmsSmsDatabaseHelper.CREATE_ADDR_TABLE_STR;
+import static com.android.providers.telephony.MmsSmsDatabaseHelper.CREATE_DRM_TABLE_STR;
+import static com.android.providers.telephony.MmsSmsDatabaseHelper.CREATE_PART_TABLE_STR;
+import static com.android.providers.telephony.MmsSmsDatabaseHelper.CREATE_PDU_TABLE_STR;
+import static com.android.providers.telephony.MmsSmsDatabaseHelper.CREATE_RATE_TABLE_STR;
+
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+/**
+ * A subclass of MmsProvider used for testing on an in-memory database
+ */
+public class MmsProviderTestable extends MmsProvider {
+    private static final String TAG = "MmsProviderTestable";
+
+    @Override
+    public boolean onCreate() {
+        Log.d(TAG, "onCreate called: mDbHelper = new InMemoryMmsProviderDbHelper()");
+        mOpenHelper = new InMemoryMmsProviderDbHelper();
+        return true;
+    }
+
+    // close mDbHelper database object
+    protected void closeDatabase() {
+        mOpenHelper.close();
+    }
+
+    /**
+     * An in memory DB for MmsProviderTestable to use
+     */
+    public static class InMemoryMmsProviderDbHelper extends SQLiteOpenHelper {
+
+
+        public InMemoryMmsProviderDbHelper() {
+            super(null,      // no context is needed for in-memory db
+                    null,      // db file name is null for in-memory db
+                    null,      // CursorFactory is null by default
+                    1);        // db version is no-op for tests
+            Log.d(TAG, "InMemoryMmsProviderDbHelper creating in-memory database");
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            // Set up the mms tables
+            Log.d(TAG, "InMemoryMmsProviderDbHelper onCreate creating the mms tables");
+            db.execSQL(CREATE_PDU_TABLE_STR);
+            db.execSQL(CREATE_ADDR_TABLE_STR);
+            db.execSQL(CREATE_PART_TABLE_STR);
+            db.execSQL(CREATE_RATE_TABLE_STR);
+            db.execSQL(CREATE_DRM_TABLE_STR);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            Log.d(TAG, "InMemorySmsProviderDbHelper onUpgrade doing nothing");
+        }
+    }
+}
diff --git a/tests/src/com/android/providers/telephony/SmsProviderTest.java b/tests/src/com/android/providers/telephony/SmsProviderTest.java
index ba632039..13d9ae98 100644
--- a/tests/src/com/android/providers/telephony/SmsProviderTest.java
+++ b/tests/src/com/android/providers/telephony/SmsProviderTest.java
@@ -16,6 +16,10 @@
 
 package com.android.providers.telephony;
 
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
 import android.app.AppOpsManager;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -26,6 +30,7 @@ import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.UserManager;
 import android.provider.Telephony;
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentResolver;
@@ -57,6 +62,7 @@ public class SmsProviderTest extends TestCase {
     private MockContextWithProvider mContext;
     private MockContentResolver mContentResolver;
     private SmsProviderTestable mSmsProviderTestable;
+    private UserManager mUserManager;
 
     private int notifyChangeCount;
 
@@ -115,6 +121,8 @@ public class SmsProviderTest extends TestCase {
                     return Mockito.mock(AppOpsManager.class);
                 case Context.TELEPHONY_SERVICE:
                     return Mockito.mock(TelephonyManager.class);
+                case Context.USER_SERVICE:
+                    return mUserManager;
                 default:
                     return null;
             }
@@ -148,6 +156,8 @@ public class SmsProviderTest extends TestCase {
         mSmsProviderTestable = new SmsProviderTestable();
         mContext = new MockContextWithProvider(mSmsProviderTestable);
         mContentResolver = mContext.getContentResolver();
+        mUserManager = Mockito.mock(UserManager.class);
+
         notifyChangeCount = 0;
     }
 
@@ -254,6 +264,51 @@ public class SmsProviderTest extends TestCase {
         cursor.close();
     }
 
+    @Test
+    public void testInsertUsingManagedProfile() {
+        when(mUserManager.isManagedProfile(anyInt())).thenReturn(true);
+
+        try {
+            assertNull(mContentResolver.insert(Telephony.Sms.CONTENT_URI, null));
+        } catch (Exception e) {
+            Log.d(TAG, "Error inserting sms: " + e);
+        }
+    }
+
+    @Test
+    public void testQueryUsingManagedProfile() {
+        when(mUserManager.isManagedProfile(anyInt())).thenReturn(true);
+
+        try (Cursor cursor = mContentResolver.query(Telephony.Sms.CONTENT_URI,
+                null, null, null, null)) {
+            assertEquals(0, cursor.getCount());
+        } catch (Exception e) {
+            Log.d(TAG, "Exception in getting count: " + e);
+        }
+    }
+
+    @Test
+    public void testUpdateUsingManagedProfile() {
+        when(mUserManager.isManagedProfile(anyInt())).thenReturn(true);
+
+        try {
+            assertEquals(0, mContentResolver.update(Telephony.Sms.CONTENT_URI, null, null, null));
+        } catch (Exception e) {
+            Log.d(TAG, "Exception in updating sms: " + e);
+        }
+    }
+
+    @Test
+    public void testDeleteUsingManagedProfile() {
+        when(mUserManager.isManagedProfile(anyInt())).thenReturn(true);
+
+        try {
+            assertEquals(0, mContentResolver.delete(Telephony.Sms.CONTENT_URI, null, null));
+        } catch (Exception e) {
+            Log.d(TAG, "Exception in deleting sms: " + e);
+        }
+    }
+
     private ContentValues getFakeRawValue() {
         ContentValues values = new ContentValues();
         values.put("pdu", mFakePdu);
