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

Commit 048273ae authored by Michael Groover's avatar Michael Groover Committed by Android (Google) Code Review
Browse files

Merge "Add tests for checkReadDeviceIdentifiers in TelephonyPermissions" into qt-dev

parents 98bef32d aed78e8e
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);
    }

}