new CarrierIdProvider for carrier resolution

create a new sqlitedb to store carrier id and matching rules.

Bug: 64131637
Test: runtest --path
tests/src/com/android/providers/telephony/CarrierIdProviderTest.java
Change-Id: I5edba0509a6ad2cba8117b2656363db9b80236f9
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5ac7fab..224fe2f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -105,6 +105,13 @@
                   android:multiprocess="false"
                   android:writePermission="android.permission.MODIFY_PHONE_STATE" />
 
+        <provider android:name="CarrierIdProvider"
+                  android:authorities="carrier_identification"
+                  android:exported="true"
+                  android:singleUser="true"
+                  android:multiprocess="false"
+                  android:writePermission="android.permission.MODIFY_PHONE_STATE" />
+
         <service
             android:name=".TelephonyBackupAgent$DeferredSmsMmsRestoreService"
             android:exported="false" />
diff --git a/src/com/android/providers/telephony/CarrierIdProvider.java b/src/com/android/providers/telephony/CarrierIdProvider.java
new file mode 100644
index 0000000..9331cb2
--- /dev/null
+++ b/src/com/android/providers/telephony/CarrierIdProvider.java
@@ -0,0 +1,231 @@
+/*
+ * 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.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class provides the ability to query the Carrier Identification databases
+ * (A.K.A. cid) which is stored in a SQLite database.
+ *
+ * Each row in carrier identification db consists of matching rule (e.g., MCCMNC, GID1, GID2, PLMN)
+ * and its matched carrier id & carrier name. Each carrier either MNO or MVNO could be
+ * identified by multiple matching rules but is assigned with a unique ID (cid).
+ *
+ *
+ * This class provides the ability to retrieve the cid of the current subscription.
+ * This is done atomically through a query.
+ *
+ * This class also provides a way to update carrier identifying attributes of an existing entry.
+ * Insert entries for new carriers or an existing carrier.
+ */
+public class CarrierIdProvider extends ContentProvider {
+
+    private static final boolean VDBG = false; // STOPSHIP if true
+    private static final String TAG = CarrierIdProvider.class.getSimpleName();
+
+    private static final String DATABASE_NAME = "carrierIdentification.db";
+    private static final int DATABASE_VERSION = 1;
+    /**
+     * The authority string for the CarrierIdProvider
+     */
+    @VisibleForTesting
+    public static final String AUTHORITY = "carrier_identification";
+
+    /**
+     * The {@code content://} style URL for the CarrierIdProvider
+     */
+    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
+    public static final String CARRIER_ID_TABLE = "carrier_id";
+
+    public static final String MCCMNC = "mccmnc";
+    public static final String GID1 = "gid1";
+    public static final String GID2 = "gid2";
+    public static final String PLMN = "plmn";
+    public static final String IMSI_PREFIX = "imsi_prefix";
+    public static final String SPN = "spn";
+    public static final String APN = "apn";
+    public static final String NAME = "carrier_name";
+    public static final String CID = "carrier_id";
+
+    private static final List<String> CARRIERS_ID_UNIQUE_FIELDS = new ArrayList<>(
+            Arrays.asList(MCCMNC, GID1, GID2, PLMN, IMSI_PREFIX, SPN, APN));
+
+    private CarrierIdDatabaseHelper mDbHelper;
+
+    @VisibleForTesting
+    public static String getStringForCarrierIdTableCreation(String tableName) {
+        return "CREATE TABLE " + tableName
+                + "(_id INTEGER PRIMARY KEY,"
+                + MCCMNC + " TEXT NOT NULL,"
+                + GID1 + " TEXT,"
+                + GID2 + " TEXT,"
+                + PLMN + " TEXT,"
+                + IMSI_PREFIX + " TEXT,"
+                + SPN + " TEXT,"
+                + APN + " TEXT,"
+                + NAME + " TEXT,"
+                + CID + " INTEGER DEFAULT -1,"
+                + "UNIQUE (" + TextUtils.join(", ", CARRIERS_ID_UNIQUE_FIELDS) + "));";
+    }
+
+    @VisibleForTesting
+    public static String getStringForIndexCreation(String tableName) {
+        return "CREATE INDEX IF NOT EXISTS mccmncIndex ON " + tableName + " (" + MCCMNC + ");";
+    }
+
+    @Override
+    public boolean onCreate() {
+        Log.d(TAG, "onCreate");
+        mDbHelper = new CarrierIdDatabaseHelper(getContext());
+        mDbHelper.getReadableDatabase();
+        return true;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        Log.d(TAG, "getType");
+        return null;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projectionIn, String selection,
+                        String[] selectionArgs, String sortOrder) {
+        if (VDBG) {
+            Log.d(TAG, "query:"
+                    + " uri=" + uri
+                    + " values=" + Arrays.toString(projectionIn)
+                    + " selection=" + selection
+                    + " selectionArgs=" + Arrays.toString(selectionArgs));
+        }
+        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+        qb.setTables(CARRIER_ID_TABLE);
+
+        SQLiteDatabase db = getReadableDatabase();
+        return qb.query(db, projectionIn, selection, selectionArgs, null, null, sortOrder);
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        final long row = getWritableDatabase().insertOrThrow(CARRIER_ID_TABLE, null, values);
+        if (row > 0) {
+            final Uri newUri = ContentUris.withAppendedId(CONTENT_URI, row);
+            getContext().getContentResolver().notifyChange(newUri, null);
+            return newUri;
+        }
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        if (VDBG) {
+            Log.d(TAG, "delete:"
+                    + " uri=" + uri
+                    + " selection={" + selection + "}"
+                    + " selection=" + selection
+                    + " selectionArgs=" + Arrays.toString(selectionArgs));
+        }
+        final int count = getWritableDatabase().delete(CARRIER_ID_TABLE, selection, selectionArgs);
+        Log.d(TAG, "  delete.count=" + count);
+        if (count > 0) {
+            getContext().getContentResolver().notifyChange(CONTENT_URI, null);
+        }
+        return count;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        if (VDBG) {
+            Log.d(TAG, "update:"
+                    + " uri=" + uri
+                    + " values={" + values + "}"
+                    + " selection=" + selection
+                    + " selectionArgs=" + Arrays.toString(selectionArgs));
+        }
+        final int count = getWritableDatabase().update(CARRIER_ID_TABLE, values, selection,
+                selectionArgs);
+        Log.d(TAG, "  update.count=" + count);
+        if (count > 0) {
+            getContext().getContentResolver().notifyChange(CONTENT_URI, null);
+        }
+        return count;
+    }
+
+    /**
+     * These methods can be overridden in a subclass for testing CarrierIdProvider using an
+     * in-memory database.
+     */
+    SQLiteDatabase getReadableDatabase() {
+        return mDbHelper.getReadableDatabase();
+    }
+    SQLiteDatabase getWritableDatabase() {
+        return mDbHelper.getWritableDatabase();
+    }
+
+    private class CarrierIdDatabaseHelper extends SQLiteOpenHelper {
+        private final String TAG = CarrierIdDatabaseHelper.class.getSimpleName();
+
+        /**
+         * CarrierIdDatabaseHelper carrier identification database helper class.
+         * @param context of the user.
+         */
+        public CarrierIdDatabaseHelper(Context context) {
+            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            Log.d(TAG, "onCreate");
+            db.execSQL(getStringForCarrierIdTableCreation(CARRIER_ID_TABLE));
+            db.execSQL(getStringForIndexCreation(CARRIER_ID_TABLE));
+        }
+
+        public void createCarrierTable(SQLiteDatabase db) {
+            db.execSQL(getStringForCarrierIdTableCreation(CARRIER_ID_TABLE));
+            db.execSQL(getStringForIndexCreation(CARRIER_ID_TABLE));
+        }
+
+        public void dropCarrierTable(SQLiteDatabase db) {
+            db.execSQL("DROP TABLE IF EXISTS " + CARRIER_ID_TABLE + ";");
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            Log.d(TAG, "dbh.onUpgrade:+ db=" + db + " oldV=" + oldVersion + " newV=" + newVersion);
+            if (oldVersion < DATABASE_VERSION) {
+                dropCarrierTable(db);
+                createCarrierTable(db);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/providers/telephony/CarrierIdProviderTest.java b/tests/src/com/android/providers/telephony/CarrierIdProviderTest.java
new file mode 100644
index 0000000..e784d8d
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/CarrierIdProviderTest.java
@@ -0,0 +1,306 @@
+/*
+ * 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.content.ContentValues;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.Handler;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests for testing CRUD operations of CarrierIdProvider.
+ * Uses CarrierIdProviderTestable 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/CarrierIdProviderTest.java \
+ *                 --test-method testInsertCarrierInfo
+ */
+public class CarrierIdProviderTest extends TestCase {
+
+    private static final String TAG = CarrierIdProviderTest.class.getSimpleName();
+
+    private static final String dummy_mccmnc = "MCCMNC_DUMMY";
+    private static final String dummy_gid1 = "GID1_DUMMY";
+    private static final String dummy_gid2 = "GID2_DUMMY";
+    private static final String dummy_plmn = "PLMN_DUMMY";
+    private static final String dummy_imsi_prefix = "IMSI_PREFIX_DUMMY";
+    private static final String dummy_spn = "SPN_DUMMY";
+    private static final String dummy_apn = "APN_DUMMY";
+    private static final String  dummy_name = "NAME_DUMMY";
+    private static final int dummy_cid = 0;
+
+    private MockContextWithProvider mContext;
+    private MockContentResolver mContentResolver;
+    private CarrierIdProviderTestable mCarrierIdProviderTestable;
+    private FakeContentObserver mContentObserver;
+
+    private class FakeContentResolver extends MockContentResolver {
+        @Override
+        public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+            super.notifyChange(uri, observer, syncToNetwork);
+            Log.d(TAG, "onChanged(uri=" + uri + ")" + observer);
+            mContentObserver.dispatchChange(false, uri);
+        }
+    }
+
+    private class FakeContentObserver extends ContentObserver {
+        private boolean changed = false;
+        private FakeContentObserver(Handler handler) {
+            super(handler);
+        }
+        @Override
+        public void onChange(boolean selfChange) {
+            changed = true;
+        }
+    }
+
+    /**
+     * This is used to give the CarrierIdProviderTest a mocked context which takes a
+     * CarrierIdProvider and attaches it to the ContentResolver.
+     */
+    private class MockContextWithProvider extends MockContext {
+        private final MockContentResolver mResolver;
+
+        public MockContextWithProvider(CarrierIdProvider carrierIdProvider) {
+            mResolver = new FakeContentResolver();
+
+            ProviderInfo providerInfo = new ProviderInfo();
+            providerInfo.authority = CarrierIdProvider.AUTHORITY;
+
+            // Add context to given carrierIdProvider
+            carrierIdProvider.attachInfoForTesting(this, providerInfo);
+            Log.d(TAG, "MockContextWithProvider: carrierIdProvider.getContext(): "
+                    + carrierIdProvider.getContext());
+
+            // Add given carrierIdProvider to mResolver, so that mResolver can send queries
+            // to the provider.
+            mResolver.addProvider(CarrierIdProvider.AUTHORITY, carrierIdProvider);
+            Log.d(TAG, "MockContextWithProvider: Add carrierIdProvider to mResolver");
+        }
+
+        @Override
+        public Object getSystemService(String name) {
+            Log.d(TAG, "getSystemService: returning null");
+            return null;
+        }
+
+        @Override
+        public MockContentResolver getContentResolver() {
+            return mResolver;
+        }
+
+        @Override
+        public int checkCallingOrSelfPermission(String permission) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mCarrierIdProviderTestable = new CarrierIdProviderTestable();
+        mContext = new MockContextWithProvider(mCarrierIdProviderTestable);
+        mContentResolver = mContext.getContentResolver();
+        mContentObserver = new FakeContentObserver(null);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mCarrierIdProviderTestable.closeDatabase();
+        super.tearDown();
+    }
+
+    /**
+     * Test inserting values in carrier identification table.
+     */
+    @Test
+    public void testInsertCarrierInfo() {
+        try {
+            mContentResolver.insert(CarrierIdProvider.CONTENT_URI, createCarrierInfoInternal());
+            Cursor countCursor = mContentResolver.query(CarrierIdProvider.CONTENT_URI,
+                    new String[]{"count(*) AS count"},
+                    null,
+                    null,
+                    null);
+            countCursor.moveToFirst();
+            assertEquals(1, countCursor.getInt(0));
+            assertTrue(mContentObserver.changed);
+        } catch (Exception e) {
+            Log.d(TAG, "Error inserting carrier info:" + e);
+        }
+    }
+
+    /**
+     * Test invalid insertion of duplicate info
+     */
+    @Test
+    public void testDuplicateInsertionCarrierInfo() {
+        try {
+            //insert same row twice to break uniqueness constraint
+            ContentValues contentValues = createCarrierInfoInternal();
+            mContentResolver.insert(CarrierIdProvider.CONTENT_URI, contentValues);
+            mContentResolver.insert(CarrierIdProvider.CONTENT_URI, contentValues);
+            Assert.fail("should throw an exception for duplicate carrier info");
+        } catch (Exception e) {
+            Log.d(TAG, "Error inserting carrier info:" + e);
+        }
+    }
+
+    /**
+     * Test invalid insertion of null mccmnc
+     */
+    @Test
+    public void testInvalidInsertionCarrierInfo() {
+        try {
+            //insert a row with null mnccmnc to break not null constraint
+            ContentValues contentValues = new ContentValues();
+            contentValues.put(CarrierIdProvider.GID1, dummy_gid1);
+            mContentResolver.insert(CarrierIdProvider.CONTENT_URI, contentValues);
+            Assert.fail("should throw an exception for null mccmnc");
+        } catch (Exception e) {
+            Log.d(TAG, "Error inserting carrier info:" + e);
+            assertTrue(e instanceof SQLException);
+            assertFalse(mContentObserver.changed);
+        }
+    }
+
+    /**
+     * Test delete.
+     */
+    @Test
+    public void testDeleteCarrierInfo() {
+        try {
+            mContentResolver.insert(CarrierIdProvider.CONTENT_URI, createCarrierInfoInternal());
+        } catch (Exception e) {
+            Log.d(TAG, "Error inserting carrier info:" + e);
+        }
+        int numRowsDeleted = -1;
+        try {
+            String whereClause = CarrierIdProvider.MCCMNC + "=?";
+            String[] whereArgs = new String[] { dummy_mccmnc };
+            numRowsDeleted = mContentResolver.delete(CarrierIdProvider.CONTENT_URI, whereClause,
+                    whereArgs);
+        } catch (Exception e) {
+            Log.d(TAG, "Error deleting values:" + e);
+        }
+        assertEquals(1, numRowsDeleted);
+        assertTrue(mContentObserver.changed);
+    }
+
+    /**
+     * Test update & query.
+     */
+    @Test
+    public void testUpdateCarrierInfo() {
+        int cid = -1;
+        ContentValues contentValues = createCarrierInfoInternal();
+
+        try {
+            mContentResolver.insert(CarrierIdProvider.CONTENT_URI, contentValues);
+        } catch (Exception e) {
+            Log.d(TAG, "Error inserting carrierInfo:" + e);
+        }
+
+        try {
+            contentValues.put(CarrierIdProvider.CID, 1);
+            mContentResolver.update(CarrierIdProvider.CONTENT_URI, contentValues,
+                    CarrierIdProvider.MCCMNC + "=?", new String[] { dummy_mccmnc });
+        } catch (Exception e) {
+            Log.d(TAG, "Error updating values:" + e);
+        }
+
+        try {
+            Cursor findEntry = mContentResolver.query(CarrierIdProvider.CONTENT_URI,
+                    new String[] { CarrierIdProvider.CID },
+                    CarrierIdProvider.MCCMNC + "=?", new String[] { dummy_mccmnc }, null);
+            findEntry.moveToFirst();
+            cid = findEntry.getInt(0);
+        } catch (Exception e) {
+            Log.d(TAG, "Query failed:" + e);
+        }
+        assertEquals(1, cid);
+        assertTrue(mContentObserver.changed);
+    }
+
+    @Test
+    public void testMultiRowInsertionQuery() {
+        ContentValues contentValues = createCarrierInfoInternal();
+
+        try {
+            // insert a MVNO
+            mContentResolver.insert(CarrierIdProvider.CONTENT_URI, contentValues);
+            // insert its MNO
+            contentValues = new ContentValues();
+            contentValues.put(CarrierIdProvider.MCCMNC, dummy_mccmnc);
+            contentValues.put(CarrierIdProvider.CID, 1);
+            mContentResolver.insert(CarrierIdProvider.CONTENT_URI, contentValues);
+        } catch (Exception e) {
+            Log.d(TAG, "Error inserting carrierInfo:" + e);
+        }
+
+        Cursor findEntry = null;
+        String[] columns ={CarrierIdProvider.CID};
+        try {
+            findEntry = mContentResolver.query(CarrierIdProvider.CONTENT_URI, columns,
+                    CarrierIdProvider.MCCMNC + "=?", new String[] { dummy_mccmnc }, null);
+        } catch (Exception e) {
+            Log.d(TAG, "Query failed:" + e);
+        }
+        assertEquals(2, findEntry.getCount());
+
+        try {
+            // query based on mccmnc & gid1
+            findEntry = mContentResolver.query(CarrierIdProvider.CONTENT_URI, columns,
+                    CarrierIdProvider.MCCMNC + "=? and " + CarrierIdProvider.GID1 + "=?",
+                    new String[] { dummy_mccmnc, dummy_gid1 }, null);
+        } catch (Exception e) {
+            Log.d(TAG, "Query failed:" + e);
+        }
+        assertEquals(1, findEntry.getCount());
+        findEntry.moveToFirst();
+        assertEquals(dummy_cid, findEntry.getInt(0));
+    }
+
+    private static ContentValues createCarrierInfoInternal() {
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(CarrierIdProvider.MCCMNC, dummy_mccmnc);
+        contentValues.put(CarrierIdProvider.GID1, dummy_gid1);
+        contentValues.put(CarrierIdProvider.GID2, dummy_gid2);
+        contentValues.put(CarrierIdProvider.PLMN, dummy_plmn);
+        contentValues.put(CarrierIdProvider.IMSI_PREFIX, dummy_imsi_prefix);
+        contentValues.put(CarrierIdProvider.SPN, dummy_spn);
+        contentValues.put(CarrierIdProvider.APN, dummy_apn);
+        contentValues.put(CarrierIdProvider.NAME, dummy_name);
+        contentValues.put(CarrierIdProvider.CID, dummy_cid);
+        return contentValues;
+    }
+}
diff --git a/tests/src/com/android/providers/telephony/CarrierIdProviderTestable.java b/tests/src/com/android/providers/telephony/CarrierIdProviderTestable.java
new file mode 100644
index 0000000..8468801
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/CarrierIdProviderTestable.java
@@ -0,0 +1,82 @@
+/*
+ * 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 CarrierIdProvider used for testing on an in-memory database
+ */
+public class CarrierIdProviderTestable extends CarrierIdProvider {
+    private static final String TAG = CarrierIdProviderTestable.class.getSimpleName();
+
+    private InMemoryCarrierIdProviderDbHelper mDbHelper;
+
+    @Override
+    public boolean onCreate() {
+        Log.d(TAG, "onCreate called: mDbHelper = new InMemoryCarrierIdProviderDbHelper()");
+        mDbHelper = new InMemoryCarrierIdProviderDbHelper();
+        return true;
+    }
+
+    // close mDbHelper database object
+    protected void closeDatabase() {
+        mDbHelper.close();
+    }
+
+    @Override
+    SQLiteDatabase getReadableDatabase() {
+        Log.d(TAG, "getReadableDatabase called" + mDbHelper.getReadableDatabase());
+        return mDbHelper.getReadableDatabase();
+    }
+
+    @Override
+    SQLiteDatabase getWritableDatabase() {
+        Log.d(TAG, "getWritableDatabase called" + mDbHelper.getWritableDatabase());
+        return mDbHelper.getWritableDatabase();
+    }
+
+    /**
+     * An in memory DB for CarrierIdProviderTestable to use
+     */
+    public static class InMemoryCarrierIdProviderDbHelper extends SQLiteOpenHelper {
+        public InMemoryCarrierIdProviderDbHelper() {
+            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, "InMemoryCarrierIdProviderDbHelper creating in-memory database");
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            //set up the Carrier id table
+            Log.d(TAG, "InMemoryCarrierIdProviderDbHelper onCreate creating the carrier infp table");
+            db.execSQL(CarrierIdProvider.getStringForCarrierIdTableCreation(
+                    CarrierIdProvider.CARRIER_ID_TABLE));
+            db.execSQL(CarrierIdProvider.getStringForIndexCreation(
+                    CarrierIdProvider.CARRIER_ID_TABLE));
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            Log.d(TAG, "InMemoryCarrierIdProviderDbHelper onUpgrade doing nothing");
+            return;
+        }
+    }
+}