Add Cellbroadcast ContentProvider
This add a new ContentProvider that provides the access of cell
broadcast message to the application. The database scheme is copied from
the content provider in cell braodcast app, with some minor changes.
Bug: 123096618
Test: atest TelephonyProviderTests
Merged-In: If1b6c19d4416f24ca2cf92005039e27712ac622b
Change-Id: If1b6c19d4416f24ca2cf92005039e27712ac622b
(cherry picked from commit 3a32e7f97cc4761b87e2871f4cbd2b2d6a1c0a1e)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4a56852..10ea098 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -99,6 +99,13 @@
android:multiprocess="false"
android:writePermission="android.permission.MODIFY_PHONE_STATE" />
+ <provider android:name="CellBroadcastProvider"
+ android:authorities="cellbroadcasts"
+ android:exported="true"
+ android:singleUser="true"
+ android:multiprocess="false"
+ android:readPermission="android.permission.READ_CELL_BROADCASTS" />
+
<provider android:name="HbpcdLookupProvider"
android:authorities="hbpcd_lookup"
android:exported="true"
diff --git a/src/com/android/providers/telephony/CellBroadcastProvider.java b/src/com/android/providers/telephony/CellBroadcastProvider.java
new file mode 100644
index 0000000..3c6c51c
--- /dev/null
+++ b/src/com/android/providers/telephony/CellBroadcastProvider.java
@@ -0,0 +1,313 @@
+/*
+ * 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";
+
+ @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(CellBroadcasts.CONTENT_URI, row);
+ getContext().getContentResolver()
+ .notifyChange(CellBroadcasts.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.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);";
+ }
+
+ 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 for CellBroadcast content provider.
+ return Binder.getCallingUid() == Process.PHONE_UID;
+ }
+
+ @Override
+ public boolean hasReadPermission() {
+ return getContext().checkCallingOrSelfPermission(
+ "android.permission.READ_CELL_BROADCASTS") == PackageManager.PERMISSION_GRANTED;
+ }
+ }
+}
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..517572e
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/CellBroadcastProviderTest.java
@@ -0,0 +1,382 @@
+/*
+ * 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();
+
+ 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(CellBroadcasts.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(CellBroadcasts.CONTENT_URI, cv, SELECT_BY_ID,
+ new String[] { uri.getLastPathSegment() });
+
+ // Query and check if the update is successed.
+ Cursor cursor = mContentResolver.query(CellBroadcasts.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(CellBroadcasts.CONTENT_URI, cv);
+ assertThat(uri).isNotNull();
+
+ // Revoke the write permission
+ doReturn(false).when(mMockPermissionChecker).hasWritePermission();
+
+ try {
+ mContentResolver.update(CellBroadcasts.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(CellBroadcasts.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(CellBroadcasts.CONTENT_URI, cv);
+ }
+
+ // Query the broadcast with message_broadcasted is false
+ Cursor cursor = mContentResolver.query(
+ CellBroadcasts.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(CellBroadcasts.CONTENT_URI, fakeCellBroadcast());
+ assertThat(uri).isNotNull();
+
+ // Revoke the write permission
+ doReturn(false).when(mMockPermissionChecker).hasWritePermission();
+
+ try {
+ mContentResolver.delete(CellBroadcasts.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(CellBroadcasts.CONTENT_URI, cv);
+ assertThat(uri).isNotNull();
+
+ String[] selectionArgs = new String[] { uri.getLastPathSegment() };
+
+ // Ensure the cell broadcast is inserted.
+ Cursor cursor = mContentResolver.query(CellBroadcasts.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(CellBroadcasts.CONTENT_URI, SELECT_BY_ID,
+ selectionArgs);
+ assertThat(rowCount).isEqualTo(1);
+
+ // Ensure the cell broadcast is deleted.
+ cursor = mContentResolver.query(CellBroadcasts.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(CellBroadcasts.CONTENT_URI, fakeCellBroadcast());
+ mContentResolver.insert(CellBroadcasts.CONTENT_URI, fakeCellBroadcast());
+
+ // Ensure the cell broadcast are inserted.
+ Cursor cursor = mContentResolver.query(CellBroadcasts.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(
+ CellBroadcasts.CONTENT_URI, null /* selection */, null /* selectionArgs */);
+ assertThat(rowCount).isEqualTo(2);
+ cursor.close();
+
+ // Ensure all cell broadcasts are deleted.
+ cursor = mContentResolver.query(CellBroadcasts.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(CellBroadcasts.CONTENT_URI, fakeCellBroadcast());
+ fail();
+ } catch (SecurityException ex) {
+ // pass the test
+ }
+ }
+
+ @Test
+ public void testInsertAndQuery() {
+ // Insert a cell broadcast message
+ Uri uri = mContentResolver.insert(CellBroadcasts.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(
+ CellBroadcasts.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);
+ }
+}