[automerger skipped] DO NOT MERGE Check permissions for URL_SIMINFO am: caab85d914 am: e7ee89118e am: a8daa7c551 -s ours am: 85b15220ea -s ours am: 639554a1a7 am: 7c10e1dbd2

Change-Id: Ia282532a03e8b05b8fade4a39d3cdb1af688647c
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4ae9acf..3b95bce 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -107,6 +107,12 @@
                   android:multiprocess="false"
                   android:writePermission="android.permission.MODIFY_PHONE_STATE" />
 
+        <provider android:name="CellBroadcastProvider"
+                  android:authorities="cellbroadcasts_fwk"
+                  android:exported="true"
+                  android:singleUser="true"
+                  android:multiprocess="false" />
+
         <provider android:name="HbpcdLookupProvider"
                   android:authorities="hbpcd_lookup"
                   android:exported="true"
diff --git a/assets/carrier_list.pb b/assets/carrier_list.pb
index 93bc1b3..3519d2c 100644
--- a/assets/carrier_list.pb
+++ b/assets/carrier_list.pb
Binary files differ
diff --git a/assets/carrier_list.textpb b/assets/carrier_list.textpb
index 1e033ee..b1c6825 100644
--- a/assets/carrier_list.textpb
+++ b/assets/carrier_list.textpb
Binary files differ
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 6cfcdfb..f3baf24 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/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">"Configuration du réseau mobile"</string>
-    <string name="app_label" product="default" msgid="8338087656149558019">"Stockage tél. et SMS/MMS"</string>
+    <string name="app_label" product="default" msgid="8338087656149558019">"Téléphone et stockage des messages"</string>
 </resources>
diff --git a/src/com/android/providers/telephony/CellBroadcastProvider.java b/src/com/android/providers/telephony/CellBroadcastProvider.java
new file mode 100644
index 0000000..4c8a7e2
--- /dev/null
+++ b/src/com/android/providers/telephony/CellBroadcastProvider.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.telephony;
+
+import android.app.AppOpsManager;
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Process;
+import android.provider.Telephony.CellBroadcasts;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * The content provider that provides access of cell broadcast message to application.
+ * Permission {@link android.permission.READ_CELL_BROADCASTS} is required for querying the cell
+ * broadcast message. Only phone process has the permission to write/update the database via this
+ * provider.
+ */
+public class CellBroadcastProvider extends ContentProvider {
+    /** Interface for read/write permission check. */
+    public interface PermissionChecker {
+        /** Return {@code True} if the caller has the permission to write/update the database. */
+        boolean hasWritePermission();
+
+        /** Return {@code True} if the caller has the permission to query the database. */
+        boolean hasReadPermission();
+    }
+
+    private static final String TAG = CellBroadcastProvider.class.getSimpleName();
+
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+    /** Database name. */
+    private static final String DATABASE_NAME = "cellbroadcasts.db";
+
+    /** Database version. */
+    private static final int DATABASE_VERSION = 1;
+
+    /** URI matcher for ContentProvider queries. */
+    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+    /** URI matcher type to get all cell broadcasts. */
+    private static final int ALL = 0;
+
+    /** MIME type for the list of all cell broadcasts. */
+    private static final String LIST_TYPE = "vnd.android.cursor.dir/cellbroadcast";
+
+    /** Table name of cell broadcast message. */
+    @VisibleForTesting
+    public static final String CELL_BROADCASTS_TABLE_NAME = "cell_broadcasts";
+
+    /** Authority string for content URIs. */
+    @VisibleForTesting
+    public static final String AUTHORITY = "cellbroadcasts_fwk";
+
+    /** Content uri of this provider. */
+    public static final Uri CONTENT_URI = Uri.parse("content://cellbroadcasts_fwk");
+
+    @VisibleForTesting
+    public PermissionChecker mPermissionChecker;
+
+    /** The database helper for this content provider. */
+    @VisibleForTesting
+    public SQLiteOpenHelper mDbHelper;
+
+    static {
+        sUriMatcher.addURI(AUTHORITY, null, ALL);
+    }
+
+    public CellBroadcastProvider() {}
+
+    @VisibleForTesting
+    public CellBroadcastProvider(PermissionChecker permissionChecker) {
+        mPermissionChecker = permissionChecker;
+    }
+
+    @Override
+    public boolean onCreate() {
+        mDbHelper = new CellBroadcastDatabaseHelper(getContext());
+        mPermissionChecker = new CellBroadcastPermissionChecker();
+        setAppOps(AppOpsManager.OP_READ_CELL_BROADCASTS, AppOpsManager.OP_NONE);
+        return true;
+    }
+
+    /**
+     * Return the MIME type of the data at the specified URI.
+     *
+     * @param uri the URI to query.
+     * @return a MIME type string, or null if there is no type.
+     */
+    @Override
+    public String getType(Uri uri) {
+        int match = sUriMatcher.match(uri);
+        switch (match) {
+            case ALL:
+                return LIST_TYPE;
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        checkReadPermission();
+
+        if (DBG) {
+            Log.d(TAG, "query:"
+                    + " uri = " + uri
+                    + " projection = " + Arrays.toString(projection)
+                    + " selection = " + selection
+                    + " selectionArgs = " + Arrays.toString(selectionArgs)
+                    + " sortOrder = " + sortOrder);
+        }
+
+        String orderBy;
+        if (!TextUtils.isEmpty(sortOrder)) {
+            orderBy = sortOrder;
+        } else {
+            orderBy = CellBroadcasts.RECEIVED_TIME + " DESC";
+        }
+
+        int match = sUriMatcher.match(uri);
+        switch (match) {
+            case ALL:
+                return getReadableDatabase().query(
+                        CELL_BROADCASTS_TABLE_NAME, projection, selection, selectionArgs,
+                        null /* groupBy */, null /* having */, orderBy);
+            default:
+                throw new IllegalArgumentException(
+                        "Query method doesn't support this uri = " + uri);
+        }
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        checkWritePermission();
+
+        if (DBG) {
+            Log.d(TAG, "insert:"
+                    + " uri = " + uri
+                    + " contentValue = " + values);
+        }
+
+        switch (sUriMatcher.match(uri)) {
+            case ALL:
+                long row = getWritableDatabase().insertOrThrow(CELL_BROADCASTS_TABLE_NAME, null,
+                        values);
+                if (row > 0) {
+                    Uri newUri = ContentUris.withAppendedId(CONTENT_URI, row);
+                    getContext().getContentResolver()
+                            .notifyChange(CONTENT_URI, null /* observer */);
+                    return newUri;
+                } else {
+                    Log.e(TAG, "Insert record failed because of unknown reason, uri = " + uri);
+                    return null;
+                }
+            default:
+                throw new IllegalArgumentException(
+                        "Insert method doesn't support this uri = " + uri);
+        }
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        checkWritePermission();
+
+        if (DBG) {
+            Log.d(TAG, "delete:"
+                    + " uri = " + uri
+                    + " selection = " + selection
+                    + " selectionArgs = " + Arrays.toString(selectionArgs));
+        }
+
+        switch (sUriMatcher.match(uri)) {
+            case ALL:
+                return getWritableDatabase().delete(CELL_BROADCASTS_TABLE_NAME,
+                        selection, selectionArgs);
+            default:
+                throw new IllegalArgumentException(
+                        "Delete method doesn't support this uri = " + uri);
+        }
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        checkWritePermission();
+
+        if (DBG) {
+            Log.d(TAG, "update:"
+                    + " uri = " + uri
+                    + " values = {" + values + "}"
+                    + " selection = " + selection
+                    + " selectionArgs = " + Arrays.toString(selectionArgs));
+        }
+
+        switch (sUriMatcher.match(uri)) {
+            case ALL:
+                int rowCount = getWritableDatabase().update(
+                        CELL_BROADCASTS_TABLE_NAME,
+                        values,
+                        selection,
+                        selectionArgs);
+                if (rowCount > 0) {
+                    getContext().getContentResolver().notifyChange(uri, null /* observer */);
+                }
+                return rowCount;
+            default:
+                throw new IllegalArgumentException(
+                        "Update method doesn't support this uri = " + uri);
+        }
+    }
+
+    @VisibleForTesting
+    public static String getStringForCellBroadcastTableCreation(String tableName) {
+        return "CREATE TABLE " + tableName + " ("
+                + CellBroadcasts._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+                + CellBroadcasts.SUB_ID + " INTEGER,"
+                + CellBroadcasts.GEOGRAPHICAL_SCOPE + " INTEGER,"
+                + CellBroadcasts.PLMN + " TEXT,"
+                + CellBroadcasts.LAC + " INTEGER,"
+                + CellBroadcasts.CID + " INTEGER,"
+                + CellBroadcasts.SERIAL_NUMBER + " INTEGER,"
+                + CellBroadcasts.SERVICE_CATEGORY + " INTEGER,"
+                + CellBroadcasts.LANGUAGE_CODE + " TEXT,"
+                + CellBroadcasts.MESSAGE_BODY + " TEXT,"
+                + CellBroadcasts.MESSAGE_FORMAT + " INTEGER,"
+                + CellBroadcasts.MESSAGE_PRIORITY + " INTEGER,"
+                + CellBroadcasts.ETWS_WARNING_TYPE + " INTEGER,"
+                + CellBroadcasts.CMAS_MESSAGE_CLASS + " INTEGER,"
+                + CellBroadcasts.CMAS_CATEGORY + " INTEGER,"
+                + CellBroadcasts.CMAS_RESPONSE_TYPE + " INTEGER,"
+                + CellBroadcasts.CMAS_SEVERITY + " INTEGER,"
+                + CellBroadcasts.CMAS_URGENCY + " INTEGER,"
+                + CellBroadcasts.CMAS_CERTAINTY + " INTEGER,"
+                + CellBroadcasts.RECEIVED_TIME + " BIGINT,"
+                + CellBroadcasts.MESSAGE_BROADCASTED + " BOOLEAN DEFAULT 0,"
+                + CellBroadcasts.GEOMETRIES + " TEXT,"
+                + CellBroadcasts.MAXIMUM_WAIT_TIME + " INTEGER);";
+    }
+
+    private SQLiteDatabase getWritableDatabase() {
+        return mDbHelper.getWritableDatabase();
+    }
+
+    private SQLiteDatabase getReadableDatabase() {
+        return mDbHelper.getReadableDatabase();
+    }
+
+    private void checkWritePermission() {
+        if (!mPermissionChecker.hasWritePermission()) {
+            throw new SecurityException(
+                    "No permission to write CellBroadcast provider");
+        }
+    }
+
+    private void checkReadPermission() {
+        if (!mPermissionChecker.hasReadPermission()) {
+            throw new SecurityException(
+                    "No permission to read CellBroadcast provider");
+        }
+    }
+
+    private class CellBroadcastDatabaseHelper extends SQLiteOpenHelper {
+        CellBroadcastDatabaseHelper(Context context) {
+            super(context, DATABASE_NAME, null /* factory */, DATABASE_VERSION);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL(getStringForCellBroadcastTableCreation(CELL_BROADCASTS_TABLE_NAME));
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
+    }
+
+    private class CellBroadcastPermissionChecker implements PermissionChecker {
+        @Override
+        public boolean hasWritePermission() {
+            // Only the phone process has the write permission to modify this provider. 
+            return Binder.getCallingUid() == Process.PHONE_UID;
+        }
+
+        @Override
+        public boolean hasReadPermission() {
+            // Only the phone process has the read permission to query data from this provider. 
+            return Binder.getCallingUid() == Process.PHONE_UID;
+        }
+    }
+}
diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
index 5fc36d2..7ecfc46 100644
--- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -545,7 +545,7 @@
                 intent.putExtra(Intents.EXTRA_IS_INITIAL_CREATE, true);
             }
 
-            mContext.sendBroadcast(intent, android.Manifest.permission.READ_SMS);
+            mContext.sendBroadcast(intent);
         }
         createMmsTables(db);
         createSmsTables(db);
diff --git a/src/com/android/providers/telephony/MmsSmsProvider.java b/src/com/android/providers/telephony/MmsSmsProvider.java
index 8c3555c..dbf85ae 100644
--- a/src/com/android/providers/telephony/MmsSmsProvider.java
+++ b/src/com/android/providers/telephony/MmsSmsProvider.java
@@ -332,6 +332,16 @@
         final String pduTable = MmsProvider.getPduTable(accessRestricted);
         final String smsTable = SmsProvider.getSmsTable(accessRestricted);
 
+        // If access is restricted, we don't allow subqueries in the query.
+        if (accessRestricted) {
+            try {
+                SqlQueryChecker.checkQueryParametersForSubqueries(projection, selection, sortOrder);
+            } catch (IllegalArgumentException e) {
+                Log.w(LOG_TAG, "Query rejected: " + e.getMessage());
+                return null;
+            }
+        }
+
         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
         Cursor cursor = null;
         final int match = URI_MATCHER.match(uri);
diff --git a/src/com/android/providers/telephony/SQLiteTokenizer.java b/src/com/android/providers/telephony/SQLiteTokenizer.java
new file mode 100644
index 0000000..39749fb
--- /dev/null
+++ b/src/com/android/providers/telephony/SQLiteTokenizer.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2019 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.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Consumer;
+
+/**
+ * Copied from frameworks/base/core/java/android/database/sqlite/SQLiteTokenizer.java
+ *
+ * SQL Tokenizer specialized to extract tokens from SQL (snippets).
+ * <p>
+ * Based on sqlite3GetToken() in tokenzie.c in SQLite.
+ * <p>
+ * Source for v3.8.6 (which android uses): http://www.sqlite.org/src/artifact/ae45399d6252b4d7
+ * (Latest source as of now: http://www.sqlite.org/src/artifact/78c8085bc7af1922)
+ * <p>
+ * Also draft spec: http://www.sqlite.org/draft/tokenreq.html
+ */
+public class SQLiteTokenizer {
+    private static boolean isAlpha(char ch) {
+        return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || (ch == '_');
+    }
+
+    private static boolean isNum(char ch) {
+        return ('0' <= ch && ch <= '9');
+    }
+
+    private static boolean isAlNum(char ch) {
+        return isAlpha(ch) || isNum(ch);
+    }
+
+    private static boolean isAnyOf(char ch, String set) {
+        return set.indexOf(ch) >= 0;
+    }
+
+    private static IllegalArgumentException genException(String message, String sql) {
+        throw new IllegalArgumentException(message + " in '" + sql + "'");
+    }
+
+    private static char peek(String s, int index) {
+        return index < s.length() ? s.charAt(index) : '\0';
+    }
+
+    public static final int OPTION_NONE = 0;
+
+    /**
+     * Require that SQL contains only tokens; any comments or values will result
+     * in an exception.
+     */
+    public static final int OPTION_TOKEN_ONLY = 1 << 0;
+
+    /**
+     * Tokenize the given SQL, returning the list of each encountered token.
+     *
+     * @throws IllegalArgumentException if invalid SQL is encountered.
+     */
+    public static List<String> tokenize(@Nullable String sql, int options) {
+        final ArrayList<String> res = new ArrayList<>();
+        tokenize(sql, options, res::add);
+        return res;
+    }
+
+    /**
+     * Tokenize the given SQL, sending each encountered token to the given
+     * {@link Consumer}.
+     *
+     * @throws IllegalArgumentException if invalid SQL is encountered.
+     */
+    public static void tokenize(@Nullable String sql, int options, Consumer<String> checker) {
+        if (sql == null) {
+            return;
+        }
+        int pos = 0;
+        final int len = sql.length();
+        while (pos < len) {
+            final char ch = peek(sql, pos);
+
+            // Regular token.
+            if (isAlpha(ch)) {
+                final int start = pos;
+                pos++;
+                while (isAlNum(peek(sql, pos))) {
+                    pos++;
+                }
+                final int end = pos;
+
+                final String token = sql.substring(start, end);
+                checker.accept(token);
+
+                continue;
+            }
+
+            // Handle quoted tokens
+            if (isAnyOf(ch, "'\"`")) {
+                final int quoteStart = pos;
+                pos++;
+
+                for (;;) {
+                    pos = sql.indexOf(ch, pos);
+                    if (pos < 0) {
+                        throw genException("Unterminated quote", sql);
+                    }
+                    if (peek(sql, pos + 1) != ch) {
+                        break;
+                    }
+                    // Quoted quote char -- e.g. "abc""def" is a single string.
+                    pos += 2;
+                }
+                final int quoteEnd = pos;
+                pos++;
+
+                if (ch != '\'') {
+                    // Extract the token
+                    final String tokenUnquoted = sql.substring(quoteStart + 1, quoteEnd);
+
+                    final String token;
+
+                    // Unquote if needed. i.e. "aa""bb" -> aa"bb
+                    if (tokenUnquoted.indexOf(ch) >= 0) {
+                        token = tokenUnquoted.replaceAll(
+                                String.valueOf(ch) + ch, String.valueOf(ch));
+                    } else {
+                        token = tokenUnquoted;
+                    }
+                    checker.accept(token);
+                } else {
+                    if ((options &= OPTION_TOKEN_ONLY) != 0) {
+                        throw genException("Non-token detected", sql);
+                    }
+                }
+                continue;
+            }
+            // Handle tokens enclosed in [...]
+            if (ch == '[') {
+                final int quoteStart = pos;
+                pos++;
+
+                pos = sql.indexOf(']', pos);
+                if (pos < 0) {
+                    throw genException("Unterminated quote", sql);
+                }
+                final int quoteEnd = pos;
+                pos++;
+
+                final String token = sql.substring(quoteStart + 1, quoteEnd);
+
+                checker.accept(token);
+                continue;
+            }
+            if ((options &= OPTION_TOKEN_ONLY) != 0) {
+                throw genException("Non-token detected", sql);
+            }
+
+            // Detect comments.
+            if (ch == '-' && peek(sql, pos + 1) == '-') {
+                pos += 2;
+                pos = sql.indexOf('\n', pos);
+                if (pos < 0) {
+                    // We disallow strings ending in an inline comment.
+                    throw genException("Unterminated comment", sql);
+                }
+                pos++;
+
+                continue;
+            }
+            if (ch == '/' && peek(sql, pos + 1) == '*') {
+                pos += 2;
+                pos = sql.indexOf("*/", pos);
+                if (pos < 0) {
+                    throw genException("Unterminated comment", sql);
+                }
+                pos += 2;
+
+                continue;
+            }
+
+            // Semicolon is never allowed.
+            if (ch == ';') {
+                throw genException("Semicolon is not allowed", sql);
+            }
+
+            // For this purpose, we can simply ignore other characters.
+            // (Note it doesn't handle the X'' literal properly and reports this X as a token,
+            // but that should be fine...)
+            pos++;
+        }
+    }
+
+    /**
+     * Test if given token is a
+     * <a href="https://www.sqlite.org/lang_keywords.html">SQLite reserved
+     * keyword</a>.
+     */
+    public static boolean isKeyword(@NonNull String token) {
+        switch (token.toUpperCase(Locale.US)) {
+            case "ABORT": case "ACTION": case "ADD": case "AFTER":
+            case "ALL": case "ALTER": case "ANALYZE": case "AND":
+            case "AS": case "ASC": case "ATTACH": case "AUTOINCREMENT":
+            case "BEFORE": case "BEGIN": case "BETWEEN": case "BINARY":
+            case "BY": case "CASCADE": case "CASE": case "CAST":
+            case "CHECK": case "COLLATE": case "COLUMN": case "COMMIT":
+            case "CONFLICT": case "CONSTRAINT": case "CREATE": case "CROSS":
+            case "CURRENT": case "CURRENT_DATE": case "CURRENT_TIME": case "CURRENT_TIMESTAMP":
+            case "DATABASE": case "DEFAULT": case "DEFERRABLE": case "DEFERRED":
+            case "DELETE": case "DESC": case "DETACH": case "DISTINCT":
+            case "DO": case "DROP": case "EACH": case "ELSE":
+            case "END": case "ESCAPE": case "EXCEPT": case "EXCLUDE":
+            case "EXCLUSIVE": case "EXISTS": case "EXPLAIN": case "FAIL":
+            case "FILTER": case "FOLLOWING": case "FOR": case "FOREIGN":
+            case "FROM": case "FULL": case "GLOB": case "GROUP":
+            case "GROUPS": case "HAVING": case "IF": case "IGNORE":
+            case "IMMEDIATE": case "IN": case "INDEX": case "INDEXED":
+            case "INITIALLY": case "INNER": case "INSERT": case "INSTEAD":
+            case "INTERSECT": case "INTO": case "IS": case "ISNULL":
+            case "JOIN": case "KEY": case "LEFT": case "LIKE":
+            case "LIMIT": case "MATCH": case "NATURAL": case "NO":
+            case "NOCASE": case "NOT": case "NOTHING": case "NOTNULL":
+            case "NULL": case "OF": case "OFFSET": case "ON":
+            case "OR": case "ORDER": case "OTHERS": case "OUTER":
+            case "OVER": case "PARTITION": case "PLAN": case "PRAGMA":
+            case "PRECEDING": case "PRIMARY": case "QUERY": case "RAISE":
+            case "RANGE": case "RECURSIVE": case "REFERENCES": case "REGEXP":
+            case "REINDEX": case "RELEASE": case "RENAME": case "REPLACE":
+            case "RESTRICT": case "RIGHT": case "ROLLBACK": case "ROW":
+            case "ROWS": case "RTRIM": case "SAVEPOINT": case "SELECT":
+            case "SET": case "TABLE": case "TEMP": case "TEMPORARY":
+            case "THEN": case "TIES": case "TO": case "TRANSACTION":
+            case "TRIGGER": case "UNBOUNDED": case "UNION": case "UNIQUE":
+            case "UPDATE": case "USING": case "VACUUM": case "VALUES":
+            case "VIEW": case "VIRTUAL": case "WHEN": case "WHERE":
+            case "WINDOW": case "WITH": case "WITHOUT":
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Test if given token is a
+     * <a href="https://www.sqlite.org/lang_corefunc.html">SQLite reserved
+     * function</a>.
+     */
+    public static boolean isFunction(@NonNull String token) {
+        switch (token.toLowerCase(Locale.US)) {
+            case "abs": case "avg": case "char": case "coalesce":
+            case "count": case "glob": case "group_concat": case "hex":
+            case "ifnull": case "instr": case "length": case "like":
+            case "likelihood": case "likely": case "lower": case "ltrim":
+            case "max": case "min": case "nullif": case "random":
+            case "randomblob": case "replace": case "round": case "rtrim":
+            case "substr": case "sum": case "total": case "trim":
+            case "typeof": case "unicode": case "unlikely": case "upper":
+            case "zeroblob":
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Test if given token is a
+     * <a href="https://www.sqlite.org/datatype3.html">SQLite reserved type</a>.
+     */
+    public static boolean isType(@NonNull String token) {
+        switch (token.toUpperCase(Locale.US)) {
+            case "INT": case "INTEGER": case "TINYINT": case "SMALLINT":
+            case "MEDIUMINT": case "BIGINT": case "INT2": case "INT8":
+            case "CHARACTER": case "VARCHAR": case "NCHAR": case "NVARCHAR":
+            case "TEXT": case "CLOB": case "BLOB": case "REAL":
+            case "DOUBLE": case "FLOAT": case "NUMERIC": case "DECIMAL":
+            case "BOOLEAN": case "DATE": case "DATETIME":
+                return true;
+            default:
+                return false;
+        }
+    }
+}
diff --git a/src/com/android/providers/telephony/SmsProvider.java b/src/com/android/providers/telephony/SmsProvider.java
index e2e6d10..1213c37 100644
--- a/src/com/android/providers/telephony/SmsProvider.java
+++ b/src/com/android/providers/telephony/SmsProvider.java
@@ -122,6 +122,16 @@
         final String smsTable = getSmsTable(accessRestricted);
         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
 
+        // If access is restricted, we don't allow subqueries in the query.
+        if (accessRestricted) {
+            try {
+                SqlQueryChecker.checkQueryParametersForSubqueries(projectionIn, selection, sort);
+            } catch (IllegalArgumentException e) {
+                Log.w(TAG, "Query rejected: " + e.getMessage());
+                return null;
+            }
+        }
+
         // Generate the body of the query.
         int match = sURLMatcher.match(url);
         SQLiteDatabase db = getReadableDatabase(match);
diff --git a/src/com/android/providers/telephony/SqlQueryChecker.java b/src/com/android/providers/telephony/SqlQueryChecker.java
new file mode 100644
index 0000000..a98dd25
--- /dev/null
+++ b/src/com/android/providers/telephony/SqlQueryChecker.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 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;
+
+public class SqlQueryChecker {
+    private static final String SELECT_TOKEN = "select";
+
+    static void checkToken(String token) {
+        if (SELECT_TOKEN.equals(token.toLowerCase())) {
+            throw new IllegalArgumentException("SELECT token not allowed in query");
+        }
+    }
+
+    /**
+     * Check the query parameters to see if they contain subqueries. Throws an
+     * {@link IllegalArgumentException} if they do. See
+     * {@link android.content.ContentProvider#query} for the definitions of the arguments.
+     */
+    static void checkQueryParametersForSubqueries(String[] projection,
+            String selection, String sortOrder) {
+        if (projection != null) {
+            for (String proj : projection) {
+                SQLiteTokenizer.tokenize(proj, SQLiteTokenizer.OPTION_NONE,
+                        SqlQueryChecker::checkToken);
+            }
+        }
+        SQLiteTokenizer.tokenize(selection, SQLiteTokenizer.OPTION_NONE,
+                SqlQueryChecker::checkToken);
+        SQLiteTokenizer.tokenize(sortOrder, SQLiteTokenizer.OPTION_NONE,
+                SqlQueryChecker::checkToken);
+    }
+}
diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index d9e368d..c564a07 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -147,7 +147,7 @@
     private static final boolean DBG = true;
     private static final boolean VDBG = false; // STOPSHIP if true
 
-    private static final int DATABASE_VERSION = 41 << 16;
+    private static final int DATABASE_VERSION = 42 << 16;
     private static final int URL_UNKNOWN = 0;
     private static final int URL_TELEPHONY = 1;
     private static final int URL_CURRENT = 2;
@@ -375,6 +375,7 @@
                 + SubscriptionManager.IS_EMBEDDED + " INTEGER DEFAULT 0,"
                 + SubscriptionManager.CARD_ID + " TEXT NOT NULL,"
                 + SubscriptionManager.ACCESS_RULES + " BLOB,"
+                + SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS + " BLOB,"
                 + SubscriptionManager.IS_REMOVABLE + " INTEGER DEFAULT 0,"
                 + SubscriptionManager.CB_EXTREME_THREAT_ALERT + " INTEGER DEFAULT 1,"
                 + SubscriptionManager.CB_SEVERE_THREAT_ALERT + " INTEGER DEFAULT 1,"
@@ -1353,6 +1354,19 @@
                 oldVersion = 41 << 16 | 6;
             }
 
+            if (oldVersion < (42 << 16 | 6)) {
+                try {
+                    // Try to update the siminfo table. It might not be there.
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN " +
+                        SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS + " BLOB;");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
+                                "The table will get created in onOpen.");
+                    }
+                }
+            }
+
 
             if (DBG) {
                 log("dbh.onUpgrade:- db=" + db + " oldV=" + oldVersion + " newV=" + newVersion);
diff --git a/tests/src/com/android/providers/telephony/CellBroadcastProviderTest.java b/tests/src/com/android/providers/telephony/CellBroadcastProviderTest.java
new file mode 100644
index 0000000..b5ff07b
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/CellBroadcastProviderTest.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2019 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 com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+
+import junit.framework.TestCase;
+
+import android.content.ContentValues;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Telephony.CellBroadcasts;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+import android.util.Log;
+
+import com.android.providers.telephony.CellBroadcastProvider.PermissionChecker;
+
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class CellBroadcastProviderTest extends TestCase {
+    private static final String TAG = CellBroadcastProviderTest.class.getSimpleName();
+
+    public static final Uri CONTENT_URI = Uri.parse("content://cellbroadcasts_fwk");
+
+    private static final int GEO_SCOPE = 1;
+    private static final String PLMN = "123456";
+    private static final int LAC = 13;
+    private static final int CID = 123;
+    private static final int SERIAL_NUMBER = 17984;
+    private static final int SERVICE_CATEGORY = 4379;
+    private static final String LANGUAGE_CODE = "en";
+    private static final String MESSAGE_BODY = "AMBER Alert: xxxx";
+    private static final int MESSAGE_FORMAT = 1;
+    private static final int MESSAGE_PRIORITY = 3;
+    private static final int ETWS_WARNING_TYPE = 1;
+    private static final int CMAS_MESSAGE_CLASS = 1;
+    private static final int CMAS_CATEGORY = 6;
+    private static final int CMAS_RESPONSE_TYPE = 1;
+    private static final int CMAS_SEVERITY = 2;
+    private static final int CMAS_URGENCY = 3;
+    private static final int CMAS_CERTAINTY = 4;
+    private static final int RECEIVED_TIME = 1562792637;
+    private static final int MESSAGE_BROADCASTED = 1;
+    private static final String GEOMETRIES_COORDINATES
+            = "polygon|0,0|0,1|1,1|1,0;circle|0,0|100";
+
+    private static final String SELECT_BY_ID = CellBroadcasts._ID + "=?";
+
+    private static final String[] QUERY_COLUMNS = {
+            CellBroadcasts._ID,
+            CellBroadcasts.GEOGRAPHICAL_SCOPE,
+            CellBroadcasts.PLMN,
+            CellBroadcasts.LAC,
+            CellBroadcasts.CID,
+            CellBroadcasts.SERIAL_NUMBER,
+            CellBroadcasts.SERVICE_CATEGORY,
+            CellBroadcasts.LANGUAGE_CODE,
+            CellBroadcasts.MESSAGE_BODY,
+            CellBroadcasts.MESSAGE_FORMAT,
+            CellBroadcasts.MESSAGE_PRIORITY,
+            CellBroadcasts.ETWS_WARNING_TYPE,
+            CellBroadcasts.CMAS_MESSAGE_CLASS,
+            CellBroadcasts.CMAS_CATEGORY,
+            CellBroadcasts.CMAS_RESPONSE_TYPE,
+            CellBroadcasts.CMAS_SEVERITY,
+            CellBroadcasts.CMAS_URGENCY,
+            CellBroadcasts.CMAS_CERTAINTY,
+            CellBroadcasts.RECEIVED_TIME,
+            CellBroadcasts.MESSAGE_BROADCASTED,
+            CellBroadcasts.GEOMETRIES
+    };
+
+    private CellBroadcastProviderTestable mCellBroadcastProviderTestable;
+    private MockContextWithProvider mContext;
+    private MockContentResolver mContentResolver;
+
+    @Mock
+    private PermissionChecker mMockPermissionChecker;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        doReturn(true).when(mMockPermissionChecker).hasReadPermission();
+        doReturn(true).when(mMockPermissionChecker).hasWritePermission();
+
+        mCellBroadcastProviderTestable = new CellBroadcastProviderTestable(mMockPermissionChecker);
+        mContext = new MockContextWithProvider(mCellBroadcastProviderTestable);
+        mContentResolver = mContext.getContentResolver();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mCellBroadcastProviderTestable.closeDatabase();
+        super.tearDown();
+    }
+
+    @Test
+    public void testUpdate() {
+        // Insert a cellbroadcast to the database.
+        ContentValues cv = fakeCellBroadcast();
+        Uri uri = mContentResolver.insert(CONTENT_URI, cv);
+        assertThat(uri).isNotNull();
+
+        // Change some fields of this cell broadcast.
+        int messageBroadcasted = 1 - cv.getAsInteger(CellBroadcasts.MESSAGE_BROADCASTED);
+        int receivedTime = 1234555555;
+        cv.put(CellBroadcasts.MESSAGE_BROADCASTED, messageBroadcasted);
+        cv.put(CellBroadcasts.RECEIVED_TIME, receivedTime);
+        mContentResolver.update(CONTENT_URI, cv, SELECT_BY_ID,
+                new String[] { uri.getLastPathSegment() });
+
+        // Query and check if the update is successed.
+        Cursor cursor = mContentResolver.query(CONTENT_URI, QUERY_COLUMNS,
+                SELECT_BY_ID, new String[] { uri.getLastPathSegment() }, null /* orderBy */);
+        cursor.moveToNext();
+
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.RECEIVED_TIME)))
+                .isEqualTo(receivedTime);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_BROADCASTED)))
+                .isEqualTo(messageBroadcasted);
+        cursor.close();
+    }
+
+    @Test
+    public void testUpdate_WithoutWritePermission_fail() {
+        ContentValues cv = fakeCellBroadcast();
+        Uri uri = mContentResolver.insert(CONTENT_URI, cv);
+        assertThat(uri).isNotNull();
+
+        // Revoke the write permission
+        doReturn(false).when(mMockPermissionChecker).hasWritePermission();
+
+        try {
+            mContentResolver.update(CONTENT_URI, cv, SELECT_BY_ID,
+                    new String[] { uri.getLastPathSegment() });
+            fail();
+        } catch (SecurityException ex) {
+            // pass the test
+        }
+    }
+
+    @Test
+    public void testGetAllCellBroadcast() {
+        // Insert some cell broadcasts which message_broadcasted is false
+        int messageNotBroadcastedCount = 5;
+        ContentValues cv = fakeCellBroadcast();
+        cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 0);
+        for (int i = 0; i < messageNotBroadcastedCount; i++) {
+            mContentResolver.insert(CONTENT_URI, cv);
+        }
+
+        // Insert some cell broadcasts which message_broadcasted is true
+        int messageBroadcastedCount = 6;
+        cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
+        for (int i = 0; i < messageBroadcastedCount; i++) {
+            mContentResolver.insert(CONTENT_URI, cv);
+        }
+
+        // Query the broadcast with message_broadcasted is false
+        Cursor cursor = mContentResolver.query(
+                CONTENT_URI,
+                QUERY_COLUMNS,
+                String.format("%s=?", CellBroadcasts.MESSAGE_BROADCASTED), /* selection */
+                new String[] {"0"}, /* selectionArgs */
+                null /* sortOrder */);
+        assertThat(cursor.getCount()).isEqualTo(messageNotBroadcastedCount);
+    }
+
+    @Test
+    public void testDelete_withoutWritePermission_throwSecurityException() {
+        Uri uri = mContentResolver.insert(CONTENT_URI, fakeCellBroadcast());
+        assertThat(uri).isNotNull();
+
+        // Revoke the write permission
+        doReturn(false).when(mMockPermissionChecker).hasWritePermission();
+
+        try {
+            mContentResolver.delete(CONTENT_URI, SELECT_BY_ID,
+                    new String[] { uri.getLastPathSegment() });
+            fail();
+        } catch (SecurityException ex) {
+            // pass the test
+        }
+    }
+
+
+    @Test
+    public void testDelete_oneRecord_success() {
+        // Insert a cellbroadcast to the database.
+        ContentValues cv = fakeCellBroadcast();
+        Uri uri = mContentResolver.insert(CONTENT_URI, cv);
+        assertThat(uri).isNotNull();
+
+        String[] selectionArgs = new String[] { uri.getLastPathSegment() };
+
+        // Ensure the cell broadcast is inserted.
+        Cursor cursor = mContentResolver.query(CONTENT_URI, QUERY_COLUMNS,
+                SELECT_BY_ID, selectionArgs, null /* orderBy */);
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.close();
+
+        // Delete the cell broadcast
+        int rowCount = mContentResolver.delete(CONTENT_URI, SELECT_BY_ID,
+                selectionArgs);
+        assertThat(rowCount).isEqualTo(1);
+
+        // Ensure the cell broadcast is deleted.
+        cursor = mContentResolver.query(CONTENT_URI, QUERY_COLUMNS, SELECT_BY_ID,
+                selectionArgs, null /* orderBy */);
+        assertThat(cursor.getCount()).isEqualTo(0);
+        cursor.close();
+    }
+
+    @Test
+    public void testDelete_all_success() {
+        // Insert a cellbroadcast to the database.
+        mContentResolver.insert(CONTENT_URI, fakeCellBroadcast());
+        mContentResolver.insert(CONTENT_URI, fakeCellBroadcast());
+
+        // Ensure the cell broadcast are inserted.
+        Cursor cursor = mContentResolver.query(CONTENT_URI, QUERY_COLUMNS,
+                null /* selection */, null /* selectionArgs */, null /* orderBy */);
+        assertThat(cursor.getCount()).isEqualTo(2);
+        cursor.close();
+
+        // Delete all cell broadcasts.
+        int rowCount = mContentResolver.delete(
+                CONTENT_URI, null /* selection */, null /* selectionArgs */);
+        assertThat(rowCount).isEqualTo(2);
+        cursor.close();
+
+        // Ensure all cell broadcasts are deleted.
+        cursor = mContentResolver.query(CONTENT_URI, QUERY_COLUMNS,
+                null /* selection */, null /* selectionArgs */, null /* orderBy */);
+        assertThat(cursor.getCount()).isEqualTo(0);
+        cursor.close();
+    }
+
+    @Test
+    public void testInsert_withoutWritePermission_fail() {
+        doReturn(false).when(mMockPermissionChecker).hasWritePermission();
+
+        try {
+            mContentResolver.insert(CONTENT_URI, fakeCellBroadcast());
+            fail();
+        } catch (SecurityException ex) {
+            // pass the test
+        }
+    }
+
+    @Test
+    public void testInsertAndQuery() {
+        // Insert a cell broadcast message
+        Uri uri = mContentResolver.insert(CONTENT_URI, fakeCellBroadcast());
+
+        // Verify that the return uri is not null and the record is inserted into the database
+        // correctly.
+        assertThat(uri).isNotNull();
+        Cursor cursor = mContentResolver.query(
+                CONTENT_URI, QUERY_COLUMNS, SELECT_BY_ID,
+                new String[] { uri.getLastPathSegment() }, null /* orderBy */);
+        assertThat(cursor.getCount()).isEqualTo(1);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.GEOGRAPHICAL_SCOPE)))
+                .isEqualTo(GEO_SCOPE);
+        assertThat(cursor.getString(cursor.getColumnIndexOrThrow(CellBroadcasts.PLMN)))
+                .isEqualTo(PLMN);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.LAC))).isEqualTo(LAC);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.CID))).isEqualTo(CID);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERIAL_NUMBER)))
+                .isEqualTo(SERIAL_NUMBER);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERVICE_CATEGORY)))
+                .isEqualTo(SERVICE_CATEGORY);
+        assertThat(cursor.getString(cursor.getColumnIndexOrThrow(CellBroadcasts.LANGUAGE_CODE)))
+                .isEqualTo(LANGUAGE_CODE);
+        assertThat(cursor.getString(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_BODY)))
+                .isEqualTo(MESSAGE_BODY);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_FORMAT)))
+                .isEqualTo(MESSAGE_FORMAT);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_PRIORITY)))
+                .isEqualTo(MESSAGE_PRIORITY);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.ETWS_WARNING_TYPE)))
+                .isEqualTo(ETWS_WARNING_TYPE);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.CMAS_MESSAGE_CLASS)))
+                .isEqualTo(CMAS_MESSAGE_CLASS);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.CMAS_CATEGORY)))
+                .isEqualTo(CMAS_CATEGORY);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.CMAS_RESPONSE_TYPE)))
+                .isEqualTo(CMAS_RESPONSE_TYPE);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.CMAS_SEVERITY)))
+                .isEqualTo(CMAS_SEVERITY);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.CMAS_URGENCY)))
+                .isEqualTo(CMAS_URGENCY);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.CMAS_CERTAINTY)))
+                .isEqualTo(CMAS_CERTAINTY);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.RECEIVED_TIME)))
+                .isEqualTo(RECEIVED_TIME);
+        assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_BROADCASTED)))
+                .isEqualTo(MESSAGE_BROADCASTED);
+        assertThat(cursor.getString(cursor.getColumnIndexOrThrow(
+                CellBroadcasts.GEOMETRIES))).isEqualTo(GEOMETRIES_COORDINATES);
+    }
+
+    /**
+     * This is used to give the CellBroadcastProviderTest a mocked context which takes a
+     * CellBroadcastProvider and attaches it to the ContentResolver.
+     */
+    private class MockContextWithProvider extends MockContext {
+        private final MockContentResolver mResolver;
+
+        public MockContextWithProvider(CellBroadcastProviderTestable cellBroadcastProvider) {
+            mResolver = new MockContentResolver();
+            cellBroadcastProvider.initializeForTesting(this);
+
+            // Add given cellBroadcastProvider to mResolver, so that mResolver can send queries
+            // to the provider.
+            mResolver.addProvider(CellBroadcastProvider.AUTHORITY, cellBroadcastProvider);
+        }
+
+        @Override
+        public MockContentResolver getContentResolver() {
+            return mResolver;
+        }
+
+
+        @Override
+        public Object getSystemService(String name) {
+            Log.d(TAG, "getSystemService: returning null");
+            return null;
+        }
+
+        @Override
+        public int checkCallingOrSelfPermission(String permission) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+    }
+
+    private static ContentValues fakeCellBroadcast() {
+        ContentValues cv = new ContentValues();
+        cv.put(CellBroadcasts.GEOGRAPHICAL_SCOPE, GEO_SCOPE);
+        cv.put(CellBroadcasts.PLMN, PLMN);
+        cv.put(CellBroadcasts.LAC, LAC);
+        cv.put(CellBroadcasts.CID, CID);
+        cv.put(CellBroadcasts.SERIAL_NUMBER, SERIAL_NUMBER);
+        cv.put(CellBroadcasts.SERVICE_CATEGORY, SERVICE_CATEGORY);
+        cv.put(CellBroadcasts.LANGUAGE_CODE, LANGUAGE_CODE);
+        cv.put(CellBroadcasts.MESSAGE_BODY, MESSAGE_BODY);
+        cv.put(CellBroadcasts.MESSAGE_FORMAT, MESSAGE_FORMAT);
+        cv.put(CellBroadcasts.MESSAGE_PRIORITY, MESSAGE_PRIORITY);
+        cv.put(CellBroadcasts.ETWS_WARNING_TYPE, ETWS_WARNING_TYPE);
+        cv.put(CellBroadcasts.CMAS_MESSAGE_CLASS, CMAS_MESSAGE_CLASS);
+        cv.put(CellBroadcasts.CMAS_CATEGORY, CMAS_CATEGORY);
+        cv.put(CellBroadcasts.CMAS_RESPONSE_TYPE, CMAS_RESPONSE_TYPE);
+        cv.put(CellBroadcasts.CMAS_SEVERITY, CMAS_SEVERITY);
+        cv.put(CellBroadcasts.CMAS_URGENCY, CMAS_URGENCY);
+        cv.put(CellBroadcasts.CMAS_CERTAINTY, CMAS_CERTAINTY);
+        cv.put(CellBroadcasts.RECEIVED_TIME, RECEIVED_TIME);
+        cv.put(CellBroadcasts.MESSAGE_BROADCASTED, MESSAGE_BROADCASTED);
+        cv.put(CellBroadcasts.GEOMETRIES, GEOMETRIES_COORDINATES);
+        return cv;
+    }
+}
diff --git a/tests/src/com/android/providers/telephony/CellBroadcastProviderTestable.java b/tests/src/com/android/providers/telephony/CellBroadcastProviderTestable.java
new file mode 100644
index 0000000..8334312
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/CellBroadcastProviderTestable.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 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.content.pm.ProviderInfo;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+public class CellBroadcastProviderTestable extends CellBroadcastProvider {
+    private static final String TAG = CellBroadcastProviderTestable.class.getSimpleName();
+
+    public CellBroadcastProviderTestable(PermissionChecker permissionChecker) {
+        super(permissionChecker);
+    }
+
+    @Override
+    public boolean onCreate() {
+        // DO NOT call super.onCreate(), otherwise the permission checker will be override.
+        Log.d(TAG, "CellBroadcastProviderTestable onCreate");
+        mDbHelper = new InMemoryCellBroadcastProviderDbHelper();
+        return true;
+    }
+
+    public void closeDatabase() {
+        mDbHelper.close();
+    }
+
+    public static class InMemoryCellBroadcastProviderDbHelper extends SQLiteOpenHelper {
+        public InMemoryCellBroadcastProviderDbHelper() {
+            super(InstrumentationRegistry.getTargetContext(),
+                    null,    // db file name is null for in-memory db
+                    null,    // CursorFactory is null by default
+                    1);      // db version is no-op for tests
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            Log.d(TAG, "IN MEMORY DB CREATED");
+            db.execSQL(getStringForCellBroadcastTableCreation(CELL_BROADCASTS_TABLE_NAME));
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
+    }
+
+    public void initializeForTesting(Context context) {
+        ProviderInfo providerInfo = new ProviderInfo();
+        providerInfo.authority = CellBroadcastProvider.AUTHORITY;
+
+        // Add context to given carrierIdProvider
+        attachInfoForTesting(context, providerInfo);
+    }
+}
diff --git a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
index b996dc3..47c1dc6 100644
--- a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
@@ -152,9 +152,9 @@
 
             doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
             doReturn(TEST_OPERATOR).when(mTelephonyManager).getSimOperator();
-            doReturn(mIcRecords).when(mUiccController).getIccRecords(anyInt(),
-                    ArgumentMatchers.eq(UiccController.APP_FAM_3GPP));
+            doReturn(mIcRecords).when(mUiccController).getIccRecords(anyInt(), anyInt());
             doReturn(TEST_SPN).when(mIcRecords).getServiceProviderName();
+            doReturn(TEST_SPN).when(mIcRecords).getServiceProviderNameWithBrandOverride();
 
             // Add authority="telephony" to given telephonyProvider
             ProviderInfo providerInfo = new ProviderInfo();
diff --git a/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java b/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java
index 6c2371d..d9f6ee3 100644
--- a/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java
+++ b/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java
@@ -92,6 +92,7 @@
         Log.d(TAG, "getIccRecords called");
         IccRecords iccRecords = mock(IccRecords.class);
         doReturn(TEST_SPN).when(iccRecords).getServiceProviderName();
+        doReturn(TEST_SPN).when(iccRecords).getServiceProviderNameWithBrandOverride();
         return iccRecords;
     }