Update BlockedNumberProvider permission checks as documented in the contract.

BUG: 26232372
Change-Id: I40ccdff1f3820c080d3a6361f17fe6cb6506aa37
diff --git a/src/com/android/providers/blockednumber/BlockedNumberProvider.java b/src/com/android/providers/blockednumber/BlockedNumberProvider.java
index 2774aa6..257756c 100644
--- a/src/com/android/providers/blockednumber/BlockedNumberProvider.java
+++ b/src/com/android/providers/blockednumber/BlockedNumberProvider.java
@@ -22,7 +22,6 @@
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.UriMatcher;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
@@ -32,10 +31,10 @@
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Process;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.BlockedNumberContract;
 import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -150,6 +149,8 @@
     @Override
     public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
             @Nullable String[] selectionArgs) {
+        enforceWritePermission();
+
         throw new UnsupportedOperationException(
                 "Update is not supported.  Use delete + insert instead");
     }
@@ -331,59 +332,56 @@
     }
 
     /**
-     * Throws {@link SecurityException} when the caller is not root, system, the system dialer,
-     * the user selected dialer, or the default SMS app.
+     * Returns {@code false} when the caller is not root, the user selected dialer, the
+     * default SMS app or a carrier app.
      */
-    private void enforceReadPermission() {
+    private boolean checkForPrivilegedApplications() {
         if (!canCurrentUserBlockUsers()) {
             throw new UnsupportedOperationException();
         }
 
-        final int callingUid = Binder.getCallingUid();
-
-        // System and root can always call it. (and myself)
-        if (UserHandle.isSameApp(callingUid, android.os.Process.SYSTEM_UID)
-                || (callingUid == Process.ROOT_UID)
-                || (callingUid == Process.myUid())) {
-            return;
+        if (Binder.getCallingUid() == Process.ROOT_UID) {
+            return true;
         }
 
         final String callingPackage = getCallingPackage();
         if (TextUtils.isEmpty(callingPackage)) {
             Log.w(TAG, "callingPackage not accessible");
         } else {
-
             final TelecomManager telecom = getContext().getSystemService(TelecomManager.class);
 
             if (callingPackage.equals(telecom.getDefaultDialerPackage())
-                || callingPackage.equals(telecom.getSystemDialerPackage())) {
-                return;
+                    || callingPackage.equals(telecom.getSystemDialerPackage())) {
+                return true;
             }
-
-            // Allow the default SMS app and the dialer app to access it.
             final AppOpsManager appOps = getContext().getSystemService(AppOpsManager.class);
-
             if (appOps.noteOp(AppOpsManager.OP_WRITE_SMS,
                     Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED) {
-                return;
+                return true;
             }
 
-            // TODO: Add an explicit permission instead.
-            try {
-                ApplicationInfo applicationInfo = getContext().
-                        getPackageManager().getPackageInfo(callingPackage, 0).applicationInfo;
-                if (applicationInfo.isPrivilegedApp() || applicationInfo.isSystemApp()) {
-                    return;
-                }
-            } catch (PackageManager.NameNotFoundException e) {
-                Log.w(TAG, "package not found: " + e);
-            }
+            final TelephonyManager telephonyManager =
+                    getContext().getSystemService(TelephonyManager.class);
+            return telephonyManager.checkCarrierPrivilegesForPackage(callingPackage) ==
+                    TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
         }
-        throw new SecurityException("Caller must be system, default dialer or default SMS app");
+        return false;
     }
 
-    public void enforceWritePermission() {
-        // Same check as read.
-        enforceReadPermission();
+    private void enforceReadPermission() {
+        checkForPermission(android.Manifest.permission.READ_BLOCKED_NUMBERS);
+    }
+
+    private void enforceWritePermission() {
+        checkForPermission(android.Manifest.permission.WRITE_BLOCKED_NUMBERS);
+    }
+
+    private void checkForPermission(String permission) {
+        boolean permitted = getContext().checkCallingPermission(permission)
+                == PackageManager.PERMISSION_GRANTED
+                || checkForPrivilegedApplications();
+        if (!permitted) {
+            throw new SecurityException("Caller must be system, default dialer or default SMS app");
+        }
     }
 }
diff --git a/tests/src/com/android/providers/blockednumber/BlockedNumberProviderTest.java b/tests/src/com/android/providers/blockednumber/BlockedNumberProviderTest.java
index 0ceb684..868735b 100644
--- a/tests/src/com/android/providers/blockednumber/BlockedNumberProviderTest.java
+++ b/tests/src/com/android/providers/blockednumber/BlockedNumberProviderTest.java
@@ -15,48 +15,52 @@
  */
 package com.android.providers.blockednumber;
 
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
-import android.content.Context;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteConstraintException;
 import android.database.sqlite.SQLiteException;
+import android.location.Country;
 import android.net.Uri;
 import android.provider.BlockedNumberContract;
 import android.provider.BlockedNumberContract.BlockedNumbers;
 import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
+
 import junit.framework.Assert;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 /**
- * m BlockedNumberProviderTest && adb install -r
- * ${ANDROID_PRODUCT_OUT}/data/app/BlockedNumberProviderTest/BlockedNumberProviderTest.apk && adb
- * shell am instrument -e class com.android.providers.blockednumber.BlockedNumberProviderTest \ -w
- * com.android.providers.blockednumber.tests/android.support.test.runner.AndroidJUnitRunner
+ * m BlockedNumberProviderTest && runtest --path packages/providers/BlockedNumberProvider/tests
  */
 public class BlockedNumberProviderTest extends AndroidTestCase {
-    private Context mRealTestContext;
     private MyMockContext mMockContext;
     private ContentResolver mResolver;
 
-    /**
-     * Whether the country detector thinks the device is in USA.
-     */
-    private boolean mInUSA;
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
-        mRealTestContext = getContext();
-        mMockContext = new MyMockContext(mRealTestContext);
+        mMockContext = new MyMockContext();
+        mMockContext.init();
         mResolver = mMockContext.getContentResolver();
-        mInUSA = "US".equals(Utils.getCurrentCountryIso(mRealTestContext));
+
+        when(mMockContext.mUserManager.isPrimaryUser()).thenReturn(true);
+        when(mMockContext.mCountryDetector.detectCountry())
+                .thenReturn(new Country("US", Country.COUNTRY_SOURCE_LOCATION));
+        when(mMockContext.mAppOpsManager.noteOp(
+                eq(AppOpsManager.OP_WRITE_SMS), anyInt(), anyString()))
+                .thenReturn(AppOpsManager.MODE_ERRORED);
     }
 
     @Override
@@ -253,11 +257,8 @@
         assertIsBlocked(true, "045-111-2222");
         assertIsBlocked(false, "045 111 2222");
 
-        if (mInUSA) {
-            // Probably won't work outside of the +1 region.
-            assertIsBlocked(true, "500-454 1111");
-            assertIsBlocked(true, "500-454 2222");
-        }
+        assertIsBlocked(true, "500-454 1111");
+        assertIsBlocked(true, "500-454 2222");
         assertIsBlocked(true, "+1 500-454 1111");
         assertIsBlocked(true, "1 500-454 1111");
 
diff --git a/tests/src/com/android/providers/blockednumber/MyMockContext.java b/tests/src/com/android/providers/blockednumber/MyMockContext.java
index 14d4f84..47fadef 100644
--- a/tests/src/com/android/providers/blockednumber/MyMockContext.java
+++ b/tests/src/com/android/providers/blockednumber/MyMockContext.java
@@ -15,57 +15,103 @@
  */
 package com.android.providers.blockednumber;
 
+import android.app.AppOpsManager;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.location.CountryDetector;
+import android.os.UserManager;
 import android.provider.BlockedNumberContract;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
 import android.test.mock.MockContentResolver;
 import android.test.mock.MockContext;
+
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.HashMap;
+
 public class MyMockContext extends MockContext {
-    public final Context realTestContext;
-
     @Mock
-    CountryDetector countryDetector;
+    CountryDetector mCountryDetector;
+    @Mock
+    AppOpsManager mAppOpsManager;
+    @Mock
+    UserManager mUserManager;
+    @Mock
+    TelecomManager mTelecomManager;
+    @Mock
+    TelephonyManager mTelephonyManager;
 
-    MockContentResolver resolver;
+    private final HashMap<Class<?>, String> mSupportedServiceNamesByClass =
+            new HashMap<Class<?>, String>();
+    private MockContentResolver mResolver;
+    private BlockedNumberProviderTestable mProvider;
 
-    BlockedNumberProviderTestable provider;
-
-    public MyMockContext(Context realTestContext) {
-        this.realTestContext = realTestContext;
-        MockitoAnnotations.initMocks(this);
-
-        resolver = new MockContentResolver();
-
-        provider = new BlockedNumberProviderTestable();
-
-        final ProviderInfo info = new ProviderInfo();
-        info.authority = BlockedNumberContract.AUTHORITY;
-        provider.attachInfoForTesting(realTestContext, info);
-
-        resolver.addProvider(BlockedNumberContract.AUTHORITY, provider);
+    @Override
+    public String getSystemServiceName(Class<?> serviceClass) {
+        if (mSupportedServiceNamesByClass.containsKey(serviceClass)) {
+            return mSupportedServiceNamesByClass.get(serviceClass);
+        }
+        throw new UnsupportedOperationException();
     }
 
     @Override
     public Object getSystemService(String name) {
         switch (name) {
             case Context.COUNTRY_DETECTOR:
-                return countryDetector;
+                return mCountryDetector;
+            case Context.APP_OPS_SERVICE:
+                return mAppOpsManager;
+            case Context.USER_SERVICE:
+                return mUserManager;
+            case Context.TELECOM_SERVICE:
+                return mTelecomManager;
+            case Context.TELEPHONY_SERVICE:
+                return mTelephonyManager;
         }
-        throw new UnsupportedOperationException();
+        throw new UnsupportedOperationException("Service not supported: " + name);
     }
 
     @Override
     public ContentResolver getContentResolver() {
-        return resolver;
+        return mResolver;
+    }
+
+    @Override
+    public int checkCallingPermission(String permission) {
+        return permission != null && (permission.equals("android.permission.READ_BLOCKED_NUMBERS")
+                || permission.equals("android.permission.WRITE_BLOCKED_NUMBERS"))
+                ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
+    }
+
+    public void init() {
+        registerServices();
+        mResolver = new MockContentResolver();
+
+        mProvider = new BlockedNumberProviderTestable();
+
+        final ProviderInfo info = new ProviderInfo();
+        info.authority = BlockedNumberContract.AUTHORITY;
+        mProvider.attachInfoForTesting(this, info);
+
+        mResolver.addProvider(BlockedNumberContract.AUTHORITY, mProvider);
+    }
+
+    private void registerServices() {
+        MockitoAnnotations.initMocks(this);
+
+        mSupportedServiceNamesByClass.put(CountryDetector.class, Context.COUNTRY_DETECTOR);
+        mSupportedServiceNamesByClass.put(AppOpsManager.class, Context.APP_OPS_SERVICE);
+        mSupportedServiceNamesByClass.put(UserManager.class, Context.USER_SERVICE);
+        mSupportedServiceNamesByClass.put(TelecomManager.class, Context.TELECOM_SERVICE);
+        mSupportedServiceNamesByClass.put(TelephonyManager.class, Context.TELEPHONY_SERVICE);
     }
 
     public void shutdown() {
-        provider.shutdown();
+        mProvider.shutdown();
     }
 }