Adding a new content value to prevent intent broadcasting during MMS
insert, and using it for MMS restore
Test: atest MmsProviderTest TelephonyBackupAgentTest
Change-Id: Ie4d4e79bb2d05f572b8abe437ec575bd25bcb250
diff --git a/.gitignore b/.gitignore
index bff2d76..7eb3721 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
*.iml
+*.idea
diff --git a/src/com/android/providers/telephony/MmsProvider.java b/src/com/android/providers/telephony/MmsProvider.java
index 35d6b04..04182ac 100644
--- a/src/com/android/providers/telephony/MmsProvider.java
+++ b/src/com/android/providers/telephony/MmsProvider.java
@@ -38,7 +38,6 @@
import android.provider.Telephony.CanonicalAddressesColumns;
import android.provider.Telephony.Mms;
import android.provider.Telephony.Mms.Addr;
-import android.provider.Telephony.Mms.Inbox;
import android.provider.Telephony.Mms.Part;
import android.provider.Telephony.Mms.Rate;
import android.provider.Telephony.MmsSms;
@@ -73,6 +72,13 @@
// The name of parts directory. The full dir is "app_parts".
static final String PARTS_DIR_NAME = "parts";
+ private ProviderUtilWrapper providerUtilWrapper = new ProviderUtilWrapper();
+
+ @VisibleForTesting
+ public void setProviderUtilWrapper(ProviderUtilWrapper providerUtilWrapper) {
+ this.providerUtilWrapper = providerUtilWrapper;
+ }
+
@Override
public boolean onCreate() {
setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS);
@@ -81,6 +87,14 @@
return true;
}
+ // wrapper class to allow easier mocking of the static ProviderUtil in tests
+ @VisibleForTesting
+ public static class ProviderUtilWrapper {
+ public boolean isAccessRestricted(Context context, String packageName, int uid) {
+ return ProviderUtil.isAccessRestricted(context, packageName, uid);
+ }
+ }
+
/**
* Return the proper view of "pdu" table for the current access status.
*
@@ -317,6 +331,15 @@
int msgBox = Mms.MESSAGE_BOX_ALL;
boolean notify = true;
+ boolean forceNoNotify = values.containsKey(TelephonyBackupAgent.NOTIFY)
+ && !values.getAsBoolean(TelephonyBackupAgent.NOTIFY);
+ values.remove(TelephonyBackupAgent.NOTIFY);
+ // check isAccessRestricted to prevent third parties from setting NOTIFY = false maliciously
+ if (forceNoNotify && !providerUtilWrapper.isAccessRestricted(
+ getContext(), getCallingPackage(), Binder.getCallingUid())) {
+ notify = false;
+ }
+
int match = sURLMatcher.match(uri);
if (LOCAL_LOGV) {
Log.v(TAG, "Insert uri=" + uri + ", match=" + match);
diff --git a/src/com/android/providers/telephony/TelephonyBackupAgent.java b/src/com/android/providers/telephony/TelephonyBackupAgent.java
index 34fed99..5f75117 100644
--- a/src/com/android/providers/telephony/TelephonyBackupAgent.java
+++ b/src/com/android/providers/telephony/TelephonyBackupAgent.java
@@ -31,11 +31,11 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
-import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
+import android.os.UserHandle;
import android.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.provider.Telephony;
@@ -288,6 +288,8 @@
private static final String QUOTA_RESET_TIME = "reset_quota_time";
private static final long QUOTA_RESET_INTERVAL = 30 * AlarmManager.INTERVAL_DAY; // 30 days.
+ // Key for explicitly settings whether mms restore should notify or not
+ static final String NOTIFY = "notify";
static {
// Consider restored messages read and seen by default. The actual data can override
@@ -732,6 +734,7 @@
jsonReader.beginArray();
int total = 0;
int numExceptions = 0;
+ final int notifyAfterCount = mMaxMsgPerFile;
while (jsonReader.hasNext()) {
final Mms mms = readMmsFromReader(jsonReader);
if (DEBUG) {
@@ -747,17 +750,32 @@
continue;
}
total++;
+ mms.values.put(NOTIFY, false);
addMmsMessage(mms);
+ // notifying every 1000 messages to follow sms restore pattern
+ if (total % notifyAfterCount == 0) {
+ notifyBulkMmsChange();
+ }
} catch (Exception e) {
Log.e(TAG, "putMmsMessagesToProvider", e);
numExceptions++;
DeferredSmsMmsRestoreService.localLog("putMmsMessagesToProvider: Exception " + e);
}
}
+ // notifying for any remaining messages
+ if (total % notifyAfterCount > 0) {
+ notifyBulkMmsChange();
+ }
Log.d(TAG, "putMmsMessagesToProvider handled " + total + " new messages.");
incremenentSharedPref(false, total, numExceptions);
}
+ private void notifyBulkMmsChange() {
+ mContentResolver.notifyChange(Telephony.MmsSms.CONTENT_URI, null,
+ ContentResolver.NOTIFY_SYNC_TO_NETWORK, UserHandle.USER_ALL);
+ ProviderUtil.notifyIfNotDefaultSmsApp(Telephony.Mms.CONTENT_URI, null, this);
+ }
+
@VisibleForTesting
static final String[] PROJECTION_ID = {BaseColumns._ID};
private static final int ID_IDX = 0;
diff --git a/tests/src/com/android/providers/telephony/MmsProviderTest.java b/tests/src/com/android/providers/telephony/MmsProviderTest.java
index 5f5de75..2e618b0 100644
--- a/tests/src/com/android/providers/telephony/MmsProviderTest.java
+++ b/tests/src/com/android/providers/telephony/MmsProviderTest.java
@@ -16,8 +16,10 @@
package com.android.providers.telephony;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -26,6 +28,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.Uri;
import android.provider.Telephony;
@@ -36,39 +39,40 @@
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;
@Override
protected void setUp() throws Exception {
super.setUp();
- MockitoAnnotations.initMocks(this);
+
mMmsProviderTestable = new MmsProviderTestable();
// setup mocks
- when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE)))
+ Context context = mock(Context.class);
+ PackageManager packageManager = mock(PackageManager.class);
+ Resources resources = mock(Resources.class);
+ when(context.getSystemService(eq(Context.APP_OPS_SERVICE)))
.thenReturn(mock(AppOpsManager.class));
- when(mContext.getSystemService(eq(Context.TELEPHONY_SERVICE)))
+ when(context.getSystemService(eq(Context.TELEPHONY_SERVICE)))
.thenReturn(mock(TelephonyManager.class));
- when(mContext.checkCallingOrSelfPermission(anyString()))
+ when(context.checkCallingOrSelfPermission(anyString()))
.thenReturn(PackageManager.PERMISSION_GRANTED);
- when(mContext.getUserId()).thenReturn(0);
- when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(context.getUserId()).thenReturn(0);
+ when(context.getPackageManager()).thenReturn(packageManager);
+ when(context.getResources()).thenReturn(resources);
+ when(resources.getString(anyInt())).thenReturn("");
/**
* This is used to give the MmsProviderTest a mocked context which takes a
- * SmsProvider and attaches it to the ContentResolver with telephony authority.
+ * MmsProvider and attaches it to the ContentResolver with telephony authority.
* The mocked context also gives WRITE_APN_SETTINGS permissions
*/
mContentResolver = new MockContentResolver() {
@@ -78,14 +82,14 @@
notifyChangeCount++;
}
};
- when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ when(context.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);
+ mMmsProviderTestable.attachInfoForTesting(context, providerInfo);
Log.d(TAG, "MockContextWithProvider: mmsProvider.getContext(): "
+ mMmsProviderTestable.getContext());
@@ -104,6 +108,35 @@
@Test
public void testInsertMms() {
+ final ContentValues values = getTestContentValues();
+
+ 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 testInsertMmsWithoutNotify() {
+
+ MmsProvider.ProviderUtilWrapper providerUtilWrapper =
+ mock(MmsProvider.ProviderUtilWrapper.class);
+ when(providerUtilWrapper.isAccessRestricted(
+ any(Context.class), anyString(), anyInt())).thenReturn(false);
+ mMmsProviderTestable.setProviderUtilWrapper(providerUtilWrapper);
+
+ final ContentValues values = getTestContentValues();
+ values.put(TelephonyBackupAgent.NOTIFY, false);
+
+ Uri expected = Uri.parse("content://mms/1");
+ Uri actual = mContentResolver.insert(Telephony.Mms.CONTENT_URI, values);
+
+ assertEquals(expected, actual);
+ assertEquals(0, notifyChangeCount);
+ }
+
+ private ContentValues getTestContentValues() {
final ContentValues values = new ContentValues();
values.put(Telephony.Mms.READ, 1);
values.put(Telephony.Mms.SEEN, 1);
@@ -111,11 +144,6 @@
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);
+ return values;
}
}
diff --git a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
index f6a9c7f..bf0f49b 100644
--- a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertArrayEquals;
import android.annotation.TargetApi;
-import android.app.backup.BackupDataOutput;
import android.app.backup.FullBackupDataOutput;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -43,6 +42,7 @@
import android.util.JsonWriter;
import android.util.SparseArray;
+import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.internal.telephony.PhoneFactory;
import libcore.io.IoUtils;
@@ -54,13 +54,11 @@
import org.json.JSONObject;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -652,13 +650,21 @@
/**
* Test restore mms with the empty json array "[]".
- * @throws Exception
*/
- public void testRestoreMms_NoMms() throws Exception {
+ public void testRestoreMms_NoMms() {
JsonReader jsonReader = new JsonReader(new StringReader(EMPTY_JSON_ARRAY));
FakeMmsProvider mmsProvider = new FakeMmsProvider(null);
mMockContentResolver.addProvider("mms", mmsProvider);
- mTelephonyBackupAgent.putMmsMessagesToProvider(jsonReader);
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyBackupAgent, (agent) -> {
+ try {
+ agent.putMmsMessagesToProvider(jsonReader);
+ } catch (IOException e) {
+ fail("Encountered exception: " + e);
+ }
+ return null;
+ }
+ );
assertEquals(0, mmsProvider.getRowsAdded());
}
@@ -670,7 +676,16 @@
JsonReader jsonReader = new JsonReader(new StringReader(addRandomDataToJson(mAllMmsJson)));
FakeMmsProvider mmsProvider = new FakeMmsProvider(mMmsAllContentValues);
mMockContentResolver.addProvider("mms", mmsProvider);
- mTelephonyBackupAgent.putMmsMessagesToProvider(jsonReader);
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyBackupAgent, (agent) -> {
+ try {
+ agent.putMmsMessagesToProvider(jsonReader);
+ } catch (IOException e) {
+ fail("Encountered exception: " + e);
+ }
+ return null;
+ }
+ );
assertEquals(18, mmsProvider.getRowsAdded());
assertEquals(mThreadProvider.mIsThreadArchived, mThreadProvider.mUpdateThreadsArchived);
}
@@ -684,7 +699,16 @@
(new StringReader(addRandomDataToJson(mMmsAllAttachmentJson)));
FakeMmsProvider mmsProvider = new FakeMmsProvider(mMmsAllContentValues);
mMockContentResolver.addProvider("mms", mmsProvider);
- mTelephonyBackupAgent.putMmsMessagesToProvider(jsonReader);
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyBackupAgent, (agent) -> {
+ try {
+ agent.putMmsMessagesToProvider(jsonReader);
+ } catch (IOException e) {
+ fail("Encountered exception: " + e);
+ }
+ return null;
+ }
+ );
assertEquals(7, mmsProvider.getRowsAdded());
}
@@ -694,7 +718,16 @@
FakeMmsProvider mmsProvider = new FakeMmsProvider(mMmsNullBodyContentValues);
mMockContentResolver.addProvider("mms", mmsProvider);
- mTelephonyBackupAgent.putMmsMessagesToProvider(jsonReader);
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyBackupAgent, (agent) -> {
+ try {
+ agent.putMmsMessagesToProvider(jsonReader);
+ } catch (IOException e) {
+ fail("Encountered exception: " + e);
+ }
+ return null;
+ }
+ );
assertEquals(3, mmsProvider.getRowsAdded());
}
@@ -943,6 +976,8 @@
for (String key : modifiedValues.keySet()) {
assertEquals("Key:"+key, modifiedValues.get(key), values.get(key));
}
+ values.remove(TelephonyBackupAgent.NOTIFY); // notify gets removed before final values
+
assertEquals(modifiedValues.size(), values.size());
return retUri;
}