Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aed78e8e authored by Michael Groover's avatar Michael Groover
Browse files

Add tests for checkReadDeviceIdentifiers in TelephonyPermissions

Bug: 132173603
Test: atest TelephonyPermissionsTest
Change-Id: I4a218054ba30ff9e75fcd49f679593b6b64eef89
parent 2d992284
Loading
Loading
Loading
Loading
+231 −3
Original line number Diff line number Diff line
@@ -26,20 +26,34 @@ import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;

import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.SmallTest;

import com.android.internal.util.test.FakeSettingsProvider;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.lang.reflect.Field;

@SmallTest
public class TelephonyPermissionsTest {

    private static final int SUB_ID = 55555;
    private static final int SUB_ID_2 = 22222;
    private static final int PID = 12345;
    private static final int UID = 54321;
    private static final String PACKAGE = "com.example";
@@ -50,17 +64,28 @@ public class TelephonyPermissionsTest {
    @Mock
    private AppOpsManager mMockAppOps;
    @Mock
    private SubscriptionManager mMockSubscriptionMananger;
    private SubscriptionManager mMockSubscriptionManager;
    @Mock
    private ITelephony mMockTelephony;
    @Mock
    private PackageManager mMockPackageManager;
    @Mock
    private ApplicationInfo mMockApplicationInfo;
    @Mock
    private DevicePolicyManager mMockDevicePolicyManager;

    private MockContentResolver mMockContentResolver;
    private FakeSettingsConfigProvider mFakeSettingsConfigProvider;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOps);
        when(mMockContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)).thenReturn(
                mMockSubscriptionMananger);
        when(mMockSubscriptionMananger.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
                mMockSubscriptionManager);
        when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
                mMockDevicePolicyManager);
        when(mMockSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});

        // By default, assume we have no permissions or app-ops bits.
        doThrow(new SecurityException()).when(mMockContext)
@@ -69,8 +94,14 @@ public class TelephonyPermissionsTest {
                .enforcePermission(anyString(), eq(PID), eq(UID), eq(MSG));
        when(mMockAppOps.noteOp(anyInt(), eq(UID), eq(PACKAGE)))
                .thenReturn(AppOpsManager.MODE_ERRORED);
        when(mMockAppOps.noteOpNoThrow(anyString(), eq(UID), eq(PACKAGE))).thenReturn(
                AppOpsManager.MODE_ERRORED);
        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), eq(UID)))
                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
        when(mMockContext.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
                PID, UID)).thenReturn(PackageManager.PERMISSION_DENIED);
        when(mMockDevicePolicyManager.checkDeviceIdentifierAccess(eq(PACKAGE), eq(PID),
                eq(UID))).thenReturn(false);
    }

    @Test
@@ -204,4 +235,201 @@ public class TelephonyPermissionsTest {
        assertTrue(TelephonyPermissions.checkReadPhoneNumber(
                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
    }

    @Test
    public void testCheckReadDeviceIdentifiers_noPermissions() throws Exception {
        setupMocksForDeviceIdentifiersErrorPath();
        try {
            TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
                    SUB_ID, PID, UID, PACKAGE, MSG);
            fail("Should have thrown SecurityException");
        } catch (SecurityException e) {
            // expected
        }
    }

    @Test
    public void testCheckReadDeviceIdentifiers_hasPrivilegedPermission() {
        when(mMockContext.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
                PID, UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
        assertTrue(
                TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
                        SUB_ID, PID, UID, PACKAGE, MSG));
    }

    @Test
    public void testCheckReadDeviceIdentifiers_hasCarrierPrivileges() throws Exception {
        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), eq(UID)))
                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
        assertTrue(
                TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
                        SUB_ID, PID, UID, PACKAGE, MSG));
    }

    @Test
    public void testCheckReadDeviceIdentifiers_hasAppOp() {
        when(mMockAppOps.noteOpNoThrow(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS, UID,
                PACKAGE)).thenReturn(AppOpsManager.MODE_ALLOWED);
        assertTrue(
                TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
                        SUB_ID, PID, UID, PACKAGE, MSG));
    }

    @Test
    public void testCheckReadDeviceIdentifiers_hasDPMDeviceIDAccess() {
        when(mMockDevicePolicyManager.checkDeviceIdentifierAccess(eq(PACKAGE), eq(PID),
                eq(UID))).thenReturn(true);
        assertTrue(
                TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
                        SUB_ID, PID, UID, PACKAGE, MSG));
    }

    @Test
    public void testCheckReadDeviceIdentifiers_hasReadPhoneStateTargetQ() throws Exception {
        // if an app is targeting Q and does not meet the new requirements for device identifier
        // access then a SecurityException should be thrown even if the app has been granted the
        // READ_PHONE_STATE permission.
        when(mMockContext.checkPermission(android.Manifest.permission.READ_PHONE_STATE, PID,
                UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
        setupMocksForDeviceIdentifiersErrorPath();
        try {
            TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
                    SUB_ID, PID, UID, PACKAGE, MSG);
            fail("Should have thrown SecurityException");
        } catch (SecurityException e) {
            // expected
        }
    }

    @Test
    public void testCheckReadDeviceIdentifiers_hasReadPhoneStateTargetPreQ() throws Exception {
        // To prevent breaking existing apps if an app is targeting pre-Q and has been granted the
        // READ_PHONE_STATE permission then checkReadDeviceIdentifiers should return false to
        // indicate the caller should return null / placeholder data.
        when(mMockContext.checkPermission(android.Manifest.permission.READ_PHONE_STATE, PID,
                UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
        setupMocksForDeviceIdentifiersErrorPath();
        mMockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
        assertFalse(
                TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
                        SUB_ID, PID, UID, PACKAGE, MSG));
    }

    @Test
    public void testCheckReadDeviceIdentifiers_hasCarrierPrivilegesOnOtherSubscription()
            throws Exception {
        when(mMockSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(
                new int[]{SUB_ID, SUB_ID_2});
        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID_2), eq(UID))).thenReturn(
                TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
        assertTrue(
                TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
                        SUB_ID, PID, UID, PACKAGE, MSG));
    }

    @Test
    public void testCheckReadDeviceIdentifiers_hasAppOpNullSubscription() {
        // The appop check comes after the carrier privilege check; this test verifies if the
        // SubscriptionManager returns a null array for the active subscription IDs this check can
        // still proceed to check if the calling package has the appop and any subsequent checks
        // without a NullPointerException.
        when(mMockSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(null);
        when(mMockAppOps.noteOpNoThrow(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS, UID,
                PACKAGE)).thenReturn(AppOpsManager.MODE_ALLOWED);
        assertTrue(
                TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
                        SUB_ID, PID, UID, PACKAGE, MSG));
    }

    @Test
    public void testCheckReadDeviceIdentifiers_nullPackageName() throws Exception {
        // If a null package name is passed in then the AppOp and DevicePolicyManager checks cannot
        // be performed, but an app targeting Q should still receive a SecurityException in this
        // case.
        setupMocksForDeviceIdentifiersErrorPath();
        try {
            TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
                    SUB_ID, PID, UID, null, MSG);
            fail("Should have thrown SecurityException");
        } catch (SecurityException e) {
            // expected
        }
    }

    public static class FakeSettingsConfigProvider extends FakeSettingsProvider {
        private static final String PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED =
                DeviceConfig.NAMESPACE_PRIVACY + "/"
                        + "device_identifier_access_restrictions_disabled";

        @Override
        public Bundle call(String method, String arg, Bundle extras) {
            switch (method) {
                case Settings.CALL_METHOD_GET_CONFIG: {
                    switch (arg) {
                        case PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED: {
                            Bundle bundle = new Bundle();
                            bundle.putString(
                                    PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED,
                                    "0");
                            return bundle;
                        }
                        default: {
                            fail("arg not expected: " + arg);
                        }
                    }
                    break;
                }
                // If this is not a get call for Settings.Config then use the FakeSettingsProvider's
                // call method.
                default:
                    return super.call(method, arg, extras);
            }
            return null;
        }
    }

    protected void setupMocksForDeviceIdentifiersErrorPath() throws Exception {
        // If the calling package does not meet the new requirements for device identifier access
        // TelephonyPermissions will query the PackageManager for the ApplicationInfo of the package
        // to determine the target SDK. For apps targeting Q a SecurityException is thrown
        // regardless of if the package satisfies the previous requirements for device ID access.
        mMockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.Q;
        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
        when(mMockPackageManager.getApplicationInfoAsUser(eq(PACKAGE), anyInt(),
                anyInt())).thenReturn(mMockApplicationInfo);

        when(mMockContext.checkCallingOrSelfPermission(
                android.Manifest.permission.READ_DEVICE_CONFIG)).thenReturn(
                PackageManager.PERMISSION_GRANTED);

        // TelephonyPermissions queries DeviceConfig to determine if the identifier access
        // restrictions should be enabled; since DeviceConfig uses
        // Activity.currentActivity.getContentResolver as the resolver for Settings.Config.getString
        // the READ_DEVICE_CONFIG permission check cannot be mocked, so replace the IContentProvider
        // in the NameValueCache's provider holder with that from the fake provider.
        mFakeSettingsConfigProvider = new FakeSettingsConfigProvider();
        mMockContentResolver = new MockContentResolver();
        mMockContentResolver.addProvider(Settings.AUTHORITY, mFakeSettingsConfigProvider);
        when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);

        Class c = Class.forName("android.provider.Settings$Config");
        Field field = c.getDeclaredField("sNameValueCache");
        field.setAccessible(true);
        Object cache = field.get(null);

        c = Class.forName("android.provider.Settings$NameValueCache");
        field = c.getDeclaredField("mProviderHolder");
        field.setAccessible(true);
        Object providerHolder = field.get(cache);

        field = MockContentProvider.class.getDeclaredField("mIContentProvider");
        field.setAccessible(true);
        Object iContentProvider = field.get(mFakeSettingsConfigProvider);

        c = Class.forName("android.provider.Settings$ContentProviderHolder");
        field = c.getDeclaredField("mContentProvider");
        field.setAccessible(true);
        field.set(providerHolder, iContentProvider);
    }

}