Import translations. DO NOT MERGE
am: 47c74c47a0  -s ours

Change-Id: I9ec14e77aaf3be51a8d2b5e87b33355398fba238
diff --git a/res/values-be-rBY/config.xml b/res/values-be-rBY/config.xml
deleted file mode 100644
index 99877a6..0000000
--- a/res/values-be-rBY/config.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="persist_apns_for_plmn">
-    <item msgid="6413072509259000954">"20404"</item>
-    <item msgid="5639159280778239123">"310004"</item>
-    <item msgid="3860605521380788028">"310120"</item>
-    <item msgid="537693705785480198">"311480"</item>
-  </string-array>
-</resources>
diff --git a/res/values-be-rBY/strings.xml b/res/values-be-rBY/strings.xml
deleted file mode 100644
index 3c86bf4..0000000
--- a/res/values-be-rBY/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2008 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" product="tablet" msgid="9194799012395299737">"Налады мабiльнай сеткi"</string>
-    <string name="app_label" product="default" msgid="8338087656149558019">"Сховішча для тэлефаніі і паведамленняў"</string>
-</resources>
diff --git a/res/values-bs-rBA/config.xml b/res/values-bs-rBA/config.xml
deleted file mode 100644
index 99877a6..0000000
--- a/res/values-bs-rBA/config.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string-array name="persist_apns_for_plmn">
-    <item msgid="6413072509259000954">"20404"</item>
-    <item msgid="5639159280778239123">"310004"</item>
-    <item msgid="3860605521380788028">"310120"</item>
-    <item msgid="537693705785480198">"311480"</item>
-  </string-array>
-</resources>
diff --git a/res/values-bs-rBA/strings.xml b/res/values-bs-rBA/strings.xml
deleted file mode 100644
index 9d19176..0000000
--- a/res/values-bs-rBA/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2008 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" product="tablet" msgid="9194799012395299737">"Konfiguracija mobilne mreže"</string>
-    <string name="app_label" product="default" msgid="8338087656149558019">"Pohrana za telefon i poruke"</string>
-</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index cb697b4..865f254 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -17,5 +17,5 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" product="tablet" msgid="9194799012395299737">"Configuración de red móvil"</string>
-    <string name="app_label" product="default" msgid="8338087656149558019">"Almacenamiento de mensajes y teléfono"</string>
+    <string name="app_label" product="default" msgid="8338087656149558019">"Almac. Mensajes/Teléfono"</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 96e7dcb..6d2ef10 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -16,6 +16,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" product="tablet" msgid="9194799012395299737">"תצורת רשת סלולרית"</string>
+    <string name="app_label" product="tablet" msgid="9194799012395299737">"תצורת רשת לנייד"</string>
     <string name="app_label" product="default" msgid="8338087656149558019">"טלפון ואחסון הודעות"</string>
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 39fa7b9..a2c590c 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -16,6 +16,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" product="tablet" msgid="9194799012395299737">"Configurația rețelei de telefonie mobilă"</string>
+    <string name="app_label" product="tablet" msgid="9194799012395299737">"Configurația reţelei de telefonie mobilă"</string>
     <string name="app_label" product="default" msgid="8338087656149558019">"Stocare Telefon/Mesagerie"</string>
 </resources>
diff --git a/src/com/android/providers/telephony/MmsProvider.java b/src/com/android/providers/telephony/MmsProvider.java
index 71e84c6..f588b23 100644
--- a/src/com/android/providers/telephony/MmsProvider.java
+++ b/src/com/android/providers/telephony/MmsProvider.java
@@ -510,7 +510,7 @@
                 // we're using the row id of the part table row but we're also using ids
                 // from the sms table so this divides the space into two large chunks.
                 // The row ids from the part table start at 2 << 32.
-                cv.put(Telephony.MmsSms.WordsTable.ID, (2 << 32) + rowId);
+                cv.put(Telephony.MmsSms.WordsTable.ID, (2L << 32) + rowId);
                 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("text"));
                 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowId);
                 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 2);
@@ -1016,4 +1016,3 @@
         }
     }
 }
-
diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
index 9218e64..c9051d6 100644
--- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -23,6 +23,7 @@
 import android.content.IntentFilter;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.os.storage.StorageManager;
 import android.provider.BaseColumns;
@@ -231,11 +232,14 @@
 
     private static MmsSmsDatabaseHelper sDeInstance = null;
     private static MmsSmsDatabaseHelper sCeInstance = null;
+
+    private static final String[] BIND_ARGS_NONE = new String[0];
+
     private static boolean sTriedAutoIncrement = false;
     private static boolean sFakeLowStorageTest = false;     // for testing only
 
     static final String DATABASE_NAME = "mmssms.db";
-    static final int DATABASE_VERSION = 65;
+    static final int DATABASE_VERSION = 66;
     private final Context mContext;
     private LowStorageMonitor mLowStorageMonitor;
 
@@ -319,133 +323,110 @@
 
     public static void updateThread(SQLiteDatabase db, long thread_id) {
         if (thread_id < 0) {
-            updateAllThreads(db, null, null);
+            updateThreads(db, null, null);
             return;
         }
-
-        db.beginTransaction();
-        try {
-            // Delete the row for this thread in the threads table if
-            // there are no more messages attached to it in either
-            // the sms or pdu tables.
-            int rows = db.delete(MmsSmsProvider.TABLE_THREADS,
-                      "_id = ? AND _id NOT IN" +
-                      "          (SELECT thread_id FROM sms " +
-                      "           UNION SELECT thread_id FROM pdu)",
-                      new String[] { String.valueOf(thread_id) });
-            if (rows > 0) {
-                // If this deleted a row, let's remove orphaned canonical_addresses and get outta here
-                removeUnferencedCanonicalAddresses(db);
-            } else {
-                // Update the message count in the threads table as the sum
-                // of all messages in both the sms and pdu tables.
-                db.execSQL(
-                        "  UPDATE threads SET message_count = " +
-                                "     (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
-                                "      ON threads._id = " + Sms.THREAD_ID +
-                                "      WHERE " + Sms.THREAD_ID + " = " + thread_id +
-                                "        AND sms." + Sms.TYPE + " != 3) + " +
-                                "     (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
-                                "      ON threads._id = " + Mms.THREAD_ID +
-                                "      WHERE " + Mms.THREAD_ID + " = " + thread_id +
-                                "        AND (m_type=132 OR m_type=130 OR m_type=128)" +
-                                "        AND " + Mms.MESSAGE_BOX + " != 3) " +
-                                "  WHERE threads._id = " + thread_id + ";");
-
-                // Update the date and the snippet (and its character set) in
-                // the threads table to be that of the most recent message in
-                // the thread.
-                db.execSQL(
-                "  UPDATE threads" +
-                "  SET" +
-                "  date =" +
-                "    (SELECT date FROM" +
-                "        (SELECT date * 1000 AS date, thread_id FROM pdu" +
-                "         UNION SELECT date, thread_id FROM sms)" +
-                "     WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)," +
-                "  snippet =" +
-                "    (SELECT snippet FROM" +
-                "        (SELECT date * 1000 AS date, sub AS snippet, thread_id FROM pdu" +
-                "         UNION SELECT date, body AS snippet, thread_id FROM sms)" +
-                "     WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)," +
-                "  snippet_cs =" +
-                "    (SELECT snippet_cs FROM" +
-                "        (SELECT date * 1000 AS date, sub_cs AS snippet_cs, thread_id FROM pdu" +
-                "         UNION SELECT date, 0 AS snippet_cs, thread_id FROM sms)" +
-                "     WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)" +
-                "  WHERE threads._id = " + thread_id + ";");
-
-                // Update the error column of the thread to indicate if there
-                // are any messages in it that have failed to send.
-                // First check to see if there are any messages with errors in this thread.
-                String query = "SELECT thread_id FROM sms WHERE type=" +
-                        Telephony.TextBasedSmsColumns.MESSAGE_TYPE_FAILED +
-                        " AND thread_id = " + thread_id +
-                        " LIMIT 1";
-                int setError = 0;
-                Cursor c = db.rawQuery(query, null);
-                if (c != null) {
-                    try {
-                        setError = c.getCount();    // Because of the LIMIT 1, count will be 1 or 0.
-                    } finally {
-                        c.close();
-                    }
-                }
-                // What's the current state of the error flag in the threads table?
-                String errorQuery = "SELECT error FROM threads WHERE _id = " + thread_id;
-                c = db.rawQuery(errorQuery, null);
-                if (c != null) {
-                    try {
-                        if (c.moveToNext()) {
-                            int curError = c.getInt(0);
-                            if (curError != setError) {
-                                // The current thread error column differs, update it.
-                                db.execSQL("UPDATE threads SET error=" + setError +
-                                        " WHERE _id = " + thread_id);
-                            }
-                        }
-                    } finally {
-                        c.close();
-                    }
-                }
-            }
-            db.setTransactionSuccessful();
-        } catch (Throwable ex) {
-            Log.e(TAG, ex.getMessage(), ex);
-        } finally {
-            db.endTransaction();
-        }
+        updateThreads(db, "(thread_id = ?)", new String[]{ String.valueOf(thread_id) });
     }
 
-    public static void updateAllThreads(SQLiteDatabase db, String where, String[] whereArgs) {
+    /**
+     * Update all threads containing SMS matching the 'where' condition. Note that the condition
+     * is applied to individual messages in the sms table, NOT the threads table.
+     */
+    public static void updateThreads(SQLiteDatabase db, String where, String[] whereArgs) {
+        if (where == null) {
+            where = "1";
+        }
+        if (whereArgs == null) {
+            whereArgs = BIND_ARGS_NONE;
+        }
         db.beginTransaction();
         try {
-            if (where == null) {
-                where = "";
-            } else {
-                where = "WHERE (" + where + ")";
+            // Delete rows in the threads table if
+            // there are no more messages attached to it in either
+            // the sms or pdu tables.
+            // Note that we do this regardless of whether they match 'where'.
+            int rows = db.delete(MmsSmsProvider.TABLE_THREADS,
+                    "_id NOT IN (" +
+                        " SELECT DISTINCT thread_id FROM sms WHERE thread_id IS NOT NULL" +
+                        " UNION" +
+                        " SELECT DISTINCT thread_id FROM pdu WHERE thread_id IS NOT NULL)",
+                        null);
+            if (rows > 0) {
+                // If this deleted a row, let's remove orphaned canonical_addresses
+                removeUnferencedCanonicalAddresses(db);
             }
-            String query = "SELECT _id FROM threads WHERE _id IN " +
-                           "(SELECT DISTINCT thread_id FROM sms " + where + ")";
-            Cursor c = db.rawQuery(query, whereArgs);
-            if (c != null) {
-                try {
-                    while (c.moveToNext()) {
-                        updateThread(db, c.getInt(0));
-                    }
-                } finally {
-                    c.close();
-                }
-            }
-            // TODO: there are several db operations in this function. Lets wrap them in a
-            // transaction to make it faster.
-            // remove orphaned threads
-            db.delete(MmsSmsProvider.TABLE_THREADS,
-                    "_id NOT IN (SELECT DISTINCT thread_id FROM sms where thread_id NOT NULL " +
-                    "UNION SELECT DISTINCT thread_id FROM pdu where thread_id NOT NULL)", null);
 
-            // remove orphaned canonical_addresses
-            removeUnferencedCanonicalAddresses(db);
+            // Update the message count in the threads table as the sum
+            // of all messages in both the sms and pdu tables.
+            db.execSQL(
+                    " UPDATE threads" +
+                    " SET message_count = (" +
+                        " SELECT COUNT(sms._id) FROM sms" +
+                        " WHERE " + Sms.THREAD_ID + " = threads._id" +
+                        " AND sms." + Sms.TYPE + " != 3" +
+                    " ) + (" +
+                        " SELECT COUNT(pdu._id) FROM pdu" +
+                        " WHERE " + Mms.THREAD_ID + " = threads._id" +
+                        " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+                        " AND " + Mms.MESSAGE_BOX + " != 3" +
+                    " )" +
+                    " WHERE EXISTS (" +
+                        " SELECT _id" +
+                        " FROM sms" +
+                        " WHERE thread_id = threads._id" +
+                        " AND (" + where + ")" +
+                        " LIMIT 1" +
+                    " );",
+                    whereArgs);
+
+            // Update the date and the snippet (and its character set) in
+            // the threads table to be that of the most recent message in
+            // the thread.
+            db.execSQL(
+                    " WITH matches AS (" +
+                        " SELECT date * 1000 AS date, sub AS snippet, sub_cs AS snippet_cs, thread_id" +
+                        " FROM pdu" +
+                        " WHERE thread_id = threads._id" +
+                        " UNION" +
+                        " SELECT date, body AS snippet, 0 AS snippet_cs, thread_id" +
+                        " FROM sms" +
+                        " WHERE thread_id = threads._id" +
+                        " ORDER BY date DESC" +
+                        " LIMIT 1" +
+                    " )" +
+                    " UPDATE threads" +
+                    " SET date   = (SELECT date FROM matches)," +
+                        " snippet    = (SELECT snippet FROM matches)," +
+                        " snippet_cs = (SELECT snippet_cs FROM matches)" +
+                    " WHERE EXISTS (" +
+                        " SELECT _id" +
+                        " FROM sms" +
+                        " WHERE thread_id = threads._id" +
+                        " AND (" + where + ")" +
+                        " LIMIT 1" +
+                    " );",
+                    whereArgs);
+
+            // Update the error column of the thread to indicate if there
+            // are any messages in it that have failed to send.
+            // First check to see if there are any messages with errors in this thread.
+            db.execSQL(
+                    " UPDATE threads" +
+                    " SET error = EXISTS (" +
+                        " SELECT type" +
+                        " FROM sms" +
+                        " WHERE type=" + Telephony.TextBasedSmsColumns.MESSAGE_TYPE_FAILED +
+                        " AND thread_id = threads._id" +
+                    " )" +
+                    " WHERE EXISTS (" +
+                        " SELECT _id" +
+                        " FROM sms" +
+                        " WHERE thread_id = threads._id" +
+                        " AND (" + where + ")" +
+                        " LIMIT 1" +
+                    " );",
+                    whereArgs);
 
             db.setTransactionSuccessful();
         } catch (Throwable ex) {
@@ -581,6 +562,7 @@
 
     private void createIndices(SQLiteDatabase db) {
         createThreadIdIndex(db);
+        createThreadIdDateIndex(db);
     }
 
     private void createThreadIdIndex(SQLiteDatabase db) {
@@ -592,6 +574,15 @@
         }
     }
 
+    private void createThreadIdDateIndex(SQLiteDatabase db) {
+        try {
+            db.execSQL("CREATE INDEX IF NOT EXISTS threadIdDateIndex ON sms" +
+            " (thread_id, date);");
+        } catch (Exception ex) {
+            Log.e(TAG, "got exception creating indices: " + ex.toString());
+        }
+    }
+
     private void createMmsTables(SQLiteDatabase db) {
         // N.B.: Whenever the columns here are changed, the columns in
         // {@ref MmsSmsProvider} must be changed to match.
@@ -1426,7 +1417,8 @@
 
             db.beginTransaction();
             try {
-                upgradeDatabaseToVersion63(db);
+                // upgrade to 63: just add a happy little index.
+                createThreadIdDateIndex(db);
                 db.setTransactionSuccessful();
             } catch (Throwable ex) {
                 Log.e(TAG, ex.getMessage(), ex);
@@ -1451,10 +1443,11 @@
                 db.endTransaction();
             }
             // fall through
-         case 64:
+        case 64:
             if (currentVersion <= 64) {
                 return;
             }
+
             db.beginTransaction();
             try {
                 upgradeDatabaseToVersion65(db);
@@ -1465,6 +1458,22 @@
             } finally {
                 db.endTransaction();
             }
+            // fall through
+        case 65:
+            if (currentVersion <= 65) {
+                return;
+            }
+
+            db.beginTransaction();
+            try {
+                upgradeDatabaseToVersion66(db);
+                db.setTransactionSuccessful();
+            } catch (Throwable ex) {
+                Log.e(TAG, ex.getMessage(), ex);
+                break;
+            } finally {
+                db.endTransaction();
+            }
 
             return;
         }
@@ -1740,16 +1749,32 @@
             " WHERE INSTR(" + Part._DATA + ", '" + partsDirName + "') > 0");
     }
 
-    private void upgradeDatabaseToVersion63(SQLiteDatabase db) {
+    private void upgradeDatabaseToVersion64(SQLiteDatabase db) {
         db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW +" ADD COLUMN deleted INTEGER DEFAULT 0");
     }
 
-    private void upgradeDatabaseToVersion64(SQLiteDatabase db) {
-        db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW +" ADD COLUMN message_body TEXT");
+    private void upgradeDatabaseToVersion65(SQLiteDatabase db) {
+        // aosp and internal code diverged at version 63. Aosp did createThreadIdDateIndex() on
+        // upgrading to 63, whereas internal (nyc) added column 'deleted'. A device upgrading from
+        // nyc will have columns deleted and message_body in raw table with version 64, but not
+        // createThreadIdDateIndex()
+        try {
+            db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW + " ADD COLUMN message_body TEXT");
+        } catch (SQLiteException e) {
+            Log.w(TAG, "[upgradeDatabaseToVersion65] Exception adding column message_body; " +
+                    "trying createThreadIdDateIndex() instead: " + e);
+            createThreadIdDateIndex(db);
+        }
     }
 
-    private void upgradeDatabaseToVersion65(SQLiteDatabase db) {
-        db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW + " ADD COLUMN display_originating_addr TEXT");
+    private void upgradeDatabaseToVersion66(SQLiteDatabase db) {
+        try {
+            db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW
+                    + " ADD COLUMN display_originating_addr TEXT");
+        } catch (SQLiteException e) {
+            Log.e(TAG, "[upgradeDatabaseToVersion66] Exception adding column "
+                    + "display_originating_addr; " + e);
+        }
     }
 
     @Override
diff --git a/src/com/android/providers/telephony/MmsSmsProvider.java b/src/com/android/providers/telephony/MmsSmsProvider.java
index d5e0ef9..1ea4d5c 100644
--- a/src/com/android/providers/telephony/MmsSmsProvider.java
+++ b/src/com/android/providers/telephony/MmsSmsProvider.java
@@ -1232,10 +1232,10 @@
                 affectedRows = MmsProvider.deleteMessages(context, db,
                                         selection, selectionArgs, uri)
                         + db.delete("sms", selection, selectionArgs);
-                // Intentionally don't pass the selection variable to updateAllThreads.
+                // Intentionally don't pass the selection variable to updateThreads.
                 // When we pass in "locked=0" there, the thread will get excluded from
                 // the selection and not get updated.
-                MmsSmsDatabaseHelper.updateAllThreads(db, null, null);
+                MmsSmsDatabaseHelper.updateThreads(db, null, null);
                 break;
             case URI_OBSOLETE_THREADS:
                 affectedRows = db.delete(TABLE_THREADS,
diff --git a/src/com/android/providers/telephony/SmsProvider.java b/src/com/android/providers/telephony/SmsProvider.java
index f50f804..da8ad50 100644
--- a/src/com/android/providers/telephony/SmsProvider.java
+++ b/src/com/android/providers/telephony/SmsProvider.java
@@ -656,7 +656,7 @@
                 count = db.delete(TABLE_SMS, where, whereArgs);
                 if (count != 0) {
                     // Don't update threads unless something changed.
-                    MmsSmsDatabaseHelper.updateAllThreads(db, where, whereArgs);
+                    MmsSmsDatabaseHelper.updateThreads(db, where, whereArgs);
                 }
                 break;
 
diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index 4081bb7..2ece324 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -47,6 +47,7 @@
 import android.util.Xml;
 
 import com.android.internal.util.XmlUtils;
+import com.android.internal.annotations.VisibleForTesting;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -152,6 +153,86 @@
         CARRIERS_UNIQUE_FIELDS.add(PROFILE_ID);
     }
 
+    @VisibleForTesting
+    public static final String CREATE_CARRIERS_TABLE_STRING = "CREATE TABLE " + CARRIERS_TABLE +
+            "(_id INTEGER PRIMARY KEY," +
+            NAME + " TEXT DEFAULT ''," +
+            NUMERIC + " TEXT DEFAULT ''," +
+            MCC + " TEXT DEFAULT ''," +
+            MNC + " TEXT DEFAULT ''," +
+            APN + " TEXT DEFAULT ''," +
+            USER + " TEXT DEFAULT ''," +
+            SERVER + " TEXT DEFAULT ''," +
+            PASSWORD + " TEXT DEFAULT ''," +
+            PROXY + " TEXT DEFAULT ''," +
+            PORT + " TEXT DEFAULT ''," +
+            MMSPROXY + " TEXT DEFAULT ''," +
+            MMSPORT + " TEXT DEFAULT ''," +
+            MMSC + " TEXT DEFAULT ''," +
+            AUTH_TYPE + " INTEGER DEFAULT -1," +
+            TYPE + " TEXT DEFAULT ''," +
+            CURRENT + " INTEGER," +
+            PROTOCOL + " TEXT DEFAULT 'IP'," +
+            ROAMING_PROTOCOL + " TEXT DEFAULT 'IP'," +
+            CARRIER_ENABLED + " BOOLEAN DEFAULT 1," +
+            BEARER + " INTEGER DEFAULT 0," +
+            BEARER_BITMASK + " INTEGER DEFAULT 0," +
+            MVNO_TYPE + " TEXT DEFAULT ''," +
+            MVNO_MATCH_DATA + " TEXT DEFAULT ''," +
+            SUBSCRIPTION_ID + " INTEGER DEFAULT "
+            + SubscriptionManager.INVALID_SUBSCRIPTION_ID + "," +
+            PROFILE_ID + " INTEGER DEFAULT 0," +
+            MODEM_COGNITIVE + " BOOLEAN DEFAULT 0," +
+            MAX_CONNS + " INTEGER DEFAULT 0," +
+            WAIT_TIME + " INTEGER DEFAULT 0," +
+            MAX_CONNS_TIME + " INTEGER DEFAULT 0," +
+            MTU + " INTEGER DEFAULT 0," +
+            EDITED + " INTEGER DEFAULT " + UNEDITED + "," +
+            USER_VISIBLE + " BOOLEAN DEFAULT 1," +
+            // Uniqueness collisions are used to trigger merge code so if a field is listed
+            // here it means we will accept both (user edited + new apn_conf definition)
+            // Columns not included in UNIQUE constraint: name, current, edited,
+            // user, server, password, authtype, type, protocol, roaming_protocol, sub_id,
+            // modem_cognitive, max_conns, wait_time, max_conns_time, mtu, bearer_bitmask,
+            // user_visible
+            "UNIQUE (" + TextUtils.join(", ", CARRIERS_UNIQUE_FIELDS) + "));";
+
+    @VisibleForTesting
+    public static final String CREATE_SIMINFO_TABLE_STRING = "CREATE TABLE " + SIMINFO_TABLE + "("
+            + SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
+                + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+            + SubscriptionManager.ICC_ID + " TEXT NOT NULL,"
+            + SubscriptionManager.SIM_SLOT_INDEX
+                + " INTEGER DEFAULT " + SubscriptionManager.SIM_NOT_INSERTED + ","
+            + SubscriptionManager.DISPLAY_NAME + " TEXT,"
+            + SubscriptionManager.CARRIER_NAME + " TEXT,"
+            + SubscriptionManager.NAME_SOURCE
+                + " INTEGER DEFAULT " + SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE + ","
+            + SubscriptionManager.COLOR
+                + " INTEGER DEFAULT " + SubscriptionManager.COLOR_DEFAULT + ","
+            + SubscriptionManager.NUMBER + " TEXT,"
+            + SubscriptionManager.DISPLAY_NUMBER_FORMAT
+                + " INTEGER NOT NULL DEFAULT " + SubscriptionManager.DISPLAY_NUMBER_DEFAULT + ","
+            + SubscriptionManager.DATA_ROAMING
+                + " INTEGER DEFAULT " + SubscriptionManager.DATA_ROAMING_DEFAULT + ","
+            + SubscriptionManager.MCC + " INTEGER DEFAULT 0,"
+            + SubscriptionManager.MNC + " INTEGER DEFAULT 0,"
+            + SubscriptionManager.SIM_PROVISIONING_STATUS
+                + " INTEGER DEFAULT " + SubscriptionManager.SIM_PROVISIONED + ","
+            + SubscriptionManager.CB_EXTREME_THREAT_ALERT + " INTEGER DEFAULT 1,"
+            + SubscriptionManager.CB_SEVERE_THREAT_ALERT + " INTEGER DEFAULT 1,"
+            + SubscriptionManager.CB_AMBER_ALERT + " INTEGER DEFAULT 1,"
+            + SubscriptionManager.CB_EMERGENCY_ALERT + " INTEGER DEFAULT 1,"
+            + SubscriptionManager.CB_ALERT_SOUND_DURATION + " INTEGER DEFAULT 4,"
+            + SubscriptionManager.CB_ALERT_REMINDER_INTERVAL + " INTEGER DEFAULT 0,"
+            + SubscriptionManager.CB_ALERT_VIBRATE + " INTEGER DEFAULT 1,"
+            + SubscriptionManager.CB_ALERT_SPEECH + " INTEGER DEFAULT 1,"
+            + SubscriptionManager.CB_ETWS_TEST_ALERT + " INTEGER DEFAULT 0,"
+            + SubscriptionManager.CB_CHANNEL_50_ALERT + " INTEGER DEFAULT 1,"
+            + SubscriptionManager.CB_CMAS_TEST_ALERT + " INTEGER DEFAULT 0,"
+            + SubscriptionManager.CB_OPT_OUT_DIALOG + " INTEGER DEFAULT 1"
+            + ");";
+
     static {
         s_urlMatcher.addURI("telephony", "carriers", URL_TELEPHONY);
         s_urlMatcher.addURI("telephony", "carriers/current", URL_CURRENT);
@@ -248,80 +329,14 @@
 
         private void createSimInfoTable(SQLiteDatabase db) {
             if (DBG) log("dbh.createSimInfoTable:+");
-            db.execSQL("CREATE TABLE " + SIMINFO_TABLE + "("
-                    + SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
-                    + SubscriptionManager.ICC_ID + " TEXT NOT NULL,"
-                    + SubscriptionManager.SIM_SLOT_INDEX + " INTEGER DEFAULT " + SubscriptionManager.SIM_NOT_INSERTED + ","
-                    + SubscriptionManager.DISPLAY_NAME + " TEXT,"
-                    + SubscriptionManager.CARRIER_NAME + " TEXT,"
-                    + SubscriptionManager.NAME_SOURCE + " INTEGER DEFAULT " + SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE + ","
-                    + SubscriptionManager.COLOR + " INTEGER DEFAULT " + SubscriptionManager.COLOR_DEFAULT + ","
-                    + SubscriptionManager.NUMBER + " TEXT,"
-                    + SubscriptionManager.DISPLAY_NUMBER_FORMAT + " INTEGER NOT NULL DEFAULT " + SubscriptionManager.DISPLAY_NUMBER_DEFAULT + ","
-                    + SubscriptionManager.DATA_ROAMING + " INTEGER DEFAULT " + SubscriptionManager.DATA_ROAMING_DEFAULT + ","
-                    + SubscriptionManager.MCC + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.MNC + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.CB_EXTREME_THREAT_ALERT + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.CB_SEVERE_THREAT_ALERT + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.CB_AMBER_ALERT + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.CB_EMERGENCY_ALERT + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.CB_ALERT_SOUND_DURATION + " INTEGER DEFAULT 4,"
-                    + SubscriptionManager.CB_ALERT_REMINDER_INTERVAL + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.CB_ALERT_VIBRATE + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.CB_ALERT_SPEECH + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.CB_ETWS_TEST_ALERT + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.CB_CHANNEL_50_ALERT + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.CB_CMAS_TEST_ALERT + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.CB_OPT_OUT_DIALOG + " INTEGER DEFAULT 1"
-                    + ");");
+            db.execSQL(CREATE_SIMINFO_TABLE_STRING);
             if (DBG) log("dbh.createSimInfoTable:-");
         }
 
         private void createCarriersTable(SQLiteDatabase db, String tableName) {
             // Set up the database schema
             if (DBG) log("dbh.createCarriersTable: " + tableName);
-            db.execSQL("CREATE TABLE " + tableName +
-                    "(_id INTEGER PRIMARY KEY," +
-                    NAME + " TEXT DEFAULT ''," +
-                    NUMERIC + " TEXT DEFAULT ''," +
-                    MCC + " TEXT DEFAULT ''," +
-                    MNC + " TEXT DEFAULT ''," +
-                    APN + " TEXT DEFAULT ''," +
-                    USER + " TEXT DEFAULT ''," +
-                    SERVER + " TEXT DEFAULT ''," +
-                    PASSWORD + " TEXT DEFAULT ''," +
-                    PROXY + " TEXT DEFAULT ''," +
-                    PORT + " TEXT DEFAULT ''," +
-                    MMSPROXY + " TEXT DEFAULT ''," +
-                    MMSPORT + " TEXT DEFAULT ''," +
-                    MMSC + " TEXT DEFAULT ''," +
-                    AUTH_TYPE + " INTEGER DEFAULT -1," +
-                    TYPE + " TEXT DEFAULT ''," +
-                    CURRENT + " INTEGER," +
-                    PROTOCOL + " TEXT DEFAULT 'IP'," +
-                    ROAMING_PROTOCOL + " TEXT DEFAULT 'IP'," +
-                    CARRIER_ENABLED + " BOOLEAN DEFAULT 1," +
-                    BEARER + " INTEGER DEFAULT 0," +
-                    BEARER_BITMASK + " INTEGER DEFAULT 0," +
-                    MVNO_TYPE + " TEXT DEFAULT ''," +
-                    MVNO_MATCH_DATA + " TEXT DEFAULT ''," +
-                    SUBSCRIPTION_ID + " INTEGER DEFAULT "
-                    + SubscriptionManager.INVALID_SUBSCRIPTION_ID + "," +
-                    PROFILE_ID + " INTEGER DEFAULT 0," +
-                    MODEM_COGNITIVE + " BOOLEAN DEFAULT 0," +
-                    MAX_CONNS + " INTEGER DEFAULT 0," +
-                    WAIT_TIME + " INTEGER DEFAULT 0," +
-                    MAX_CONNS_TIME + " INTEGER DEFAULT 0," +
-                    MTU + " INTEGER DEFAULT 0," +
-                    EDITED + " INTEGER DEFAULT " + UNEDITED + "," +
-                    USER_VISIBLE + " BOOLEAN DEFAULT 1," +
-                    // Uniqueness collisions are used to trigger merge code so if a field is listed
-                    // here it means we will accept both (user edited + new apn_conf definition)
-                    // Columns not included in UNIQUE constraint: name, current, edited,
-                    // user, server, password, authtype, type, protocol, roaming_protocol, sub_id,
-                    // modem_cognitive, max_conns, wait_time, max_conns_time, mtu, bearer_bitmask,
-                    // user_visible
-                    "UNIQUE (" + TextUtils.join(", ", CARRIERS_UNIQUE_FIELDS) + "));");
+            db.execSQL(CREATE_CARRIERS_TABLE_STRING);
             if (DBG) log("dbh.createCarriersTable:-");
         }
 
@@ -1459,13 +1474,31 @@
         }
     }
 
+    /**
+     * These methods can be overridden in a subclass for testing TelephonyProvider using an
+     * in-memory database.
+     */
+    SQLiteDatabase getReadableDatabase() {
+        return mOpenHelper.getReadableDatabase();
+    }
+    SQLiteDatabase getWritableDatabase() {
+        return mOpenHelper.getWritableDatabase();
+    }
+    void initDatabaseWithDatabaseHelper(SQLiteDatabase db) {
+        mOpenHelper.initDatabase(db);
+    }
+    boolean needApnDbUpdate() {
+        return mOpenHelper.apnDbUpdateNeeded();
+    }
+
+
     @Override
     public boolean onCreate() {
         mOpenHelper = new DatabaseHelper(getContext());
 
         // Call getReadableDatabase() to make sure onUpgrade is called
         if (VDBG) log("onCreate: calling getReadableDatabase to trigger onUpgrade");
-        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        SQLiteDatabase db = getReadableDatabase();
 
         // Update APN db on build update
         String newBuildId = SystemProperties.get("ro.build.id", null);
@@ -1557,7 +1590,7 @@
 
     private void setPreferredApn(Long id, int subId) {
         log("setPreferredApn: _id " + id + " subId " + subId);
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        SQLiteDatabase db = getWritableDatabase();
         // query all unique fields from id
         String[] proj = CARRIERS_UNIQUE_FIELDS.toArray(new String[CARRIERS_UNIQUE_FIELDS.size()]);
         Cursor c = db.query(CARRIERS_TABLE, proj, "_id=" + id, null, null, null, null);
@@ -1585,7 +1618,7 @@
 
     private long getPreferredApnIdFromApn(int subId) {
         log("getPreferredApnIdFromApn: for subId " + subId);
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        SQLiteDatabase db = getWritableDatabase();
         String where = TextUtils.join("=? and ", CARRIERS_UNIQUE_FIELDS) + "=?";
         String[] whereArgs = new String[CARRIERS_UNIQUE_FIELDS.size()];
         SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_FULL_APN,
@@ -1739,7 +1772,7 @@
             }
         }
 
-        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        SQLiteDatabase db = getReadableDatabase();
         Cursor ret = null;
         try {
             // Exclude entries marked deleted
@@ -1794,7 +1827,7 @@
 
         checkPermission();
 
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        SQLiteDatabase db = getWritableDatabase();
         int match = s_urlMatcher.match(url);
         boolean notify = false;
         switch (match)
@@ -1944,7 +1977,7 @@
 
         checkPermission();
 
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        SQLiteDatabase db = getWritableDatabase();
         int match = s_urlMatcher.match(url);
         switch (match)
         {
@@ -2079,7 +2112,7 @@
 
         checkPermission();
 
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        SQLiteDatabase db = getWritableDatabase();
         int match = s_urlMatcher.match(url);
         switch (match)
         {
@@ -2234,7 +2267,7 @@
     private DatabaseHelper mOpenHelper;
 
     private void restoreDefaultAPN(int subId) {
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        SQLiteDatabase db = getWritableDatabase();
 
         try {
             db.delete(CARRIERS_TABLE, null, null);
@@ -2242,16 +2275,16 @@
             loge("got exception when deleting to restore: " + e);
         }
         setPreferredApnId((long) INVALID_APN_ID, subId);
-        mOpenHelper.initDatabase(db);
+        initDatabaseWithDatabaseHelper(db);
     }
 
     private synchronized void updateApnDb() {
-        if (!mOpenHelper.apnDbUpdateNeeded()) {
+        if (!needApnDbUpdate()) {
             log("Skipping apn db update since apn-conf has not changed.");
             return;
         }
 
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        SQLiteDatabase db = getWritableDatabase();
 
         // Delete preferred APN for all subIds
         deletePreferredApnId();
@@ -2264,7 +2297,7 @@
             loge("got exception when deleting to update: " + e);
         }
 
-        mOpenHelper.initDatabase(db);
+        initDatabaseWithDatabaseHelper(db);
 
         // Notify listereners of DB change since DB has been updated
         getContext().getContentResolver().notifyChange(
diff --git a/tests/Android.mk b/tests/Android.mk
index fc12378..9af28ab 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -3,7 +3,7 @@
 
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target legacy-android-test
 
 LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
 
diff --git a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
new file mode 100644
index 0000000..dd505d9
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2016 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.annotation.TargetApi;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+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.Cursor;
+import android.database.DatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.net.Uri;
+import android.os.Build;
+import android.os.FileUtils;
+import android.provider.Telephony.Carriers;
+import android.telephony.SubscriptionManager;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.providers.telephony.TelephonyProvider;
+
+import junit.framework.TestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+
+/**
+ * Tests for testing CRUD operations of TelephonyProvider.
+ * Uses a MockContentResolver to get permission WRITE_APN_SETTINGS in order to test insert/delete
+ * Uses TelephonyProviderTestable 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/TelephonyProviderTest.java \
+ *                 --test-method testInsertCarriers
+ */
+public class TelephonyProviderTest extends TestCase {
+    private static final String TAG = "TelephonyProviderTest";
+
+    private MockContextWithProvider mContext;
+    private MockContentResolver mContentResolver;
+    private TelephonyProviderTestable mTelephonyProviderTestable;
+
+
+    /**
+     * This is used to give the TelephonyProviderTest a mocked context which takes a
+     * TelephonyProvider 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(TelephonyProvider telephonyProvider) {
+            mResolver = new MockContentResolver();
+
+            // Add authority="telephony" to given telephonyProvider
+            ProviderInfo providerInfo = new ProviderInfo();
+            providerInfo.authority = "telephony";
+
+            // Add context to given telephonyProvider
+            telephonyProvider.attachInfoForTesting(this, providerInfo);
+            Log.d(TAG, "MockContextWithProvider: telephonyProvider.getContext(): "
+                    + telephonyProvider.getContext());
+
+            // Add given telephonyProvider to mResolver with authority="telephony" so that
+            // mResolver can send queries to mTelephonyProvider
+            mResolver.addProvider("telephony", telephonyProvider);
+            Log.d(TAG, "MockContextWithProvider: Add telephonyProvider to mResolver");
+        }
+
+        @Override
+        public Object getSystemService(String name) {
+            Log.d(TAG, "getSystemService: returning null");
+            return null;
+        }
+
+        @Override
+        public Resources getResources() {
+            Log.d(TAG, "getResources: returning null");
+            return null;
+        }
+
+        @Override
+        public MockContentResolver getContentResolver() {
+            return mResolver;
+        }
+
+        // Gives permission to write to the APN table within the MockContext
+        @Override
+        public int checkCallingOrSelfPermission(String permission) {
+            if (TextUtils.equals(permission, "android.permission.WRITE_APN_SETTINGS")) {
+                Log.d(TAG, "checkCallingOrSelfPermission: permission=" + permission
+                        + ", returning PackageManager.PERMISSION_GRANTED");
+                return PackageManager.PERMISSION_GRANTED;
+            } else {
+                Log.d(TAG, "checkCallingOrSelfPermission: permission=" + permission
+                        + ", returning PackageManager.PERMISSION_DENIED");
+                return PackageManager.PERMISSION_DENIED;
+            }
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mTelephonyProviderTestable = new TelephonyProviderTestable();
+        mContext = new MockContextWithProvider(mTelephonyProviderTestable);
+        mContentResolver = (MockContentResolver) mContext.getContentResolver();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mTelephonyProviderTestable.closeDatabase();
+    }
+
+    /**
+     * Test inserting, querying, and deleting values in carriers table.
+     * Verify that the inserted values match the result of the query and are deleted.
+     */
+    @Test
+    @SmallTest
+    public void testInsertCarriers() {
+        // insert test contentValues
+        ContentValues contentValues = new ContentValues();
+        final String insertApn = "exampleApnName";
+        final String insertName = "exampleName";
+        final Integer insertCurrent = 1;
+        final String insertNumeric = "123456";
+        contentValues.put(Carriers.APN, insertApn);
+        contentValues.put(Carriers.NAME, insertName);
+        contentValues.put(Carriers.CURRENT, insertCurrent);
+        contentValues.put(Carriers.NUMERIC, insertNumeric);
+
+        Log.d(TAG, "testInsertCarriers Inserting contentValues: " + contentValues);
+        mContentResolver.insert(Carriers.CONTENT_URI, contentValues);
+
+        // get values in table
+        final String[] testProjection =
+        {
+            Carriers.APN,
+            Carriers.NAME,
+            Carriers.CURRENT,
+        };
+        final String selection = Carriers.NUMERIC + "=?";
+        String[] selectionArgs = { insertNumeric };
+        Log.d(TAG, "testInsertCarriers query projection: " + testProjection
+                + "\ntestInsertCarriers selection: " + selection
+                + "\ntestInsertCarriers selectionArgs: " + selectionArgs);
+        Cursor cursor = mContentResolver.query(Carriers.CONTENT_URI,
+                testProjection, selection, selectionArgs, null);
+
+        // verify that inserted values match results of query
+        assertNotNull(cursor);
+        assertEquals(1, cursor.getCount());
+        cursor.moveToFirst();
+        final String resultApn = cursor.getString(0);
+        final String resultName = cursor.getString(1);
+        final Integer resultCurrent = cursor.getInt(2);
+        assertEquals(insertApn, resultApn);
+        assertEquals(insertName, resultName);
+        assertEquals(insertCurrent, resultCurrent);
+
+        // delete test content
+        final String selectionToDelete = Carriers.NUMERIC + "=?";
+        String[] selectionArgsToDelete = { insertNumeric };
+        Log.d(TAG, "testInsertCarriers deleting selection: " + selectionToDelete
+                + "testInsertCarriers selectionArgs: " + selectionArgs);
+        int numRowsDeleted = mContentResolver.delete(Carriers.CONTENT_URI,
+                selectionToDelete, selectionArgsToDelete);
+        assertEquals(1, numRowsDeleted);
+
+        // verify that deleted values are gone
+        cursor = mContentResolver.query(Carriers.CONTENT_URI,
+                testProjection, selection, selectionArgs, null);
+        assertEquals(0, cursor.getCount());
+    }
+
+    /**
+     * Test inserting, querying, and deleting values in carriers table.
+     * Verify that the inserted values match the result of the query and are deleted.
+     */
+    @Test
+    @SmallTest
+    public void testSimTable() {
+        // insert test contentValues
+        ContentValues contentValues = new ContentValues();
+        final int insertSubId = 11;
+        final String insertDisplayName = "exampleDisplayName";
+        final String insertCarrierName = "exampleCarrierName";
+        final String insertIccId = "exampleIccId";
+        contentValues.put(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID, insertSubId);
+        contentValues.put(SubscriptionManager.DISPLAY_NAME, insertDisplayName);
+        contentValues.put(SubscriptionManager.CARRIER_NAME, insertCarrierName);
+        contentValues.put(SubscriptionManager.ICC_ID, insertIccId);
+
+        Log.d(TAG, "testSimTable Inserting contentValues: " + contentValues);
+        mContentResolver.insert(SubscriptionManager.CONTENT_URI, contentValues);
+
+        // get values in table
+        final String[] testProjection =
+        {
+            SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID,
+            SubscriptionManager.CARRIER_NAME,
+        };
+        final String selection = SubscriptionManager.DISPLAY_NAME + "=?";
+        String[] selectionArgs = { insertDisplayName };
+        Log.d(TAG,"\ntestSimTable selection: " + selection
+                + "\ntestSimTable selectionArgs: " + selectionArgs.toString());
+        Cursor cursor = mContentResolver.query(SubscriptionManager.CONTENT_URI,
+                testProjection, selection, selectionArgs, null);
+
+        // verify that inserted values match results of query
+        assertNotNull(cursor);
+        assertEquals(1, cursor.getCount());
+        cursor.moveToFirst();
+        final int resultSubId = cursor.getInt(0);
+        final String resultCarrierName = cursor.getString(1);
+        assertEquals(insertSubId, resultSubId);
+        assertEquals(insertCarrierName, resultCarrierName);
+
+        // delete test content
+        final String selectionToDelete = SubscriptionManager.DISPLAY_NAME + "=?";
+        String[] selectionArgsToDelete = { insertDisplayName };
+        Log.d(TAG, "testSimTable deleting selection: " + selectionToDelete
+                + "testSimTable selectionArgs: " + selectionArgs);
+        int numRowsDeleted = mContentResolver.delete(SubscriptionManager.CONTENT_URI,
+                selectionToDelete, selectionArgsToDelete);
+        assertEquals(1, numRowsDeleted);
+
+        // verify that deleted values are gone
+        cursor = mContentResolver.query(SubscriptionManager.CONTENT_URI,
+                testProjection, selection, selectionArgs, null);
+        assertEquals(0, cursor.getCount());
+    }
+}
diff --git a/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java b/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java
new file mode 100644
index 0000000..b736545
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.Telephony;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+import java.util.ArrayList;
+import com.android.providers.telephony.TelephonyProvider;
+import static android.provider.Telephony.Carriers.*;
+
+/**
+ * A subclass of TelephonyProvider used for testing on an in-memory database
+ */
+public class TelephonyProviderTestable extends TelephonyProvider {
+    private static final String TAG = "TelephonyProviderTestable";
+
+    private InMemoryTelephonyProviderDbHelper mDbHelper;
+
+    @Override
+    public boolean onCreate() {
+        Log.d(TAG, "onCreate called: mDbHelper = new InMemoryTelephonyProviderDbHelper()");
+        mDbHelper = new InMemoryTelephonyProviderDbHelper();
+        return true;
+    }
+
+    // close mDbHelper database object
+    protected void closeDatabase() {
+        mDbHelper.close();
+    }
+
+    @Override
+    SQLiteDatabase getReadableDatabase() {
+        Log.d(TAG, "getReadableDatabase called");
+        return mDbHelper.getReadableDatabase();
+    }
+
+    @Override
+    SQLiteDatabase getWritableDatabase() {
+        Log.d(TAG, "getWritableDatabase called");
+        return mDbHelper.getWritableDatabase();
+    }
+
+    @Override
+    void initDatabaseWithDatabaseHelper(SQLiteDatabase db) {
+        Log.d(TAG, "initDatabaseWithDatabaseHelper called; doing nothing");
+    }
+
+    @Override
+    boolean needApnDbUpdate() {
+        Log.d(TAG, "needApnDbUpdate called; returning false");
+        return false;
+    }
+
+    /**
+     * An in memory DB for TelephonyProviderTestable to use
+     */
+    public static class InMemoryTelephonyProviderDbHelper extends SQLiteOpenHelper {
+
+
+        public InMemoryTelephonyProviderDbHelper() {
+            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, "InMemoryTelephonyProviderDbHelper creating in-memory database");
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            // Set up the carriers table
+            Log.d(TAG, "InMemoryTelephonyProviderDbHelper onCreate creating the carriers table");
+            db.execSQL(CREATE_CARRIERS_TABLE_STRING);
+
+            // set up the siminfo table
+            Log.d(TAG, "InMemoryTelephonyProviderDbHelper onCreate creating the siminfo table");
+            db.execSQL(CREATE_SIMINFO_TABLE_STRING);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            Log.d(TAG, "InMemoryTelephonyProviderDbHelper onUpgrade doing nothing");
+            return;
+        }
+    }
+}