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;
+        }
+    }
 }