Merge "Existing APN profile is incorrectly getting merged" am: 5045385168  -s ours am: 770b4b3817  -s ours am: 2e13a9b3fd  -s ours
am: f7f368ee85  -s ours

Change-Id: Id181f20e7ddc87ee403a8a0c5b3b0e2448da98cf
diff --git a/src/com/android/providers/telephony/CarrierDatabaseHelper.java b/src/com/android/providers/telephony/CarrierDatabaseHelper.java
index 5236b89..b654a77 100644
--- a/src/com/android/providers/telephony/CarrierDatabaseHelper.java
+++ b/src/com/android/providers/telephony/CarrierDatabaseHelper.java
@@ -21,6 +21,8 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.text.TextUtils;
+import android.util.Log;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -30,7 +32,7 @@
 
     private static final String DATABASE_NAME = "CarrierInformation.db";
     public static final String CARRIER_KEY_TABLE = "carrier_key";
-    private static final int DATABASE_VERSION = 1;
+    private static final int DATABASE_VERSION = 2;
 
     /**
      * CarrierDatabaseHelper carrier database helper class.
@@ -40,14 +42,15 @@
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
     }
 
-    static final String KEY_TYPE = "key_type";
-    static final String KEY = "key";
-    static final String MCC = "mcc";
-    static final String MNC = "mnc";
-    static final String MVNO_TYPE = "mvno_type";
-    static final String MVNO_MATCH_DATA = "mvno_match_data";
-    static final String PUBLIC_CERTIFICATE = "public_certificate";
-    static final String LAST_MODIFIED = "last_modified";
+    public static final String KEY_TYPE = "key_type";
+    public static final String MCC = "mcc";
+    public static final String MNC = "mnc";
+    public static final String MVNO_TYPE = "mvno_type";
+    public static final String MVNO_MATCH_DATA = "mvno_match_data";
+    public static final String PUBLIC_KEY = "public_key";
+    public static final String KEY_IDENTIFIER = "key_identifier";
+    public static final String EXPIRATION_TIME = "expiration_time";
+    public static final String LAST_MODIFIED = "last_modified";
 
     private static final List<String> CARRIERS_UNIQUE_FIELDS = new ArrayList<String>();
 
@@ -67,8 +70,9 @@
                 MVNO_TYPE + " TEXT DEFAULT ''," +
                 MVNO_MATCH_DATA + " TEXT DEFAULT ''," +
                 KEY_TYPE + " TEXT DEFAULT ''," +
-                KEY + " TEXT DEFAULT ''," +
-                PUBLIC_CERTIFICATE + " TEXT DEFAULT ''," +
+                KEY_IDENTIFIER + " TEXT DEFAULT ''," +
+                PUBLIC_KEY + " BLOB DEFAULT ''," +
+                EXPIRATION_TIME + " INTEGER DEFAULT 0," +
                 LAST_MODIFIED + " INTEGER DEFAULT 0," +
                 "UNIQUE (" + TextUtils.join(", ", CARRIERS_UNIQUE_FIELDS) + "));";
     }
@@ -78,8 +82,20 @@
         db.execSQL(getStringForCarrierKeyTableCreation(CARRIER_KEY_TABLE));
     }
 
+    public void createCarrierTable(SQLiteDatabase db) {
+        db.execSQL(getStringForCarrierKeyTableCreation(CARRIER_KEY_TABLE));
+    }
+
+    public void dropCarrierTable(SQLiteDatabase db) {
+        db.execSQL("DROP TABLE IF EXISTS " + CARRIER_KEY_TABLE + ";");
+    }
+
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-        // do nothing
+        Log.d(TAG, "dbh.onUpgrade:+ db=" + db + " oldV=" + oldVersion + " newV=" + newVersion);
+        if (oldVersion < 2) {
+            dropCarrierTable(db);
+            createCarrierTable(db);
+        }
     }
 }
diff --git a/src/com/android/providers/telephony/CarrierProvider.java b/src/com/android/providers/telephony/CarrierProvider.java
index 1c85806..a13c7e3 100644
--- a/src/com/android/providers/telephony/CarrierProvider.java
+++ b/src/com/android/providers/telephony/CarrierProvider.java
@@ -78,14 +78,14 @@
     @Override
     public Uri insert(Uri uri, ContentValues values) {
         values.put(CarrierDatabaseHelper.LAST_MODIFIED, System.currentTimeMillis());
-        long row = getWritableDatabase().insert(CarrierDatabaseHelper.CARRIER_KEY_TABLE,
+        long row = getWritableDatabase().insertOrThrow(CarrierDatabaseHelper.CARRIER_KEY_TABLE,
                 null, values);
         if (row > 0) {
             Uri newUri = ContentUris.withAppendedId(CONTENT_URI, row);
             getContext().getContentResolver().notifyChange(newUri, null);
             return newUri;
         }
-        throw new SQLException("Fail to add a new record into " + uri);
+        return null;
     }
 
     @Override
@@ -95,7 +95,7 @@
 
     @Override
     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-
+        values.put(CarrierDatabaseHelper.LAST_MODIFIED, System.currentTimeMillis());
         if (VDBG) {
             Log.d(TAG, "update:"
                     + " uri=" + uri
diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
index c9051d6..095313f 100644
--- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -39,6 +39,7 @@
 import android.telephony.SubscriptionManager;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.google.android.mms.pdu.EncodedStringValue;
 import com.google.android.mms.pdu.PduHeaders;
 
@@ -851,33 +852,44 @@
                    "END;");
     }
 
+    @VisibleForTesting
+    public static String CREATE_SMS_TABLE_STRING =
+            "CREATE TABLE sms (" +
+            "_id INTEGER PRIMARY KEY," +
+            "thread_id INTEGER," +
+            "address TEXT," +
+            "person INTEGER," +
+            "date INTEGER," +
+            "date_sent INTEGER DEFAULT 0," +
+            "protocol INTEGER," +
+            "read INTEGER DEFAULT 0," +
+            "status INTEGER DEFAULT -1," + // a TP-Status value
+            // or -1 if it
+            // status hasn't
+            // been received
+            "type INTEGER," +
+            "reply_path_present INTEGER," +
+            "subject TEXT," +
+            "body TEXT," +
+            "service_center TEXT," +
+            "locked INTEGER DEFAULT 0," +
+            "sub_id INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " +
+            "error_code INTEGER DEFAULT 0," +
+            "creator TEXT," +
+            "seen INTEGER DEFAULT 0" +
+            ");";
+
+    @VisibleForTesting
+    public static String CREATE_ATTACHMENTS_TABLE_STRING =
+            "CREATE TABLE attachments (" +
+            "sms_id INTEGER," +
+            "content_url TEXT," +
+            "offset INTEGER);";
+
     private void createSmsTables(SQLiteDatabase db) {
         // N.B.: Whenever the columns here are changed, the columns in
         // {@ref MmsSmsProvider} must be changed to match.
-        db.execSQL("CREATE TABLE sms (" +
-                   "_id INTEGER PRIMARY KEY," +
-                   "thread_id INTEGER," +
-                   "address TEXT," +
-                   "person INTEGER," +
-                   "date INTEGER," +
-                   "date_sent INTEGER DEFAULT 0," +
-                   "protocol INTEGER," +
-                   "read INTEGER DEFAULT 0," +
-                   "status INTEGER DEFAULT -1," + // a TP-Status value
-                                                  // or -1 if it
-                                                  // status hasn't
-                                                  // been received
-                   "type INTEGER," +
-                   "reply_path_present INTEGER," +
-                   "subject TEXT," +
-                   "body TEXT," +
-                   "service_center TEXT," +
-                   "locked INTEGER DEFAULT 0," +
-                   "sub_id INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " +
-                   "error_code INTEGER DEFAULT 0," +
-                   "creator TEXT," +
-                   "seen INTEGER DEFAULT 0" +
-                   ");");
+        db.execSQL(CREATE_SMS_TABLE_STRING);
 
         /**
          * This table is used by the SMS dispatcher to hold
@@ -899,10 +911,7 @@
                    // email address if from an email gateway, otherwise same as address
         );
 
-        db.execSQL("CREATE TABLE attachments (" +
-                   "sms_id INTEGER," +
-                   "content_url TEXT," +
-                   "offset INTEGER);");
+        db.execSQL(CREATE_ATTACHMENTS_TABLE_STRING);
 
         /**
          * This table is used by the SMS dispatcher to hold pending
diff --git a/src/com/android/providers/telephony/SmsProvider.java b/src/com/android/providers/telephony/SmsProvider.java
index eca18a6..37a1044 100644
--- a/src/com/android/providers/telephony/SmsProvider.java
+++ b/src/com/android/providers/telephony/SmsProvider.java
@@ -120,7 +120,7 @@
 
         // Generate the body of the query.
         int match = sURLMatcher.match(url);
-        SQLiteDatabase db = getDBOpenHelper(match).getReadableDatabase();
+        SQLiteDatabase db = getReadableDatabase(match);
         switch (match) {
             case SMS_ALL:
                 constructQueryForBox(qb, Sms.MESSAGE_TYPE_ALL, smsTable);
@@ -521,7 +521,7 @@
                 return null;
         }
 
-        SQLiteDatabase db = getDBOpenHelper(match).getWritableDatabase();
+        SQLiteDatabase db = getWritableDatabase(match);
 
         if (table.equals(TABLE_SMS)) {
             boolean addDate = false;
@@ -632,12 +632,7 @@
             db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv);
         }
         if (rowID > 0) {
-            Uri uri;
-            if (table == TABLE_SMS) {
-                uri = Uri.withAppendedPath(url, "/" + rowID);
-            } else {
-                uri = Uri.withAppendedPath(url, "/" + table + "/" + rowID );
-            }
+            Uri uri = Uri.withAppendedPath(url, String.valueOf(rowID));
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.d(TAG, "insert " + uri + " succeeded");
             }
@@ -653,7 +648,7 @@
     public int delete(Uri url, String where, String[] whereArgs) {
         int count;
         int match = sURLMatcher.match(url);
-        SQLiteDatabase db = getDBOpenHelper(match).getWritableDatabase();
+        SQLiteDatabase db = getWritableDatabase(match);
         boolean notifyIfNotDefault = true;
         switch (match) {
             case SMS_ALL:
@@ -760,7 +755,7 @@
         String extraWhere = null;
         boolean notifyIfNotDefault = true;
         int match = sURLMatcher.match(url);
-        SQLiteDatabase db = getDBOpenHelper(match).getWritableDatabase();
+        SQLiteDatabase db = getWritableDatabase(match);
 
         switch (match) {
             case SMS_RAW_MESSAGE:
@@ -922,4 +917,16 @@
         sURLMatcher.addURI("sms", "sim", SMS_ALL_ICC);
         sURLMatcher.addURI("sms", "sim/#", SMS_ICC);
     }
+
+    /**
+     * These methods can be overridden in a subclass for testing SmsProvider using an
+     * in-memory database.
+     */
+    SQLiteDatabase getReadableDatabase(int match) {
+        return getDBOpenHelper(match).getReadableDatabase();
+    }
+
+    SQLiteDatabase getWritableDatabase(int match) {
+        return  getDBOpenHelper(match).getWritableDatabase();
+    }
 }
diff --git a/tests/src/com/android/providers/telephony/CarrierProviderTest.java b/tests/src/com/android/providers/telephony/CarrierProviderTest.java
index 6a56343..b0b9b09 100644
--- a/tests/src/com/android/providers/telephony/CarrierProviderTest.java
+++ b/tests/src/com/android/providers/telephony/CarrierProviderTest.java
@@ -54,7 +54,7 @@
     private MockContentResolver mContentResolver;
     private CarrierProviderTestable mCarrierProviderTestable;
 
-    public static final String dummy_type = "TYPE5";
+    public static final int dummy_type = 1;
     public static final String dummy_mnc = "MNC001";
     public static final String dummy_mnc2 = "MNC002";
     public static final String dummy_mcc = "MCC005";
@@ -62,6 +62,8 @@
     public static final String dummy_key2 = "PUBKEY2";
     public static final String dummy_mvno_type = "100";
     public static final String dummy_mvno_match_data = "101";
+    public static final String  dummy_key_identifier_data = "key_identifier1";
+    public static final long  dummy_key_expiration = 1496795015L;
 
 
     /**
@@ -147,7 +149,9 @@
         contentValues.put(CarrierDatabaseHelper.MNC, dummy_mnc);
         contentValues.put(CarrierDatabaseHelper.MVNO_TYPE, dummy_mvno_type);
         contentValues.put(CarrierDatabaseHelper.MVNO_MATCH_DATA, dummy_mvno_match_data);
-        contentValues.put(CarrierDatabaseHelper.PUBLIC_CERTIFICATE, dummy_key1);
+        contentValues.put(CarrierDatabaseHelper.KEY_IDENTIFIER, dummy_key_identifier_data);
+        contentValues.put(CarrierDatabaseHelper.PUBLIC_KEY, dummy_key1.getBytes());
+        contentValues.put(CarrierDatabaseHelper.EXPIRATION_TIME, dummy_key_expiration);
 
         try {
             mContentResolver.insert(CarrierProvider.CONTENT_URI, contentValues);
@@ -181,7 +185,9 @@
         contentValues.put(CarrierDatabaseHelper.MNC, dummy_mnc);
         contentValues.put(CarrierDatabaseHelper.MVNO_TYPE, dummy_mvno_type);
         contentValues.put(CarrierDatabaseHelper.MVNO_MATCH_DATA, dummy_mvno_match_data);
-        contentValues.put(CarrierDatabaseHelper.PUBLIC_CERTIFICATE, dummy_key1);
+        contentValues.put(CarrierDatabaseHelper.KEY_IDENTIFIER, dummy_key_identifier_data);
+        contentValues.put(CarrierDatabaseHelper.PUBLIC_KEY, dummy_key1.getBytes());
+        contentValues.put(CarrierDatabaseHelper.EXPIRATION_TIME, dummy_key_expiration);
 
         try {
             mContentResolver.insert(CarrierProvider.CONTENT_URI, contentValues);
@@ -191,18 +197,19 @@
 
         try {
             ContentValues updatedValues = new ContentValues();
-            updatedValues.put(CarrierDatabaseHelper.PUBLIC_CERTIFICATE, dummy_key2);
+            updatedValues.put(CarrierDatabaseHelper.PUBLIC_KEY, dummy_key2);
             mContentResolver.update(CarrierProvider.CONTENT_URI, updatedValues,
-                    "mcc=? and mnc=? and key_type=?", new String[] { dummy_mcc, dummy_mnc, dummy_type });
+                    "mcc=? and mnc=? and key_type=?", new String[] { dummy_mcc, dummy_mnc,
+                            String.valueOf(dummy_type) });
         } catch (Exception e) {
             Log.d(TAG, "Error updating values:" + e);
         }
 
         try {
-            String[] columns ={CarrierDatabaseHelper.PUBLIC_CERTIFICATE};
+            String[] columns ={CarrierDatabaseHelper.PUBLIC_KEY};
             Cursor findEntry = mContentResolver.query(CarrierProvider.CONTENT_URI, columns,
                     "mcc=? and mnc=? and key_type=?",
-                    new String[] { dummy_mcc, dummy_mnc, dummy_type }, null);
+                    new String[] { dummy_mcc, dummy_mnc, String.valueOf(dummy_type) }, null);
             findEntry.moveToFirst();
             key = findEntry.getString(0);
         } catch (Exception e) {
@@ -224,7 +231,8 @@
         contentValues.put(CarrierDatabaseHelper.MNC, dummy_mnc);
         contentValues.put(CarrierDatabaseHelper.MVNO_TYPE, dummy_mvno_type);
         contentValues.put(CarrierDatabaseHelper.MVNO_MATCH_DATA, dummy_mvno_match_data);
-        contentValues.put(CarrierDatabaseHelper.PUBLIC_CERTIFICATE, dummy_key1);
+        contentValues.put(CarrierDatabaseHelper.KEY_IDENTIFIER, dummy_key_identifier_data);
+        contentValues.put(CarrierDatabaseHelper.PUBLIC_KEY, dummy_key1.getBytes());
 
         ContentValues contentValuesNew = new ContentValues();
         contentValuesNew.put(CarrierDatabaseHelper.KEY_TYPE, dummy_type);
@@ -232,7 +240,8 @@
         contentValuesNew.put(CarrierDatabaseHelper.MNC, dummy_mnc2);
         contentValuesNew.put(CarrierDatabaseHelper.MVNO_TYPE, dummy_mvno_type);
         contentValuesNew.put(CarrierDatabaseHelper.MVNO_MATCH_DATA, dummy_mvno_match_data);
-        contentValuesNew.put(CarrierDatabaseHelper.PUBLIC_CERTIFICATE, dummy_key2);
+        contentValues.put(CarrierDatabaseHelper.KEY_IDENTIFIER, dummy_key_identifier_data);
+        contentValuesNew.put(CarrierDatabaseHelper.PUBLIC_KEY, dummy_key2.getBytes());
 
         try {
             mContentResolver.insert(CarrierProvider.CONTENT_URI, contentValues);
@@ -266,7 +275,7 @@
         contentValues.put(CarrierDatabaseHelper.MNC, dummy_mnc);
         contentValues.put(CarrierDatabaseHelper.MVNO_TYPE, dummy_mvno_type);
         contentValues.put(CarrierDatabaseHelper.MVNO_MATCH_DATA, dummy_mvno_match_data);
-        contentValues.put(CarrierDatabaseHelper.PUBLIC_CERTIFICATE, dummy_key1);
+        contentValues.put(CarrierDatabaseHelper.PUBLIC_KEY, dummy_key1.getBytes());
 
         try {
             mContentResolver.insert(CarrierProvider.CONTENT_URI, contentValues);
diff --git a/tests/src/com/android/providers/telephony/SmsProviderTest.java b/tests/src/com/android/providers/telephony/SmsProviderTest.java
new file mode 100644
index 0000000..8fba85b
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/SmsProviderTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017 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 android.app.AppOpsManager;
+import android.content.ContentValues;
+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;
+import android.telephony.TelephonyManager;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+
+/**
+ * Tests for testing CRUD operations of SmsProvider.
+ * Uses a MockContentResolver to test insert
+ * Uses SmsProviderTestable to set up in-memory database
+ *
+ * Build, install and run the tests by running the commands below:
+ *     runtest --path <dir or file>
+ *     runtest --path <dir or file> --test-method <testMethodName>
+ *     e.g.)
+ *         runtest --path tests/src/com/android/providers/telephony/SmsProviderTest.java \
+ *                 --test-method testInsertUri
+ */
+public class SmsProviderTest extends TestCase {
+    private static final String TAG = "TelephonyProviderTest";
+
+    private MockContextWithProvider mContext;
+    private MockContentResolver mContentResolver;
+    private SmsProviderTestable mSmsProviderTestable;
+
+    private int notifyChangeCount;
+
+
+    /**
+     * This is used to give the SmsProviderTest 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
+     */
+    private class MockContextWithProvider extends MockContext {
+        private final MockContentResolver mResolver;
+
+        public MockContextWithProvider(SmsProvider smsProvider) {
+            mResolver = new MockContentResolver() {
+                @Override
+                public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork,
+                        int userHandle) {
+                    notifyChangeCount++;
+                }
+            };
+
+            // Add authority="sms" to given smsProvider
+            ProviderInfo providerInfo = new ProviderInfo();
+            providerInfo.authority = "sms";
+
+            // Add context to given smsProvider
+            smsProvider.attachInfoForTesting(this, providerInfo);
+            Log.d(TAG, "MockContextWithProvider: smsProvider.getContext(): "
+                    + smsProvider.getContext());
+
+            // Add given SmsProvider to mResolver with authority="sms" so that
+            // mResolver can send queries to mSmsProvider
+            mResolver.addProvider("sms", smsProvider);
+            Log.d(TAG, "MockContextWithProvider: Add SmsProvider to mResolver");
+        }
+
+        @Override
+        public Object getSystemService(String name) {
+            Log.d(TAG, "getSystemService: returning null");
+            switch (name) {
+                case Context.APP_OPS_SERVICE:
+                    return Mockito.mock(AppOpsManager.class);
+                case Context.TELEPHONY_SERVICE:
+                    return Mockito.mock(TelephonyManager.class);
+                default:
+                    return null;
+            }
+        }
+
+        @Override
+        public Resources getResources() {
+            Log.d(TAG, "getResources: returning null");
+            return null;
+        }
+
+        @Override
+        public int getUserId() {
+            return 0;
+        }
+
+        @Override
+        public MockContentResolver getContentResolver() {
+            return mResolver;
+        }
+
+        @Override
+        public int checkCallingOrSelfPermission(String permission) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mSmsProviderTestable = new SmsProviderTestable();
+        mContext = new MockContextWithProvider(mSmsProviderTestable);
+        mContentResolver = mContext.getContentResolver();
+        notifyChangeCount = 0;
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mSmsProviderTestable.closeDatabase();
+    }
+
+    @Test
+    @SmallTest
+    public void testInsertUri() {
+        // insert test contentValues
+        final ContentValues values = new ContentValues();
+        values.put(Telephony.Sms.SUBSCRIPTION_ID, 1);
+        values.put(Telephony.Sms.ADDRESS, "12345");
+        values.put(Telephony.Sms.BODY, "test");
+        values.put(Telephony.Sms.DATE, System.currentTimeMillis()); // milliseconds
+        values.put(Telephony.Sms.SEEN, 1);
+        values.put(Telephony.Sms.READ, 1);
+        values.put(Telephony.Sms.THREAD_ID, 1);
+
+        // test for sms table
+        Log.d(TAG, "testInsertSmsTable Inserting contentValues: " + values);
+        assertEquals(Uri.parse("content://sms/1"),
+                mContentResolver.insert(Uri.parse("content://sms"), values));
+        assertEquals(Uri.parse("content://sms/2"),
+                mContentResolver.insert(Uri.parse("content://sms"), values));
+
+        // test for attachments table
+        values.clear();
+        values.put("sms_id", 1);
+        values.put("content_url", "test");
+        values.put("offset", 0);
+        Log.d(TAG, "testInsertAttachmentTable Inserting contentValues: " + values);
+        assertEquals(Uri.parse("content://sms/attachments/1"),
+                mContentResolver.insert(Uri.parse("content://sms/attachments"), values));
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/providers/telephony/SmsProviderTestable.java b/tests/src/com/android/providers/telephony/SmsProviderTestable.java
new file mode 100644
index 0000000..9c3372e
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/SmsProviderTestable.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 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 android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+/**
+ * A subclass of SmsProvider used for testing on an in-memory database
+ */
+public class SmsProviderTestable extends SmsProvider {
+    private static final String TAG = "SmsProviderTestable";
+
+    private InMemorySmsProviderDbHelper mDbHelper;
+
+    @Override
+    public boolean onCreate() {
+        Log.d(TAG, "onCreate called: mDbHelper = new InMemorySmsProviderDbHelper()");
+        mDbHelper = new InMemorySmsProviderDbHelper();
+        return true;
+    }
+
+    @Override
+    SQLiteDatabase getReadableDatabase(int match) {
+        Log.d(TAG, "getReadableDatabase called");
+        return mDbHelper.getReadableDatabase();
+    }
+
+    @Override
+    SQLiteDatabase getWritableDatabase(int match) {
+        Log.d(TAG, "getWritableDatabase called");
+        return mDbHelper.getWritableDatabase();
+    }
+
+    // close mDbHelper database object
+    protected void closeDatabase() {
+        mDbHelper.close();
+    }
+
+    /**
+     * An in memory DB for SmsProviderTestable to use
+     */
+    public static class InMemorySmsProviderDbHelper extends SQLiteOpenHelper {
+
+
+        public InMemorySmsProviderDbHelper() {
+            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, "InMemorySmsProviderDbHelper creating in-memory database");
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            // Set up the sms tables
+            Log.d(TAG, "InMemorySmsProviderDbHelper onCreate creating the sms tables");
+            db.execSQL(MmsSmsDatabaseHelper.CREATE_SMS_TABLE_STRING);
+            db.execSQL(MmsSmsDatabaseHelper.CREATE_ATTACHMENTS_TABLE_STRING);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            Log.d(TAG, "InMemorySmsProviderDbHelper onUpgrade doing nothing");
+            return;
+        }
+    }
+}
+