Merge "Telephony: simplify apns-conf.xml loading logic"
diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index 5df2843..feccfdc 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -99,6 +99,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
@@ -108,7 +109,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IApnSourceService;
 import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.dataconnection.ApnSetting;
+import com.android.internal.telephony.dataconnection.ApnSettingUtils;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.util.XmlUtils;
@@ -125,9 +126,11 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.zip.CRC32;
+import java.util.Set;
 
 public class TelephonyProvider extends ContentProvider
 {
@@ -223,6 +226,7 @@
 
     private static final int INVALID_APN_ID = -1;
     private static final List<String> CARRIERS_UNIQUE_FIELDS = new ArrayList<String>();
+    private static final Set<String> CARRIERS_BOOLEAN_FIELDS = new HashSet<String>();
     private static final Map<String, String> CARRIERS_UNIQUE_FIELDS_DEFAULTS = new HashMap();
 
     @VisibleForTesting
@@ -260,6 +264,14 @@
         CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(APN_SET_ID, String.valueOf(NO_SET_SET));
 
         CARRIERS_UNIQUE_FIELDS.addAll(CARRIERS_UNIQUE_FIELDS_DEFAULTS.keySet());
+
+        // SQLite databases store bools as ints but the ContentValues objects passed in through
+        // queries use bools. As a result there is some special handling of boolean fields within
+        // the TelephonyProvider.
+        CARRIERS_BOOLEAN_FIELDS.add(CARRIER_ENABLED);
+        CARRIERS_BOOLEAN_FIELDS.add(MODEM_COGNITIVE);
+        CARRIERS_BOOLEAN_FIELDS.add(USER_VISIBLE);
+        CARRIERS_BOOLEAN_FIELDS.add(USER_EDITABLE);
     }
 
     @VisibleForTesting
@@ -284,7 +296,7 @@
                 CURRENT + " INTEGER," +
                 PROTOCOL + " TEXT DEFAULT " + DEFAULT_PROTOCOL + "," +
                 ROAMING_PROTOCOL + " TEXT DEFAULT " + DEFAULT_ROAMING_PROTOCOL + "," +
-                CARRIER_ENABLED + " BOOLEAN DEFAULT 1," +
+                CARRIER_ENABLED + " BOOLEAN DEFAULT 1," + // SQLite databases store bools as ints
                 BEARER + " INTEGER DEFAULT 0," +
                 BEARER_BITMASK + " INTEGER DEFAULT 0," +
                 NETWORK_TYPE_BITMASK + " INTEGER DEFAULT 0," +
@@ -422,7 +434,8 @@
         mInjector = injector;
     }
 
-    private static class DatabaseHelper extends SQLiteOpenHelper {
+    @VisibleForTesting
+    public static class DatabaseHelper extends SQLiteOpenHelper {
         // Context to access resources with
         private Context mContext;
 
@@ -438,10 +451,15 @@
             setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
         }
 
-        private static int getVersion(Context context) {
+        @VisibleForTesting
+        public static int getVersion(Context context) {
             if (VDBG) log("getVersion:+");
             // Get the database version, combining a static schema version and the XML version
             Resources r = context.getResources();
+            if (r == null) {
+                loge("resources=null, return version=" + Integer.toHexString(DATABASE_VERSION));
+                return DATABASE_VERSION;
+            }
             XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
             try {
                 XmlUtils.beginDocument(parser, "apns");
@@ -596,16 +614,20 @@
             if (VDBG) log("dbh.initDatabase:+ db=" + db);
             // Read internal APNS data
             Resources r = mContext.getResources();
-            XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
             int publicversion = -1;
-            try {
-                XmlUtils.beginDocument(parser, "apns");
-                publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
-                loadApns(db, parser);
-            } catch (Exception e) {
-                loge("Got exception while loading APN database." + e);
-            } finally {
-                parser.close();
+            if (r != null) {
+                XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
+                try {
+                    XmlUtils.beginDocument(parser, "apns");
+                    publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
+                    loadApns(db, parser);
+                } catch (Exception e) {
+                    loge("Got exception while loading APN database." + e);
+                } finally {
+                    parser.close();
+                }
+            } else {
+                loge("initDatabase: resources=null");
             }
 
             // Read external APNS data (partner-provided)
@@ -1052,6 +1074,8 @@
             if (DBG) {
                 log("dbh.onUpgrade:- db=" + db + " oldV=" + oldVersion + " newV=" + newVersion);
             }
+            // when adding fields to onUpgrade, also add a unit test to TelephonyDatabaseHelperTest
+            // and update the DATABASE_VERSION field
         }
 
         private void recreateSimInfoDB(Cursor c, SQLiteDatabase db, String[] proj) {
@@ -1325,15 +1349,13 @@
             whereArgs[i++] = values.containsKey(ROAMING_PROTOCOL) ?
                     values.getAsString(ROAMING_PROTOCOL) : DEFAULT_ROAMING_PROTOCOL;
 
-            if (values.containsKey(CARRIER_ENABLED) &&
-                    (values.getAsString(CARRIER_ENABLED).
-                            equalsIgnoreCase("false") ||
-                            values.getAsString(CARRIER_ENABLED).equals("0"))) {
-                whereArgs[i++] = "false";
-                whereArgs[i++] = "0";
+            if (values.containsKey(CARRIER_ENABLED)) {
+                whereArgs[i++] = convertStringToBoolString(values.getAsString(CARRIER_ENABLED));
+                whereArgs[i++] = convertStringToIntString(values.getAsString(CARRIER_ENABLED));
             } else {
-                whereArgs[i++] = "true";
-                whereArgs[i++] = "1";
+                String defaultIntString = CARRIERS_UNIQUE_FIELDS_DEFAULTS.get(CARRIER_ENABLED);
+                whereArgs[i++] = convertStringToBoolString(defaultIntString);
+                whereArgs[i++] = defaultIntString;
             }
 
             whereArgs[i++] = values.containsKey(BEARER) ?
@@ -1446,82 +1468,87 @@
 
         private void copyPreservedApnsToNewTable(SQLiteDatabase db, Cursor c) {
             // Move entries from CARRIERS_TABLE to CARRIERS_TABLE_TMP
-            if (c != null) {
-                String[] persistApnsForPlmns = mContext.getResources().getStringArray(
-                        R.array.persist_apns_for_plmn);
-                while (c.moveToNext()) {
-                    ContentValues cv = new ContentValues();
-                    String val;
-                    // Using V17 copy function for V15 upgrade. This should be fine since it handles
-                    // columns that may not exist properly (getStringValueFromCursor() and
-                    // getIntValueFromCursor() handle column index -1)
-                    copyApnValuesV17(cv, c);
-                    // Change bearer to a bitmask
-                    String bearerStr = c.getString(c.getColumnIndex(BEARER));
-                    if (!TextUtils.isEmpty(bearerStr)) {
-                        int bearer_bitmask = ServiceState.getBitmaskForTech(
-                                Integer.parseInt(bearerStr));
-                        cv.put(BEARER_BITMASK, bearer_bitmask);
+            if (c != null && mContext.getResources() != null) {
+                try {
+                    String[] persistApnsForPlmns = mContext.getResources().getStringArray(
+                            R.array.persist_apns_for_plmn);
+                    while (c.moveToNext()) {
+                        ContentValues cv = new ContentValues();
+                        String val;
+                        // Using V17 copy function for V15 upgrade. This should be fine since it handles
+                        // columns that may not exist properly (getStringValueFromCursor() and
+                        // getIntValueFromCursor() handle column index -1)
+                        copyApnValuesV17(cv, c);
+                        // Change bearer to a bitmask
+                        String bearerStr = c.getString(c.getColumnIndex(BEARER));
+                        if (!TextUtils.isEmpty(bearerStr)) {
+                            int bearer_bitmask = ServiceState.getBitmaskForTech(
+                                    Integer.parseInt(bearerStr));
+                            cv.put(BEARER_BITMASK, bearer_bitmask);
 
-                        int networkTypeBitmask = ServiceState.getBitmaskForTech(
-                                ServiceState.rilRadioTechnologyToNetworkType(
-                                        Integer.parseInt(bearerStr)));
-                        cv.put(NETWORK_TYPE_BITMASK, networkTypeBitmask);
-                    }
-
-                    int userEditedColumnIdx = c.getColumnIndex("user_edited");
-                    if (userEditedColumnIdx != -1) {
-                        String user_edited = c.getString(userEditedColumnIdx);
-                        if (!TextUtils.isEmpty(user_edited)) {
-                            cv.put(EDITED, new Integer(user_edited));
+                            int networkTypeBitmask = ServiceState.getBitmaskForTech(
+                                    ServiceState.rilRadioTechnologyToNetworkType(
+                                            Integer.parseInt(bearerStr)));
+                            cv.put(NETWORK_TYPE_BITMASK, networkTypeBitmask);
                         }
-                    } else {
-                        cv.put(EDITED, CARRIER_EDITED);
-                    }
 
-                    // New EDITED column. Default value (UNEDITED) will
-                    // be used for all rows except for non-mvno entries for plmns indicated
-                    // by resource: those will be set to CARRIER_EDITED to preserve
-                    // their current values
-                    val = c.getString(c.getColumnIndex(NUMERIC));
-                    for (String s : persistApnsForPlmns) {
-                        if (!TextUtils.isEmpty(val) && val.equals(s) &&
-                                (!cv.containsKey(MVNO_TYPE) ||
-                                        TextUtils.isEmpty(cv.getAsString(MVNO_TYPE)))) {
-                            if (userEditedColumnIdx == -1) {
-                                cv.put(EDITED, CARRIER_EDITED);
-                            } else { // if (oldVersion == 14) -- if db had user_edited column
-                                if (cv.getAsInteger(EDITED) == USER_EDITED) {
-                                    cv.put(EDITED, CARRIER_EDITED);
-                                }
+                        int userEditedColumnIdx = c.getColumnIndex("user_edited");
+                        if (userEditedColumnIdx != -1) {
+                            String user_edited = c.getString(userEditedColumnIdx);
+                            if (!TextUtils.isEmpty(user_edited)) {
+                                cv.put(EDITED, new Integer(user_edited));
                             }
+                        } else {
+                            cv.put(EDITED, CARRIER_EDITED);
+                        }
 
-                            break;
+                        // New EDITED column. Default value (UNEDITED) will
+                        // be used for all rows except for non-mvno entries for plmns indicated
+                        // by resource: those will be set to CARRIER_EDITED to preserve
+                        // their current values
+                        val = c.getString(c.getColumnIndex(NUMERIC));
+                        for (String s : persistApnsForPlmns) {
+                            if (!TextUtils.isEmpty(val) && val.equals(s) &&
+                                    (!cv.containsKey(MVNO_TYPE) ||
+                                            TextUtils.isEmpty(cv.getAsString(MVNO_TYPE)))) {
+                                if (userEditedColumnIdx == -1) {
+                                    cv.put(EDITED, CARRIER_EDITED);
+                                } else { // if (oldVersion == 14) -- if db had user_edited column
+                                    if (cv.getAsInteger(EDITED) == USER_EDITED) {
+                                        cv.put(EDITED, CARRIER_EDITED);
+                                    }
+                                }
+
+                                break;
+                            }
+                        }
+
+                        try {
+                            db.insertWithOnConflict(CARRIERS_TABLE_TMP, null, cv,
+                                    SQLiteDatabase.CONFLICT_ABORT);
+                            if (VDBG) {
+                                log("dbh.copyPreservedApnsToNewTable: db.insert returned >= 0; " +
+                                        "insert successful for cv " + cv);
+                            }
+                        } catch (SQLException e) {
+                            if (VDBG)
+                                log("dbh.copyPreservedApnsToNewTable insertWithOnConflict exception " +
+                                        e + " for cv " + cv);
+                            // Insertion failed which could be due to a conflict. Check if that is
+                            // the case and merge the entries
+                            Cursor oldRow = DatabaseHelper.selectConflictingRow(db,
+                                    CARRIERS_TABLE_TMP, cv);
+                            if (oldRow != null) {
+                                ContentValues mergedValues = new ContentValues();
+                                mergeFieldsAndUpdateDb(db, CARRIERS_TABLE_TMP, oldRow, cv,
+                                        mergedValues, true, mContext);
+                                oldRow.close();
+                            }
                         }
                     }
-
-                    try {
-                        db.insertWithOnConflict(CARRIERS_TABLE_TMP, null, cv,
-                                SQLiteDatabase.CONFLICT_ABORT);
-                        if (VDBG) {
-                            log("dbh.copyPreservedApnsToNewTable: db.insert returned >= 0; " +
-                                    "insert successful for cv " + cv);
-                        }
-                    } catch (SQLException e) {
-                        if (VDBG)
-                            log("dbh.copyPreservedApnsToNewTable insertWithOnConflict exception " +
-                                    e + " for cv " + cv);
-                        // Insertion failed which could be due to a conflict. Check if that is
-                        // the case and merge the entries
-                        Cursor oldRow = DatabaseHelper.selectConflictingRow(db,
-                                CARRIERS_TABLE_TMP, cv);
-                        if (oldRow != null) {
-                            ContentValues mergedValues = new ContentValues();
-                            mergeFieldsAndUpdateDb(db, CARRIERS_TABLE_TMP, oldRow, cv,
-                                    mergedValues, true, mContext);
-                            oldRow.close();
-                        }
-                    }
+                } catch (Resources.NotFoundException e) {
+                    loge("array.persist_apns_for_plmn is not found");
+                    return;
                 }
             }
         }
@@ -1909,13 +1936,17 @@
             boolean match = false;
 
             // Check if APN falls under persist_apns_for_plmn
-            String[] persistApnsForPlmns = context.getResources().getStringArray(
-                    R.array.persist_apns_for_plmn);
-            for (String s : persistApnsForPlmns) {
-                if (s.equalsIgnoreCase(newRow.getAsString(NUMERIC))) {
-                    match = true;
-                    break;
+            if (context.getResources() != null) {
+                String[] persistApnsForPlmns = context.getResources().getStringArray(
+                        R.array.persist_apns_for_plmn);
+                for (String s : persistApnsForPlmns) {
+                    if (s.equalsIgnoreCase(newRow.getAsString(NUMERIC))) {
+                        match = true;
+                        break;
+                    }
                 }
+            } else {
+                loge("separateRowsNeeded: resources=null");
             }
 
             if (!match) return false;
@@ -2010,15 +2041,16 @@
             int i = 0;
             String[] selectionArgs = new String[CARRIERS_UNIQUE_FIELDS.size()];
             for (String field : CARRIERS_UNIQUE_FIELDS) {
-                if (CARRIER_ENABLED.equals(field)) {
-                    // for CARRIER_ENABLED we overwrite the value "false" with "0"
-                    selectionArgs[i++] = row.containsKey(CARRIER_ENABLED) &&
-                            (row.getAsString(CARRIER_ENABLED).equals("0") ||
-                                    row.getAsString(CARRIER_ENABLED).equals("false")) ?
-                            "0" : CARRIERS_UNIQUE_FIELDS_DEFAULTS.get(CARRIER_ENABLED);
+                if (!row.containsKey(field)) {
+                    selectionArgs[i++] = CARRIERS_UNIQUE_FIELDS_DEFAULTS.get(field);
                 } else {
-                    selectionArgs[i++] = row.containsKey(field) ?
-                            row.getAsString(field) : CARRIERS_UNIQUE_FIELDS_DEFAULTS.get(field);
+                    if (CARRIERS_BOOLEAN_FIELDS.contains(field)) {
+                        // for boolean fields we overwrite the strings "true" and "false" with "1"
+                        // and "0"
+                        selectionArgs[i++] = convertStringToIntString(row.getAsString(field));
+                    } else {
+                        selectionArgs[i++] = row.getAsString(field);
+                    }
                 }
             }
 
@@ -2048,6 +2080,24 @@
     }
 
     /**
+     * Convert "true" and "false" to "1" and "0".
+     * If the passed in string is already "1" or "0" returns the passed in string.
+     */
+    private static String convertStringToIntString(String boolString) {
+        if ("0".equals(boolString) || "false".equalsIgnoreCase(boolString)) return "0";
+        return "1";
+    }
+
+    /**
+     * Convert "1" and "0" to "true" and "false".
+     * If the passed in string is already "true" or "false" returns the passed in string.
+     */
+    private static String convertStringToBoolString(String intString) {
+        if ("0".equals(intString) || "false".equalsIgnoreCase(intString)) return "false";
+        return "true";
+    }
+
+    /**
      * These methods can be overridden in a subclass for testing TelephonyProvider using an
      * in-memory database.
      */
@@ -3249,7 +3299,8 @@
                 String mvnoType = cursor.getString(0 /* MVNO_TYPE index */);
                 String mvnoMatchData = cursor.getString(1 /* MVNO_MATCH_DATA index */);
                 if (!TextUtils.isEmpty(mvnoType) && !TextUtils.isEmpty(mvnoMatchData)
-                        && ApnSetting.mvnoMatches(iccRecords, mvnoType, mvnoMatchData)) {
+                        && ApnSettingUtils.mvnoMatches(iccRecords,
+                        ApnSetting.getMvnoTypeIntFromString(mvnoType), mvnoMatchData)) {
                     where = NUMERIC + "='" + simOperator + "'"
                             + " AND " + MVNO_TYPE + "='" + mvnoType + "'"
                             + " AND " + MVNO_MATCH_DATA + "='" + mvnoMatchData + "'"
diff --git a/tests/src/com/android/providers/telephony/TelephonyDatabaseHelperTest.java b/tests/src/com/android/providers/telephony/TelephonyDatabaseHelperTest.java
new file mode 100644
index 0000000..d6b989a
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/TelephonyDatabaseHelperTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.Carriers;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteDatabase;
+import android.support.test.InstrumentationRegistry;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class TelephonyDatabaseHelperTest {
+
+    private final static String TAG = TelephonyDatabaseHelperTest.class.getSimpleName();
+
+    private Context mContext;
+    private TelephonyProvider.DatabaseHelper mHelper; // the actual class being tested
+    private SQLiteOpenHelper mInMemoryDbHelper; // used to give us an in-memory db
+
+    @Before
+    public void setUp() {
+        Log.d(TAG, "setUp() +");
+        mContext = InstrumentationRegistry.getContext();
+        mHelper = new TelephonyProvider.DatabaseHelper(mContext);
+        mInMemoryDbHelper = new InMemoryTelephonyProviderV5DbHelper();
+        Log.d(TAG, "setUp() -");
+    }
+
+    @Test
+    public void databaseHelperOnUpgrade_hasApnSetIdField() {
+        Log.d(TAG, "databaseHelperOnUpgrade_hasApnSetIdField");
+        // (5 << 16 | 6) is the first upgrade trigger in onUpgrade
+        SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
+        mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
+
+        // the upgraded db must have the APN_SET_ID field
+        Cursor cursor = db.query("carriers", null, null, null, null, null, null);
+        String[] upgradedColumns = cursor.getColumnNames();
+        Log.d(TAG, "carriers columns: " + Arrays.toString(upgradedColumns));
+
+        assertTrue(Arrays.asList(upgradedColumns).contains(Carriers.APN_SET_ID));
+    }
+
+    @Test
+    public void databaseHelperOnUpgrade_columnsMatchNewlyCreatedDb() {
+        Log.d(TAG, "databaseHelperOnUpgrade_columnsMatchNewlyCreatedDb");
+        // (5 << 16 | 6) is the first upgrade trigger in onUpgrade
+        SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
+        mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
+
+        // compare upgraded carriers table to a carriers table created from scratch
+        db.execSQL(TelephonyProvider.getStringForCarrierTableCreation("carriers_full"));
+
+        Cursor cursor = db.query("carriers", null, null, null, null, null, null);
+        String[] upgradedColumns = cursor.getColumnNames();
+        Log.d(TAG, "carriers columns: " + Arrays.toString(upgradedColumns));
+
+        cursor = db.query("carriers_full", null, null, null, null, null, null);
+        String[] fullColumns = cursor.getColumnNames();
+        Log.d(TAG, "carriers_full colunmns: " + Arrays.toString(fullColumns));
+
+        assertArrayEquals("Carriers table from onUpgrade doesn't match full table",
+                fullColumns, upgradedColumns);
+
+        // compare upgraded siminfo table to siminfo table created from scratch
+        db.execSQL(TelephonyProvider.getStringForSimInfoTableCreation("siminfo_full"));
+
+        cursor = db.query("siminfo", null, null, null, null, null, null);
+        upgradedColumns = cursor.getColumnNames();
+        Log.d(TAG, "siminfo columns: " + Arrays.toString(upgradedColumns));
+
+        cursor = db.query("siminfo_full", null, null, null, null, null, null);
+        fullColumns = cursor.getColumnNames();
+        Log.d(TAG, "siminfo_full colunmns: " + Arrays.toString(fullColumns));
+
+        assertArrayEquals("Siminfo table from onUpgrade doesn't match full table",
+                fullColumns, upgradedColumns);
+    }
+
+    /**
+     * Helper for an in memory DB used to test the TelephonyProvider#DatabaseHelper.
+     *
+     * We pass this in-memory db to DatabaseHelper#onUpgrade so we can use the actual function
+     * without using the actual telephony db.
+     */
+    private static class InMemoryTelephonyProviderV5DbHelper extends SQLiteOpenHelper {
+
+        public InMemoryTelephonyProviderV5DbHelper() {
+            super(InstrumentationRegistry.getContext(),
+                    null,    // db file name is null for in-memory db
+                    null,    // CursorFactory is null by default
+                    1);      // in-memory db version doesn't seem to matter
+            Log.d(TAG, "InMemoryTelephonyProviderV5DbHelper creating in-memory database");
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            // Set up the carriers table without any fields added in onUpgrade
+            // since these are the initial fields, there is no need to update this test fixture in
+            // the future
+            List<String> originalUniqueFields = new ArrayList<String>();
+            originalUniqueFields.add(Carriers.NUMERIC);
+            originalUniqueFields.add(Carriers.MCC);
+            originalUniqueFields.add(Carriers.MNC);
+            originalUniqueFields.add(Carriers.APN);
+            originalUniqueFields.add(Carriers.PROXY);
+            originalUniqueFields.add(Carriers.PORT);
+            originalUniqueFields.add(Carriers.MMSPROXY);
+            originalUniqueFields.add(Carriers.MMSPORT);
+            originalUniqueFields.add(Carriers.MMSC);
+            Log.d(TAG, "InMemoryTelephonyProviderV5DbHelper onCreate creating the carriers table");
+            db.execSQL(
+                    "CREATE TABLE carriers" +
+                    "(_id INTEGER PRIMARY KEY," +
+                    Carriers.NAME + " TEXT DEFAULT ''," +
+                    Carriers.NUMERIC + " TEXT DEFAULT ''," +
+                    Carriers.MCC + " TEXT DEFAULT ''," +
+                    Carriers.MNC + " TEXT DEFAULT ''," +
+                    Carriers.APN + " TEXT DEFAULT ''," +
+                    Carriers.USER + " TEXT DEFAULT ''," +
+                    Carriers.SERVER + " TEXT DEFAULT ''," +
+                    Carriers.PASSWORD + " TEXT DEFAULT ''," +
+                    Carriers.PROXY + " TEXT DEFAULT ''," +
+                    Carriers.PORT + " TEXT DEFAULT ''," +
+                    Carriers.MMSPROXY + " TEXT DEFAULT ''," +
+                    Carriers.MMSPORT + " TEXT DEFAULT ''," +
+                    Carriers.MMSC + " TEXT DEFAULT ''," +
+                    Carriers.TYPE + " TEXT DEFAULT ''," +
+                    Carriers.CURRENT + " INTEGER," +
+                    "UNIQUE (" + TextUtils.join(", ", originalUniqueFields) + "));");
+
+            // set up the siminfo table without any fields added in onUpgrade
+            // since these are the initial fields, there is no need to update this test fixture in
+            // the future
+            Log.d(TAG, "InMemoryTelephonyProviderV5DbHelper onCreate creating the siminfo table");
+            db.execSQL(
+                    "CREATE TABLE siminfo ("
+                    + 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.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.CARD_ID + " TEXT NOT NULL"
+                    + ");");
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            Log.d(TAG, "InMemoryTelephonyProviderV5DbHelper onUpgrade doing nothing");
+            return;
+        }
+    }
+}