Merge "Add DPC, FILTERED and ENFORCE_MANAGED URI in TelephonyProvider."
diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index b720c0b..b1af883 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -76,6 +76,7 @@
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
+import android.database.MatrixCursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
@@ -86,6 +87,7 @@
import android.os.Environment;
import android.os.FileUtils;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -140,6 +142,11 @@
private static final int URL_SIMINFO_USING_SUBID = 13;
private static final int URL_UPDATE_DB = 14;
private static final int URL_DELETE = 15;
+ private static final int URL_DPC = 16;
+ private static final int URL_DPC_ID = 17;
+ private static final int URL_FILTERED = 18;
+ private static final int URL_ENFORCE_MANAGED = 19;
+
private static final String TAG = "TelephonyProvider";
private static final String CARRIERS_TABLE = "carriers";
@@ -156,6 +163,9 @@
private static final String BUILD_ID_FILE = "build-id";
private static final String RO_BUILD_ID = "ro_build_id";
+ private static final String ENFORCED_FILE = "dpc-apn-enforced";
+ private static final String ENFORCED_KEY = "enforced";
+
private static final String PREF_FILE = "telephonyprovider";
private static final String APN_CONF_CHECKSUM = "apn_conf_checksum";
@@ -190,7 +200,8 @@
EDITED + "=" + CARRIER_DELETED_BUT_PRESENT_IN_XML;
private static final String IS_NOT_CARRIER_DELETED_BUT_PRESENT_IN_XML =
EDITED + "!=" + CARRIER_DELETED_BUT_PRESENT_IN_XML;
- private static final String NOT_OWNED_BY_DPC = OWNED_BY + "!=" + OWNED_BY_DPC;
+ private static final String IS_OWNED_BY_DPC = OWNED_BY + "=" + OWNED_BY_DPC;
+ private static final String IS_NOT_OWNED_BY_DPC = OWNED_BY + "!=" + OWNED_BY_DPC;
private static final int INVALID_APN_ID = -1;
private static final List<String> CARRIERS_UNIQUE_FIELDS = new ArrayList<String>();
@@ -201,6 +212,9 @@
protected final Object mLock = new Object();
@GuardedBy("mLock")
private IApnSourceService mIApnSourceService;
+ private Injector mInjector;
+
+ private boolean mManagedApnEnforced;
static {
// Columns not included in UNIQUE constraint: name, current, edited, user, server, password,
@@ -333,6 +347,16 @@
s_urlMatcher.addURI("telephony", "carriers/update_db", URL_UPDATE_DB);
s_urlMatcher.addURI("telephony", "carriers/delete", URL_DELETE);
+ // Only called by DevicePolicyManager to manipulate DPC records.
+ s_urlMatcher.addURI("telephony", "carriers/dpc", URL_DPC);
+ // Only called by DevicePolicyManager to manipulate a DPC record with certain _ID.
+ s_urlMatcher.addURI("telephony", "carriers/dpc/#", URL_DPC_ID);
+ // Only called by Settings app, DcTracker and other telephony components to get APN list
+ // according to whether DPC records are enforced.
+ s_urlMatcher.addURI("telephony", "carriers/filtered", URL_FILTERED);
+ // Only Called by DevicePolicyManager to enforce DPC records.
+ s_urlMatcher.addURI("telephony", "carriers/enforce_managed", URL_ENFORCE_MANAGED);
+
s_currentNullMap = new ContentValues(1);
s_currentNullMap.put(CURRENT, "0");
@@ -340,6 +364,25 @@
s_currentSetMap.put(CURRENT, "1");
}
+ /**
+ * Unit test will subclass it to inject mocks.
+ */
+ @VisibleForTesting
+ static class Injector {
+ int binderGetCallingUid() {
+ return Binder.getCallingUid();
+ }
+ }
+
+ public TelephonyProvider() {
+ this(new Injector());
+ }
+
+ @VisibleForTesting
+ public TelephonyProvider(Injector injector) {
+ mInjector = injector;
+ }
+
private static class DatabaseHelper extends SQLiteOpenHelper {
// Context to access resources with
private Context mContext;
@@ -1834,10 +1877,30 @@
}
}
+ SharedPreferences sp = getContext().getSharedPreferences(ENFORCED_FILE,
+ Context.MODE_PRIVATE);
+ mManagedApnEnforced = sp.getBoolean(ENFORCED_KEY, false);
+
if (VDBG) log("onCreate:- ret true");
+
return true;
}
+ private synchronized boolean isManagedApnEnforced() {
+ return mManagedApnEnforced;
+ }
+
+ private void setManagedApnEnforced(boolean enforced) {
+ SharedPreferences sp = getContext().getSharedPreferences(ENFORCED_FILE,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putBoolean(ENFORCED_KEY, enforced);
+ editor.apply();
+ synchronized (this) {
+ mManagedApnEnforced = enforced;
+ }
+ }
+
private void setPreferredApnId(Long id, int subId, boolean saveApn) {
SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_APN,
Context.MODE_PRIVATE);
@@ -1976,6 +2039,16 @@
}
}
+ boolean isCallingFromSystemUid() {
+ return mInjector.binderGetCallingUid() == Process.SYSTEM_UID;
+ }
+
+ void ensureCallingFromSystemUid(String message) {
+ if (!isCallingFromSystemUid()) {
+ throw new SecurityException(message);
+ }
+ }
+
@Override
public synchronized Cursor query(Uri url, String[] projectionIn, String selection,
String[] selectionArgs, String sort) {
@@ -2007,7 +2080,7 @@
}
// intentional fall through from above case
case URL_TELEPHONY: {
- constraints.add(NOT_OWNED_BY_DPC);
+ constraints.add(IS_NOT_OWNED_BY_DPC);
break;
}
@@ -2025,7 +2098,7 @@
//intentional fall through from above case
case URL_CURRENT: {
constraints.add("current IS NOT NULL");
- constraints.add(NOT_OWNED_BY_DPC);
+ constraints.add(IS_NOT_OWNED_BY_DPC);
// do not ignore the selection since MMS may use it.
//selection = null;
break;
@@ -2033,7 +2106,7 @@
case URL_ID: {
constraints.add("_id = " + url.getPathSegments().get(1));
- constraints.add(NOT_OWNED_BY_DPC);
+ constraints.add(IS_NOT_OWNED_BY_DPC);
break;
}
@@ -2056,6 +2129,31 @@
break;
}
+ case URL_DPC: {
+ ensureCallingFromSystemUid("URL_DPC called from non SYSTEM_UID.");
+ // DPC query only returns DPC records.
+ constraints.add(IS_OWNED_BY_DPC);
+ break;
+ }
+
+ case URL_FILTERED: {
+ if(isManagedApnEnforced()) {
+ // If enforced, return DPC records only.
+ constraints.add(IS_OWNED_BY_DPC);
+ } else {
+ // Otherwise return non-DPC records only.
+ constraints.add(IS_NOT_OWNED_BY_DPC);
+ }
+ break;
+ }
+
+ case URL_ENFORCE_MANAGED: {
+ ensureCallingFromSystemUid("URL_ENFORCE_MANAGED called from non SYSTEM_UID.");
+ MatrixCursor cursor = new MatrixCursor(new String[]{ENFORCED_KEY});
+ cursor.addRow(new Object[]{isManagedApnEnforced() ? 1 : 0});
+ return cursor;
+ }
+
case URL_SIMINFO: {
qb.setTables(SIMINFO_TABLE);
break;
@@ -2179,6 +2277,45 @@
return rowAndNotify.first;
}
+ /**
+ * Internal insert function to prevent code duplication for URL_TELEPHONY and URL_DPC.
+ *
+ * @param values the value that caller wants to insert
+ * @return a pair in which the first element refers to the Uri for the row inserted, the second
+ * element refers to whether sends out nofitication.
+ */
+ private Pair<Uri, Boolean> insertRowWithValue(ContentValues values) {
+ Uri result = null;
+ boolean notify = false;
+ SQLiteDatabase db = getWritableDatabase();
+
+ try {
+ // Replace on conflict so that if same APN is present in db with edited
+ // as UNEDITED or USER/CARRIER_DELETED, it is replaced with
+ // edited USER/CARRIER_EDITED
+ long rowID = db.insertWithOnConflict(CARRIERS_TABLE, null, values,
+ SQLiteDatabase.CONFLICT_ABORT);
+ if (rowID >= 0) {
+ result = ContentUris.withAppendedId(CONTENT_URI, rowID);
+ notify = true;
+ }
+ if (VDBG) log("insert: inserted " + values.toString() + " rowID = " + rowID);
+ } catch (SQLException e) {
+ log("insert: exception " + e);
+ // Insertion failed which could be due to a conflict. Check if that is the case
+ // and merge the entries
+ Cursor oldRow = DatabaseHelper.selectConflictingRow(db, CARRIERS_TABLE, values);
+ if (oldRow != null) {
+ ContentValues mergedValues = new ContentValues();
+ DatabaseHelper.mergeFieldsAndUpdateDb(db, CARRIERS_TABLE, oldRow, values,
+ mergedValues, false, getContext());
+ oldRow.close();
+ notify = true;
+ }
+ }
+ return Pair.create(result, notify);
+ }
+
private Pair<Uri, Boolean> insertSingleRow(Uri url, ContentValues initialValues) {
Uri result = null;
int subId = SubscriptionManager.getDefaultSubscriptionId();
@@ -2213,40 +2350,16 @@
}
values.put(SUBSCRIPTION_ID, subId);
-
values = DatabaseHelper.setDefaultValue(values);
if (!values.containsKey(EDITED)) {
values.put(EDITED, USER_EDITED);
}
-
// Owned_by should be others if inserted via general uri.
values.put(OWNED_BY, OWNED_BY_OTHERS);
- try {
- // Replace on conflict so that if same APN is present in db with edited
- // as UNEDITED or USER/CARRIER_DELETED, it is replaced with
- // edited USER/CARRIER_EDITED
- long rowID = db.insertWithOnConflict(CARRIERS_TABLE, null, values,
- SQLiteDatabase.CONFLICT_REPLACE);
- if (rowID >= 0) {
- result = ContentUris.withAppendedId(CONTENT_URI, rowID);
- notify = true;
- }
- if (VDBG) log("insert: inserted " + values.toString() + " rowID = " + rowID);
- } catch (SQLException e) {
- log("insert: exception " + e);
- // Insertion failed which could be due to a conflict. Check if that is the case
- // and merge the entries
- Cursor oldRow = DatabaseHelper.selectConflictingRow(db, CARRIERS_TABLE, values);
- if (oldRow != null) {
- ContentValues mergedValues = new ContentValues();
- DatabaseHelper.mergeFieldsAndUpdateDb(db, CARRIERS_TABLE, oldRow, values,
- mergedValues, false, getContext());
- oldRow.close();
- notify = true;
- }
- }
-
+ Pair<Uri, Boolean> ret = insertRowWithValue(values);
+ result = ret.first;
+ notify = ret.second;
break;
}
@@ -2309,6 +2422,26 @@
break;
}
+ case URL_DPC: {
+ ensureCallingFromSystemUid("URL_DPC called from non SYSTEM_UID.");
+
+ ContentValues values;
+ if (initialValues != null) {
+ values = new ContentValues(initialValues);
+ } else {
+ values = new ContentValues();
+ }
+
+ // Owned_by should be DPC if inserted via URL_DPC.
+ values.put(OWNED_BY, OWNED_BY_DPC);
+ // DPC records should not be user editable.
+ values.put(USER_EDITABLE, false);
+ Pair<Uri, Boolean> ret = insertRowWithValue(values);
+ result = ret.first;
+ notify = ret.second;
+ break;
+ }
+
case URL_SIMINFO: {
long id = db.insert(SIMINFO_TABLE, null, initialValues);
result = ContentUris.withAppendedId(SubscriptionManager.CONTENT_URI, id);
@@ -2320,8 +2453,7 @@
}
@Override
- public synchronized int delete(Uri url, String where, String[] whereArgs)
- {
+ public synchronized int delete(Uri url, String where, String[] whereArgs) {
int count = 0;
int subId = SubscriptionManager.getDefaultSubscriptionId();
String userOrCarrierEdited = ") and (" +
@@ -2346,7 +2478,7 @@
deletePreferredApnId();
// Delete unedited entries
count = db.delete(CARRIERS_TABLE, "(" + where + unedited + " and " +
- NOT_OWNED_BY_DPC, whereArgs);
+ IS_NOT_OWNED_BY_DPC, whereArgs);
break;
}
@@ -2368,10 +2500,10 @@
{
// Delete user/carrier edited entries
count = db.delete(CARRIERS_TABLE, "(" + where + userOrCarrierEdited
- + " and " + NOT_OWNED_BY_DPC, whereArgs);
+ + " and " + IS_NOT_OWNED_BY_DPC, whereArgs);
// Otherwise mark as user deleted instead of deleting
count += db.update(CARRIERS_TABLE, cv, "(" + where +
- notUserOrCarrierEdited + " and " + NOT_OWNED_BY_DPC, whereArgs);
+ notUserOrCarrierEdited + " and " + IS_NOT_OWNED_BY_DPC, whereArgs);
break;
}
@@ -2392,10 +2524,10 @@
{
// Delete user/carrier edited entries
count = db.delete(CARRIERS_TABLE, "(" + where + userOrCarrierEdited
- + " and " + NOT_OWNED_BY_DPC, whereArgs);
+ + " and " + IS_NOT_OWNED_BY_DPC, whereArgs);
// Otherwise mark as user deleted instead of deleting
count += db.update(CARRIERS_TABLE, cv, "(" + where +
- notUserOrCarrierEdited + " and " + NOT_OWNED_BY_DPC, whereArgs);
+ notUserOrCarrierEdited + " and " + IS_NOT_OWNED_BY_DPC, whereArgs);
break;
}
@@ -2404,12 +2536,12 @@
// Delete user/carrier edited entries
count = db.delete(CARRIERS_TABLE,
"(" + _ID + "=?" + userOrCarrierEdited +
- " and " + NOT_OWNED_BY_DPC,
+ " and " + IS_NOT_OWNED_BY_DPC,
new String[] { url.getLastPathSegment() });
// Otherwise mark as user deleted instead of deleting
count += db.update(CARRIERS_TABLE, cv,
"(" + _ID + "=?" + notUserOrCarrierEdited +
- " and " + NOT_OWNED_BY_DPC,
+ " and " + IS_NOT_OWNED_BY_DPC,
new String[]{url.getLastPathSegment() });
break;
}
@@ -2452,6 +2584,15 @@
break;
}
+ case URL_DPC_ID: {
+ ensureCallingFromSystemUid("URL_DPC_ID called from non SYSTEM_UID.");
+
+ // Only delete if owned by DPC.
+ count = db.delete(CARRIERS_TABLE, "(" + _ID + "=?)" + " and " + IS_OWNED_BY_DPC,
+ new String[] { url.getLastPathSegment() });
+ break;
+ }
+
case URL_SIMINFO: {
count = db.delete(SIMINFO_TABLE, where, whereArgs);
break;
@@ -2513,7 +2654,7 @@
// as UNEDITED or USER/CARRIER_DELETED, it is replaced with
// edited USER/CARRIER_EDITED
count = db.updateWithOnConflict(CARRIERS_TABLE, values, where +
- " and " + NOT_OWNED_BY_DPC, whereArgs,
+ " and " + IS_NOT_OWNED_BY_DPC, whereArgs,
SQLiteDatabase.CONFLICT_REPLACE);
break;
}
@@ -2541,7 +2682,7 @@
// as UNEDITED or USER/CARRIER_DELETED, it is replaced with
// edited USER/CARRIER_EDITED
count = db.updateWithOnConflict(CARRIERS_TABLE, values, where +
- " and " + NOT_OWNED_BY_DPC,
+ " and " + IS_NOT_OWNED_BY_DPC,
whereArgs, SQLiteDatabase.CONFLICT_REPLACE);
break;
}
@@ -2555,15 +2696,10 @@
if (!values.containsKey(EDITED)) {
values.put(EDITED, USER_EDITED);
}
- // Replace on conflict so that if same APN is present in db with edited
- // as UNEDITED or USER/CARRIER_DELETED, it is replaced with
- // edited USER/CARRIER_EDITED
- count = db.updateWithOnConflict(CARRIERS_TABLE, values,
- _ID + "=?" + " and " + NOT_OWNED_BY_DPC,
- new String[] { url.getLastPathSegment() }, SQLiteDatabase.CONFLICT_REPLACE);
+
try {
count = db.updateWithOnConflict(CARRIERS_TABLE, values,
- _ID + "=?" + " and " + NOT_OWNED_BY_DPC,
+ _ID + "=?" + " and " + IS_NOT_OWNED_BY_DPC,
new String[] { url.getLastPathSegment() }, SQLiteDatabase.CONFLICT_ABORT);
} catch (SQLException e) {
// Update failed which could be due to a conflict. Check if that is
@@ -2607,6 +2743,31 @@
break;
}
+ case URL_DPC_ID:
+ {
+ ensureCallingFromSystemUid("URL_DPC_ID called from non SYSTEM_UID.");
+
+ if (where != null || whereArgs != null) {
+ throw new UnsupportedOperationException(
+ "Cannot update URL " + url + " with a where clause");
+ }
+ count = db.updateWithOnConflict(CARRIERS_TABLE, values,
+ _ID + "=?" + " and " + IS_OWNED_BY_DPC,
+ new String[] { url.getLastPathSegment() }, SQLiteDatabase.CONFLICT_REPLACE);
+ break;
+ }
+
+ case URL_ENFORCE_MANAGED: {
+ ensureCallingFromSystemUid("URL_ENFORCE_MANAGED called from non SYSTEM_UID.");
+ if (values != null) {
+ if (values.containsKey(ENFORCED_KEY)) {
+ setManagedApnEnforced(values.getAsBoolean(ENFORCED_KEY));
+ count = 1;
+ }
+ }
+ break;
+ }
+
case URL_SIMINFO: {
count = db.update(SIMINFO_TABLE, values, where, whereArgs);
uriType = URL_SIMINFO;
@@ -2660,7 +2821,7 @@
SQLiteDatabase db = getWritableDatabase();
try {
- db.delete(CARRIERS_TABLE, NOT_OWNED_BY_DPC, null);
+ db.delete(CARRIERS_TABLE, IS_NOT_OWNED_BY_DPC, null);
} catch (SQLException e) {
loge("got exception when deleting to restore: " + e);
}
@@ -2705,7 +2866,7 @@
// Delete entries in db
try {
if (VDBG) log("updateApnDb: deleting edited=UNEDITED entries");
- db.delete(CARRIERS_TABLE, IS_UNEDITED + " and " + NOT_OWNED_BY_DPC, null);
+ db.delete(CARRIERS_TABLE, IS_UNEDITED + " and " + IS_NOT_OWNED_BY_DPC, null);
} catch (SQLException e) {
loge("got exception when deleting to update: " + e);
}
diff --git a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
index 74dfdf9..b935a31 100644
--- a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
@@ -24,6 +24,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.res.Resources;
+import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.ContentObserver;
import android.database.DatabaseErrorHandler;
@@ -33,6 +34,7 @@
import android.net.Uri;
import android.os.Build;
import android.os.FileUtils;
+import android.os.Process;
import android.provider.Telephony.Carriers;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -55,6 +57,10 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.Set;
/**
@@ -84,6 +90,12 @@
private static final Uri CONTENT_URI_WITH_SUBID = Uri.parse(
"content://telephony/carriers/subId/" + TEST_SUBID);
+ // Constants for DPC related tests.
+ private static final Uri URI_DPC = Uri.parse("content://telephony/carriers/dpc");
+ private static final Uri URI_TELEPHONY = Carriers.CONTENT_URI;
+ private static final Uri URI_FILTERED = Uri.parse("content://telephony/carriers/filtered");
+ private static final Uri URI_ENFORCE_MANAGED= Uri.parse("content://telephony/carriers/enforce_managed");
+ private static final String ENFORCED_KEY = "enforced";
/**
* This is used to give the TelephonyProviderTest a mocked context which takes a
@@ -92,6 +104,8 @@
*/
private class MockContextWithProvider extends MockContext {
private final MockContentResolver mResolver;
+ private final SharedPreferences mSharedPreferences = mock(SharedPreferences.class);
+ private final SharedPreferences.Editor mEditor = mock(SharedPreferences.Editor.class);
private TelephonyManager mTelephonyManager = mock(TelephonyManager.class);
public MockContextWithProvider(TelephonyProvider telephonyProvider) {
@@ -119,6 +133,8 @@
// mResolver can send queries to mTelephonyProvider
mResolver.addProvider("telephony", telephonyProvider);
Log.d(TAG, "MockContextWithProvider: Add telephonyProvider to mResolver");
+
+ when(mSharedPreferences.edit()).thenReturn(mEditor);
}
@Override
@@ -143,6 +159,11 @@
return mResolver;
}
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ return mSharedPreferences;
+ }
+
// Gives permission to write to the APN table within the MockContext
@Override
public int checkCallingOrSelfPermission(String permission) {
@@ -419,4 +440,379 @@
testProjection, selection, selectionArgs, null);
assertEquals(0, cursor.getCount());
}
+
+ private int parseIdFromInsertedUri(Uri uri) {
+ int id = 0;
+ if (uri != null) {
+ try {
+ id = Integer.parseInt(uri.getLastPathSegment());
+ }
+ catch (NumberFormatException e) {
+ }
+ }
+ assertTrue("Can't parse ID for inserted APN", id != 0);
+ return id;
+ }
+
+ private int insertApnRecord(Uri uri, String apn, String name, int current, String numeric) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(Carriers.APN, apn);
+ contentValues.put(Carriers.NAME, name);
+ contentValues.put(Carriers.CURRENT, current);
+ contentValues.put(Carriers.NUMERIC, numeric);
+ Uri resultUri = mContentResolver.insert(uri, contentValues);
+ return parseIdFromInsertedUri(resultUri);
+ }
+
+ /**
+ * Test URL_ENFORCE_MANAGED and URL_FILTERED works correctly.
+ * Verify that when enforce is set true via URL_ENFORCE_MANAGED, only DPC records are returned
+ * for URL_FILTERED.
+ * Verify that when enforce is set false via URL_ENFORCE_MANAGED, only non-DPC records
+ * are returned for URL_FILTERED.
+ */
+ @Test
+ @SmallTest
+ public void testEnforceManagedUri() {
+ mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
+
+ final int current = 1;
+ final String numeric = "123456789";
+
+ // Insert DPC record.
+ final String dpcRecordApn = "exampleApnNameDPC";
+ final String dpcRecordName = "exampleNameDPC";
+ int dpcRecordId = insertApnRecord(URI_DPC, dpcRecordApn, dpcRecordName,
+ current, numeric);
+
+ // Insert non-DPC record.
+ final String othersRecordApn = "exampleApnNameOTHERS";
+ final String othersRecordName = "exampleNameDPOTHERS";
+ int othersRecordId = insertApnRecord(URI_TELEPHONY, othersRecordApn, othersRecordName,
+ current, numeric);
+
+ // Set enforced = false.
+ ContentValues enforceManagedValue = new ContentValues();
+ enforceManagedValue.put(ENFORCED_KEY, false);
+ Log.d(TAG, "testEnforceManagedUri Updating enforced = false: "
+ + enforceManagedValue);
+ mContentResolver.update(URI_ENFORCE_MANAGED, enforceManagedValue, "", new String[]{});
+
+ // Verify that enforced is set to false in TelephonyProvider.
+ Cursor enforceCursor = mContentResolver.query(URI_ENFORCE_MANAGED,
+ null, null, null, null);
+ assertNotNull(enforceCursor);
+ assertEquals(1, enforceCursor.getCount());
+ enforceCursor.moveToFirst();
+ assertEquals(0, enforceCursor.getInt(0));
+
+ // Verify URL_FILTERED query only returns non-DPC record.
+ final String[] testProjection =
+ {
+ Carriers._ID,
+ Carriers.OWNED_BY
+ };
+ final String selection = Carriers.NUMERIC + "=?";
+ String[] selectionArgs = { numeric };
+ Cursor cursorNotEnforced = mContentResolver.query(URI_FILTERED,
+ testProjection, selection, selectionArgs, null);
+ assertNotNull(cursorNotEnforced);
+ assertEquals(1, cursorNotEnforced.getCount());
+ cursorNotEnforced.moveToFirst();
+ assertEquals(othersRecordId, cursorNotEnforced.getInt(0));
+ assertEquals(Carriers.OWNED_BY_OTHERS, cursorNotEnforced.getInt(1));
+
+ // Set enforced = true.
+ enforceManagedValue.put(ENFORCED_KEY, true);
+ Log.d(TAG, "testEnforceManagedUri Updating enforced = true: "
+ + enforceManagedValue);
+ mContentResolver.update(URI_ENFORCE_MANAGED, enforceManagedValue, "", new String[]{});
+
+ // Verify that enforced is set to true in TelephonyProvider.
+ enforceCursor = mContentResolver.query(URI_ENFORCE_MANAGED,
+ null, null, null, null);
+ assertNotNull(enforceCursor);
+ assertEquals(1, enforceCursor.getCount());
+ enforceCursor.moveToFirst();
+ assertEquals(1, enforceCursor.getInt(0));
+
+ // Verify URL_FILTERED query only returns DPC record.
+ Cursor cursorEnforced = mContentResolver.query(URI_FILTERED,
+ testProjection, selection, selectionArgs, null);
+ assertNotNull(cursorEnforced);
+ assertEquals(1, cursorEnforced.getCount());
+ cursorEnforced.moveToFirst();
+ assertEquals(dpcRecordId, cursorEnforced.getInt(0));
+ assertEquals(Carriers.OWNED_BY_DPC, cursorEnforced.getInt(1));
+
+ // Delete testing records.
+ int numRowsDeleted = mContentResolver.delete(URI_TELEPHONY, selection, selectionArgs);
+ assertEquals(1, numRowsDeleted);
+
+ numRowsDeleted = mContentResolver.delete(
+ Uri.parse(URI_DPC + "/" + dpcRecordId),
+ "", new String[]{});
+ assertEquals(1, numRowsDeleted);
+ }
+
+ @Test
+ @SmallTest
+ /**
+ * Test URL_TELEPHONY cannot insert, query, update or delete DPC records.
+ */
+ public void testTelephonyUriDPCRecordAccessControl() {
+ mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
+
+ final int current = 1;
+ final String numeric = "123456789";
+
+ // Insert DPC record.
+ final String dpcRecordApn = "exampleApnNameDPC";
+ final String dpcRecordName = "exampleNameDPC";
+ int dpcRecordId = insertApnRecord(URI_DPC, dpcRecordApn, dpcRecordName,
+ current, numeric);
+
+ // Insert non-DPC record.
+ final String othersRecordApn = "exampleApnNameOTHERS";
+ final String othersRecordName = "exampleNameDPOTHERS";
+ int othersRecordId = insertApnRecord(URI_TELEPHONY, othersRecordApn, othersRecordName,
+ current, numeric);
+
+ // Verify URL_TELEPHONY query only returns non-DPC record.
+ final String[] testProjection =
+ {
+ Carriers._ID,
+ Carriers.APN,
+ Carriers.NAME,
+ Carriers.CURRENT,
+ Carriers.OWNED_BY,
+ };
+ final String selection = Carriers.NUMERIC + "=?";
+ String[] selectionArgs = { numeric };
+ Cursor cursorTelephony = mContentResolver.query(URI_TELEPHONY,
+ testProjection, selection, selectionArgs, null);
+ assertNotNull(cursorTelephony);
+ assertEquals(1, cursorTelephony.getCount());
+ cursorTelephony.moveToFirst();
+ assertEquals(othersRecordId, cursorTelephony.getInt(0));
+ assertEquals(othersRecordApn, cursorTelephony.getString(1));
+ assertEquals(othersRecordName, cursorTelephony.getString(2));
+ assertEquals(current, cursorTelephony.getInt(3));
+ assertEquals(Carriers.OWNED_BY_OTHERS, cursorTelephony.getInt(4));
+
+ // Verify URI_TELEPHONY updates only non-DPC records.
+ ContentValues contentValuesOthersUpdate = new ContentValues();
+ final String othersRecordUpdatedApn = "exampleApnNameOTHERSUpdated";
+ final String othersRecordUpdatedName = "exampleNameOTHERSpdated";
+ contentValuesOthersUpdate.put(Carriers.APN, othersRecordUpdatedApn);
+ contentValuesOthersUpdate.put(Carriers.NAME, othersRecordUpdatedName);
+ final int updateCount = mContentResolver.update(URI_TELEPHONY, contentValuesOthersUpdate,
+ selection, selectionArgs);
+ assertEquals(1, updateCount);
+ Cursor cursorNonDPCUpdate = mContentResolver.query(URI_TELEPHONY,
+ testProjection, selection, selectionArgs, null);
+ Cursor cursorDPCUpdate = mContentResolver.query(URI_DPC,
+ testProjection, selection, selectionArgs, null);
+
+ // Verify that non-DPC records are updated.
+ assertNotNull(cursorNonDPCUpdate);
+ assertEquals(1, cursorNonDPCUpdate.getCount());
+ cursorNonDPCUpdate.moveToFirst();
+ assertEquals(othersRecordId, cursorNonDPCUpdate.getInt(0));
+ assertEquals(othersRecordUpdatedApn, cursorNonDPCUpdate.getString(1));
+ assertEquals(othersRecordUpdatedName, cursorNonDPCUpdate.getString(2));
+
+ // Verify that DPC records are not updated.
+ assertNotNull(cursorDPCUpdate);
+ assertEquals(1, cursorDPCUpdate.getCount());
+ cursorDPCUpdate.moveToFirst();
+ assertEquals(dpcRecordId, cursorDPCUpdate.getInt(0));
+ assertEquals(dpcRecordApn, cursorDPCUpdate.getString(1));
+ assertEquals(dpcRecordName, cursorDPCUpdate.getString(2));
+
+ // Verify URI_TELEPHONY deletes only non-DPC records.
+ int numRowsDeleted = mContentResolver.delete(URI_TELEPHONY, selection, selectionArgs);
+ assertEquals(1, numRowsDeleted);
+ Cursor cursorTelephonyRemaining = mContentResolver.query(URI_TELEPHONY,
+ testProjection, selection, selectionArgs, null);
+ assertNotNull(cursorTelephonyRemaining);
+ assertEquals(0, cursorTelephonyRemaining.getCount());
+ Cursor cursorDPCDeleted = mContentResolver.query(URI_DPC,
+ testProjection, selection, selectionArgs, null);
+ assertNotNull(cursorDPCDeleted);
+ assertEquals(1, cursorDPCDeleted.getCount());
+
+ // Delete remaining test records.
+ numRowsDeleted = mContentResolver.delete(
+ Uri.parse(URI_DPC + "/" + dpcRecordId), "", new String[]{});
+ assertEquals(1, numRowsDeleted);
+ }
+
+ /**
+ * Test URL_DPC cannot insert or query non-DPC records.
+ * Test URL_DPC_ID cannot update or delete non-DPC records.
+ */
+ @Test
+ @SmallTest
+ public void testDpcUri() {
+ mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
+
+ final int current = 1;
+ final String numeric = "123456789";
+
+ // Insert DPC record.
+ final String dpcRecordApn = "exampleApnNameDPC";
+ final String dpcRecordName = "exampleNameDPC";
+ int dpcRecordId = insertApnRecord(URI_DPC, dpcRecordApn, dpcRecordName,
+ current, numeric);
+
+ // Insert non-DPC record.
+ final String othersRecordApn = "exampleApnNameOTHERS";
+ final String othersRecordName = "exampleNameDPOTHERS";
+ int othersRecordId = insertApnRecord(URI_TELEPHONY, othersRecordApn, othersRecordName,
+ current, numeric);
+
+ Log.d(TAG, "testDPCIdUri Id for inserted DPC record: " + dpcRecordId);
+ Log.d(TAG, "testDPCIdUri Id for inserted non-DPC record: " + othersRecordId);
+
+ // Verify that URI_DPC query only returns DPC records.
+ // The columns to get in table.
+ final String[] testProjection =
+ {
+ Carriers._ID,
+ Carriers.APN,
+ Carriers.NAME,
+ Carriers.CURRENT,
+ Carriers.OWNED_BY,
+ };
+ final String selection = Carriers.NUMERIC + "=?";
+ String[] selectionArgs = { numeric };
+ Cursor cursorDPC = mContentResolver.query(URI_DPC,
+ testProjection, selection, selectionArgs, null);
+
+ // Verify that DPC query returns only DPC records.
+ assertNotNull(cursorDPC);
+ assertEquals(1, cursorDPC.getCount());
+ cursorDPC.moveToFirst();
+ assertEquals(dpcRecordId, cursorDPC.getInt(0));
+ assertEquals(dpcRecordApn, cursorDPC.getString(1));
+ assertEquals(dpcRecordName, cursorDPC.getString(2));
+ assertEquals(current, cursorDPC.getInt(3));
+ assertEquals(Carriers.OWNED_BY_DPC, cursorDPC.getInt(4));
+
+ // Verify that URI_DPC_ID updates only DPC records.
+ ContentValues contentValuesDpcUpdate = new ContentValues();
+ final String dpcRecordUpdatedApn = "exampleApnNameDPCUpdated";
+ final String dpcRecordUpdatedName = "exampleNameDPCUpdated";
+ contentValuesDpcUpdate.put(Carriers.APN, dpcRecordUpdatedApn);
+ contentValuesDpcUpdate.put(Carriers.NAME, dpcRecordUpdatedName);
+ final int updateCount = mContentResolver.update(
+ Uri.parse(URI_DPC + "/" + dpcRecordId),
+ contentValuesDpcUpdate, null, null);
+ assertEquals(1, updateCount);
+ Cursor cursorNonDPCUpdate = mContentResolver.query(URI_TELEPHONY,
+ testProjection, selection, selectionArgs, null);
+ Cursor cursorDPCUpdate = mContentResolver.query(URI_DPC,
+ testProjection, selection, selectionArgs, null);
+
+ // Verify that non-DPC records are not updated.
+ assertNotNull(cursorNonDPCUpdate);
+ assertEquals(1, cursorNonDPCUpdate.getCount());
+ cursorNonDPCUpdate.moveToFirst();
+ assertEquals(othersRecordId, cursorNonDPCUpdate.getInt(0));
+ assertEquals(othersRecordApn, cursorNonDPCUpdate.getString(1));
+ assertEquals(othersRecordName, cursorNonDPCUpdate.getString(2));
+
+ // Verify that DPC records are updated.
+ assertNotNull(cursorDPCUpdate);
+ assertEquals(1, cursorDPCUpdate.getCount());
+ cursorDPCUpdate.moveToFirst();
+ assertEquals(dpcRecordId, cursorDPCUpdate.getInt(0));
+ assertEquals(dpcRecordUpdatedApn, cursorDPCUpdate.getString(1));
+ assertEquals(dpcRecordUpdatedName, cursorDPCUpdate.getString(2));
+
+ // Test URI_DPC_ID deletes only DPC records.
+ int numRowsDeleted = mContentResolver.delete(
+ Uri.parse(URI_DPC + "/" + dpcRecordId),
+ null, new String[]{});
+ assertEquals(1, numRowsDeleted);
+ numRowsDeleted = mContentResolver.delete(
+ Uri.parse(URI_DPC + "/" + dpcRecordId),
+ null, new String[]{});
+ assertEquals(0, numRowsDeleted);
+
+ // Delete remaining test records.
+ numRowsDeleted = mContentResolver.delete(
+ Uri.parse(URI_TELEPHONY + "/" + othersRecordId),
+ null, new String[]{});
+ assertEquals(1, numRowsDeleted);
+ }
+
+ /**
+ * Verify that SecurityException is thrown if URL_DPC, URL_FILTERED and
+ * URL_ENFORCE_MANAGED is accessed from non-SYSTEM_UID.
+ */
+ @Test
+ @SmallTest
+ public void testAccessURLDPCThrowSecurityExceptionFromOtherUid() {
+ mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID + 1);
+
+ // Test insert().
+ ContentValues contentValuesDPC = new ContentValues();
+ try {
+ mContentResolver.insert(URI_DPC, contentValuesDPC);
+ assertFalse("SecurityException should be thrown when URI_DPC is called from"
+ + " non-SYSTEM_UID", true);
+ } catch (SecurityException e) {
+ // Should catch SecurityException.
+ }
+
+ // Test query().
+ try {
+ mContentResolver.query(URI_DPC,
+ new String[]{}, "", new String[]{}, null);
+ assertFalse("SecurityException should be thrown when URI_DPC is called from"
+ + " non-SYSTEM_UID", true);
+ } catch (SecurityException e) {
+ // Should catch SecurityException.
+ }
+ try {
+ mContentResolver.query(URI_ENFORCE_MANAGED,
+ new String[]{}, "", new String[]{}, null);
+ assertFalse("SecurityException should be thrown when URI_ENFORCE_MANAGED is called "
+ + "from non-SYSTEM_UID", true);
+ } catch (SecurityException e) {
+ // Should catch SecurityException.
+ }
+
+ // Test update().
+ ContentValues contentValuesDPCUpdate = new ContentValues();
+ try {
+ mContentResolver.update(
+ Uri.parse(URI_DPC + "/1"),
+ contentValuesDPCUpdate, "", new String[]{});
+ assertFalse("SecurityException should be thrown when URI_DPC is called"
+ + " from non-SYSTEM_UID", true);
+ } catch (SecurityException e) {
+ // Should catch SecurityException.
+ }
+ try {
+ mContentResolver.update(URI_ENFORCE_MANAGED, contentValuesDPCUpdate,
+ "", new String[]{});
+ assertFalse("SecurityException should be thrown when URI_DPC is called"
+ + " from non-SYSTEM_UID", true);
+ } catch (SecurityException e) {
+ // Should catch SecurityException.
+ }
+
+ // Test delete().
+ try {
+ mContentResolver.delete(
+ Uri.parse(URI_DPC + "/0"), "", new String[]{});
+ assertFalse("SecurityException should be thrown when URI_DPC is called"
+ + " from non-SYSTEM_UID", true);
+ } catch (SecurityException e) {
+ // Should catch SecurityException.
+ }
+ }
}
diff --git a/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java b/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java
index 7ed40ae..f379982 100644
--- a/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java
+++ b/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java
@@ -36,6 +36,16 @@
private static final String TAG = "TelephonyProviderTestable";
private InMemoryTelephonyProviderDbHelper mDbHelper;
+ private MockInjector mMockInjector;
+
+ public TelephonyProviderTestable() {
+ this(new MockInjector());
+ }
+
+ private TelephonyProviderTestable(MockInjector mockInjector) {
+ super(mockInjector);
+ mMockInjector = mockInjector;
+ }
@Override
public boolean onCreate() {
@@ -72,6 +82,10 @@
return false;
}
+ public void fakeCallingUid(int uid) {
+ mMockInjector.fakeCallingUid(uid);
+ }
+
/**
* An in memory DB for TelephonyProviderTestable to use
*/
@@ -103,4 +117,17 @@
return;
}
}
+
+ static class MockInjector extends Injector {
+ private int callingUid = 0;
+
+ @Override
+ int binderGetCallingUid() {
+ return callingUid;
+ }
+
+ void fakeCallingUid(int uid) {
+ callingUid = uid;
+ }
+ }
}