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

Unverified Commit fe0401df authored by Kevin F. Haggerty's avatar Kevin F. Haggerty
Browse files

Merge tag 'android-security-10.0.0_r53' into staging/lineage-17.1_merge_android-security-10.0.0_r53

Android security 10.0.0 release 53

* tag 'android-security-10.0.0_r53':
  Remove unecessary locking to avoid dead lock.
  Fix bug of disabling grouped CBRS during profile switch on primary SIM
  Move permission checks out of synchronized block
  Guard ICC ID card string behind new identifier access requirements
  Guard ICC ID behind new identifier access requirements

Conflicts:
	src/java/com/android/internal/telephony/MultiSimSettingController.java
	src/java/com/android/internal/telephony/SubscriptionController.java
	tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java

Change-Id: I12a0bf18b9a0ef68ccce6ee23294c11098547a7d
parents 5482900d 9b12a56b
Loading
Loading
Loading
Loading
+162 −76
Original line number Diff line number Diff line
@@ -284,6 +284,24 @@ public class SubscriptionController extends ISub.Stub {
                Manifest.permission.READ_PRIVILEGED_PHONE_STATE, message);
    }

    /**
     * Returns whether the {@code callingPackage} has access to subscriber identifiers on the
     * specified {@code subId} using the provided {@code message} in any resulting
     * SecurityException.
     */
    private boolean hasSubscriberIdentifierAccess(int subId, String callingPackage,
            String message) {
        try {
            return TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mContext, subId,
                    callingPackage, message);
        } catch (SecurityException e) {
            // A SecurityException indicates that the calling package is targeting at least the
            // minimum level that enforces identifier access restrictions and the new access
            // requirements are not met.
            return false;
        }
    }

    /**
     * Broadcast when SubscriptionInfo has changed
     * FIXME: Hopefully removed if the API council accepts SubscriptionInfoListener
@@ -515,17 +533,20 @@ public class SubscriptionController extends ISub.Stub {

        // Now that all security checks passes, perform the operation as ourselves.
        final long identity = Binder.clearCallingIdentity();
        List<SubscriptionInfo> subList;
        try {
            List<SubscriptionInfo> subList = getActiveSubscriptionInfoList(
                    mContext.getOpPackageName());
            subList = getActiveSubscriptionInfoList(mContext.getOpPackageName());
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
        if (subList != null) {
            for (SubscriptionInfo si : subList) {
                if (si.getSubscriptionId() == subId) {
                        if (DBG) {
                    if (VDBG) {
                        logd("[getActiveSubscriptionInfo]+ subId=" + subId + " subInfo=" + si);
                    }

                        return si;
                    return conditionallyRemoveIdentifiers(si, callingPackage,
                            "getActiveSubscriptionInfo");
                }
            }
        }
@@ -533,9 +554,6 @@ public class SubscriptionController extends ISub.Stub {
            logd("[getActiveSubscriptionInfo]- subId=" + subId
                    + " subList=" + subList + " subInfo=null");
        }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }

        return null;
    }
@@ -634,9 +652,12 @@ public class SubscriptionController extends ISub.Stub {

        // Now that all security checks passes, perform the operation as ourselves.
        final long identity = Binder.clearCallingIdentity();
        List<SubscriptionInfo> subList;
        try {
            List<SubscriptionInfo> subList = getActiveSubscriptionInfoList(
                    mContext.getOpPackageName());
            subList = getActiveSubscriptionInfoList(mContext.getOpPackageName());
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
        if (subList != null) {
            for (SubscriptionInfo si : subList) {
                if (si.getSimSlotIndex() == slotIndex) {
@@ -644,7 +665,8 @@ public class SubscriptionController extends ISub.Stub {
                        logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex="
                                + slotIndex + " subId=" + si);
                    }
                        return si;
                    return conditionallyRemoveIdentifiers(si, callingPackage,
                            "getActiveSubscriptionInfoForSimSlotIndex");
                }
            }
            if (DBG) {
@@ -656,9 +678,7 @@ public class SubscriptionController extends ISub.Stub {
                logd("[getActiveSubscriptionInfoForSimSlotIndex]+ subList=null");
            }
        }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }


        return null;
    }
@@ -697,6 +717,12 @@ public class SubscriptionController extends ISub.Stub {
        }
    }

    private List<SubscriptionInfo> makeCacheListCopyWithLock(List<SubscriptionInfo> cacheSubList) {
        synchronized (mSubInfoListLock) {
            return new ArrayList<>(cacheSubList);
        }
    }

    /**
     * Get the SubInfoRecord(s) of the currently active SIM(s) - which include both local
     * and remote SIMs.
@@ -706,7 +732,8 @@ public class SubscriptionController extends ISub.Stub {
    @UnsupportedAppUsage
    @Override
    public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage) {
        return getSubscriptionInfoListFromCacheHelper(callingPackage, mCacheActiveSubInfoList);
        return getSubscriptionInfoListFromCacheHelper(callingPackage,
                makeCacheListCopyWithLock(mCacheActiveSubInfoList));
    }

    /**
@@ -717,13 +744,13 @@ public class SubscriptionController extends ISub.Stub {
    public void refreshCachedActiveSubscriptionInfoList() {
        boolean opptSubListChanged;

        synchronized (mSubInfoListLock) {
        List<SubscriptionInfo> activeSubscriptionInfoList = getSubInfo(
                SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
                + SubscriptionManager.SUBSCRIPTION_TYPE + "="
                + SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM,
                null);

        synchronized (mSubInfoListLock) {
            if (activeSubscriptionInfoList != null) {
                // Log when active sub info changes.
                if (mCacheActiveSubInfoList.size() != activeSubscriptionInfoList.size()
@@ -738,10 +765,6 @@ public class SubscriptionController extends ISub.Stub {
                logd("activeSubscriptionInfoList is null.");
                mCacheActiveSubInfoList.clear();
            }

            // Refresh cached opportunistic sub list and detect whether it's changed.
            refreshCachedOpportunisticSubscriptionInfoList();

            if (DBG_CACHE) {
                if (!mCacheActiveSubInfoList.isEmpty()) {
                    for (SubscriptionInfo si : mCacheActiveSubInfoList) {
@@ -753,6 +776,9 @@ public class SubscriptionController extends ISub.Stub {
                }
            }
        }

        // Refresh cached opportunistic sub list and detect whether it's changed.
        refreshCachedOpportunisticSubscriptionInfoList();
    }

    /**
@@ -2965,8 +2991,8 @@ public class SubscriptionController extends ISub.Stub {

    @Override
    public List<SubscriptionInfo> getOpportunisticSubscriptions(String callingPackage) {
        return getSubscriptionInfoListFromCacheHelper(
                callingPackage, mCacheOpportunisticSubInfoList);
        return getSubscriptionInfoListFromCacheHelper(callingPackage,
                makeCacheListCopyWithLock(mCacheOpportunisticSubInfoList));
    }

    /**
@@ -3334,7 +3360,9 @@ public class SubscriptionController extends ISub.Stub {
            return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId,
                    callingPackage, "getSubscriptionsInGroup")
                    || info.canManageSubscription(mContext, callingPackage);
        }).collect(Collectors.toList());
        }).map(subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo,
                callingPackage, "getSubscriptionsInGroup"))
        .collect(Collectors.toList());
    }

    public ParcelUuid getGroupUuid(int subId) {
@@ -3601,34 +3629,93 @@ public class SubscriptionController extends ISub.Stub {
    // They are doing similar things except operating on different cache.
    private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper(
            String callingPackage, List<SubscriptionInfo> cacheSubList) {
        boolean canReadAllPhoneState;
        boolean canReadPhoneState = false;
        boolean canReadIdentifiers = false;
        try {
            canReadAllPhoneState = TelephonyPermissions.checkReadPhoneState(mContext,
            canReadPhoneState = TelephonyPermissions.checkReadPhoneState(mContext,
                    SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(),
                    Binder.getCallingUid(), callingPackage, "getSubscriptionInfoList");
            // If the calling package has the READ_PHONE_STATE permission then check if the caller
            // also has access to subscriber identifiers to ensure that the ICC
            // ID and any other unique identifiers are removed if the caller should not have access.
            if (canReadPhoneState) {
                canReadIdentifiers = hasSubscriberIdentifierAccess(
                        SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
                        "getSubscriptionInfoList");
            }
        } catch (SecurityException e) {
            canReadAllPhoneState = false;
            // If a SecurityException is thrown during the READ_PHONE_STATE check then the only way
            // to access a subscription is to have carrier privileges for its subId; an app with
            // carrier privileges for a subscription is also granted access to all identifiers so
            // the identifier and phone number access checks are not required.
        }

        synchronized (mSubInfoListLock) {
        // If the caller can read all phone state, just return the full list.
            if (canReadAllPhoneState) {
                return new ArrayList<>(cacheSubList);
        if (canReadIdentifiers) {
            return cacheSubList;
        }

        // Filter the list to only include subscriptions which the caller can manage.
            return cacheSubList.stream()
                    .filter(subscriptionInfo -> {
                        try {
                            return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext,
                                    subscriptionInfo.getSubscriptionId(), callingPackage,
                                    "getSubscriptionInfoList");
                        } catch (SecurityException e) {
                            return false;
        List<SubscriptionInfo> subscriptions = new ArrayList<>(cacheSubList.size());
        for (SubscriptionInfo subscriptionInfo : cacheSubList) {
            int subId = subscriptionInfo.getSubscriptionId();
            boolean hasCarrierPrivileges = TelephonyPermissions.checkCarrierPrivilegeForSubId(
                    subId);
            // If the caller does not have the READ_PHONE_STATE permission nor carrier
            // privileges then they cannot access the current subscription.
            if (!canReadPhoneState && !hasCarrierPrivileges) {
                continue;
            }
            // If the caller has carrier privileges then they are granted access to all
            // identifiers for their subscription.
            if (hasCarrierPrivileges) {
                subscriptions.add(subscriptionInfo);
            } else {
                // The caller does not have carrier privileges for this subId, filter the
                // identifiers in the subscription based on the results of the initial
                // permission checks.
                subscriptions.add(
                        conditionallyRemoveIdentifiers(subscriptionInfo, canReadIdentifiers));
            }
                    })
                    .collect(Collectors.toList());
        }
        return subscriptions;
    }

    /**
     * Conditionally removes identifiers from the provided {@code subInfo} if the {@code
     * callingPackage} does not meet the access requirements for identifiers and returns the
     * potentially modified object..
     *
     * <p>If the caller does not meet the access requirements for identifiers a clone of the
     * provided SubscriptionInfo is created and modified to avoid altering SubscriptionInfo objects
     * in a cache.
     */
    private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo,
            String callingPackage,  String message) {
        SubscriptionInfo result = subInfo;
        int subId = subInfo.getSubscriptionId();
        boolean hasIdentifierAccess = hasSubscriberIdentifierAccess(subId, callingPackage, message);
        return conditionallyRemoveIdentifiers(subInfo, hasIdentifierAccess);
    }

    /**
     * Conditionally removes identifiers from the provided {@code subInfo} based on if the calling
     * package {@code hasIdentifierAccess} and returns the potentially modified object.
     *
     * <p>If the caller specifies the package does not have identifier access
     * a clone of the provided SubscriptionInfo is created and modified to avoid altering
     * SubscriptionInfo objects in a cache.
     */
    private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo,
            boolean hasIdentifierAccess) {
        if (hasIdentifierAccess) {
            return subInfo;
        }
        SubscriptionInfo result = new SubscriptionInfo(subInfo);
        if (!hasIdentifierAccess) {
            result.clearIccId();
            result.clearCardString();
        }
        return result;
    }

    private synchronized boolean addToSubIdList(int slotIndex, int subId, int subscriptionType) {
@@ -3690,13 +3777,12 @@ public class SubscriptionController extends ISub.Stub {
    }

    private void refreshCachedOpportunisticSubscriptionInfoList() {
        synchronized (mSubInfoListLock) {
            List<SubscriptionInfo> oldOpptCachedList = mCacheOpportunisticSubInfoList;

        List<SubscriptionInfo> subList = getSubInfo(
                SubscriptionManager.IS_OPPORTUNISTIC + "=1 AND ("
                        + SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
                        + SubscriptionManager.IS_EMBEDDED + "=1)", null);
        synchronized (mSubInfoListLock) {
            List<SubscriptionInfo> oldOpptCachedList = mCacheOpportunisticSubInfoList;

            if (subList != null) {
                subList.sort(SUBSCRIPTION_INFO_COMPARATOR);
+230 −0
Original line number Diff line number Diff line
@@ -78,6 +78,8 @@ public class SubscriptionControllerTest extends TelephonyTest {
    private static final String MAC_ADDRESS_PREFIX = "mac_";
    private static final String DISPLAY_NAME_PREFIX = "my_phone_";

    private static final String UNAVAILABLE_ICCID = "";

    @Before
    public void setUp() throws Exception {
        super.setUp("SubscriptionControllerTest");
@@ -941,6 +943,234 @@ public class SubscriptionControllerTest extends TelephonyTest {
        assertTrue("active sub ids = " + subIds, Arrays.equals(subIds, new int[]{2, 1}));
    }

    @Test
    public void testGetActiveSubscriptionInfoWithNoPermissions() throws Exception {
        // If the calling package does not have the READ_PHONE_STATE permission or carrier
        // privileges then getActiveSubscriptionInfo should throw a SecurityException;
        testInsertSim();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
        int subId = getFirstSubId();

        try {
            mSubscriptionControllerUT.getActiveSubscriptionInfo(subId, mCallingPackage);
            fail("getActiveSubscriptionInfo should fail when invoked with no permissions");
        } catch (SecurityException expected) {
        }
    }

    @Test
    public void testGetActiveSubscriptionInfoWithReadPhoneState() throws Exception {
        // If the calling package only has the READ_PHONE_STATE permission then
        // getActiveSubscriptionInfo should still return a result but the ICC ID should not be
        // available via getIccId or getCardString.
        testInsertSim();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
        setupMocksForTelephonyPermissions();
        int subId = getFirstSubId();

        SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo(
                subId, mCallingPackage);
        assertNotNull(subscriptionInfo);
        assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getIccId());
        assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getCardString());
    }

    @Test
    public void testGetActiveSubscriptionWithPrivilegedPermission() throws Exception {
        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
        // privileges the ICC ID should be available in the SubscriptionInfo.
        testInsertSim();
        int subId = getFirstSubId();

        SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo(
                subId, mCallingPackage);
        assertNotNull(subscriptionInfo);
        assertTrue(subscriptionInfo.getIccId().length() > 0);
        assertTrue(subscriptionInfo.getCardString().length() > 0);
    }

    @Test
    public void testGetActiveSubscriptionInfoForSimSlotIndexWithNoPermission() throws Exception {
        // If the calling package does not have the READ_PHONE_STATE permission or carrier
        // privileges then getActiveSubscriptionInfoForSimSlotIndex should throw a
        // SecurityException.
        testInsertSim();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);

        try {
            mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, mCallingPackage);
            fail("getActiveSubscriptionInfoForSimSlotIndex should fail when invoked with no "
                    + "permissions");
        } catch (SecurityException expected) {
        }
    }

    @Test
    public void testGetActiveSubscriptionInfoForSimSlotIndexWithReadPhoneState() throws Exception {
        // If the calling package only has the READ_PHONE_STATE permission then
        // getActiveSubscriptionInfoForSimlSlotIndex should still return the SubscriptionInfo but
        // the ICC ID should not be available via getIccId or getCardString.
        testInsertSim();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
        setupMocksForTelephonyPermissions();

        SubscriptionInfo subscriptionInfo =
                mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0,
                        mCallingPackage);
        assertNotNull(subscriptionInfo);
        assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getIccId());
        assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getCardString());
    }

    @Test
    public void testGetActiveSubscriptionInfoForSimSlotIndexWithPrivilegedPermission()
            throws Exception {
        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
        // privileges the ICC ID should be available in the SubscriptionInfo.
        testInsertSim();

        SubscriptionInfo subscriptionInfo =
                mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0,
                        mCallingPackage);
        assertNotNull(subscriptionInfo);
        assertTrue(subscriptionInfo.getIccId().length() > 0);
        assertTrue(subscriptionInfo.getCardString().length() > 0);
    }

    @Test
    public void testGetActiveSubscriptionInfoListWithNoPermission() throws Exception {
        // If the calling package does not have the READ_PHONE_STATE permission or carrier
        // privileges then getActiveSubscriptionInfoList should return a list with 0 elements.
        testInsertSim();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);

        List<SubscriptionInfo> subInfoList =
                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage);
        assertNotNull(subInfoList);
        assertTrue(subInfoList.size() == 0);
    }

    @Test
    public void testGetActiveSubscriptionInfoListWithReadPhoneState() throws Exception {
        // If the calling package only has the READ_PHONE_STATE permission then
        // getActiveSubscriptionInfoList should still return the list of SubscriptionInfo objects
        // but the ICC ID should not be available via getIccId or getCardString.
        testInsertSim();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
        setupMocksForTelephonyPermissions();

        List<SubscriptionInfo> subInfoList =
                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage);
        assertTrue(subInfoList.size() > 0);
        for (SubscriptionInfo info : subInfoList) {
            assertEquals(UNAVAILABLE_ICCID, info.getIccId());
            assertEquals(UNAVAILABLE_ICCID, info.getCardString());
        }
    }

    @Test
    public void testGetActiveSubscriptionInfoListWithIdentifierAccessWithoutNumberAccess()
            throws Exception {
        // An app with access to device identifiers may not have access to the device phone number
        // (ie an app that passes the device / profile owner check or an app that has been granted
        // the device identifiers appop); this test verifies that an app with identifier access
        // can read the ICC ID but does not receive the phone number.
        testInsertSim();

        List<SubscriptionInfo> subInfoList =
                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage);

        assertEquals(1, subInfoList.size());
        SubscriptionInfo subInfo = subInfoList.get(0);
        assertEquals("test", subInfo.getIccId());
    }

    @Test
    public void testGetActiveSubscriptionInfoListWithPrivilegedPermission() throws Exception {
        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
        // privileges the ICC ID should be available in the SubscriptionInfo objects in the List.
        testInsertSim();

        List<SubscriptionInfo> subInfoList =
                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage);
        assertTrue(subInfoList.size() > 0);
        for (SubscriptionInfo info : subInfoList) {
            assertTrue(info.getIccId().length() > 0);
            assertTrue(info.getCardString().length() > 0);
        }
    }

    @Test
    public void testGetSubscriptionsInGroupWithNoPermission() throws Exception {
        // If the calling package does not have the READ_PHONE_STATE permission or carrier
        // privileges then getSubscriptionsInGroup should throw a SecurityException when the
        // READ_PHONE_STATE permission check is performed.
        ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);

        try {
            mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid, mCallingPackage);
            fail("getSubscriptionsInGroup should fail when invoked with no permissions");
        } catch (SecurityException expected) {
        }
    }

    @Test
    public void testGetSubscriptionsInGroupWithReadPhoneState() throws Exception {
        // If the calling package only has the READ_PHONE_STATE permission then
        // getSubscriptionsInGroup should still return the list of SubscriptionInfo objects
        // but the ICC ID should not be available via getIccId or getCardString.
        ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
        setupMocksForTelephonyPermissions();

        List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
                groupUuid, mCallingPackage);
        assertTrue(subInfoList.size() > 0);
        for (SubscriptionInfo info : subInfoList) {
            assertEquals(UNAVAILABLE_ICCID, info.getIccId());
            assertEquals(UNAVAILABLE_ICCID, info.getCardString());
        }
    }

    @Test
    public void testGetSubscriptionsInGroupWithPrivilegedPermission() throws Exception {
        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
        // privileges the ICC ID should be available in the SubscriptionInfo objects in the List.
        ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();

        List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
                groupUuid, mCallingPackage);
        assertTrue(subInfoList.size() > 0);
        for (SubscriptionInfo info : subInfoList) {
            assertTrue(info.getIccId().length() > 0);
            assertTrue(info.getCardString().length() > 0);
        }
    }

    private ParcelUuid setupGetSubscriptionsInGroupTest() throws Exception {
        testInsertSim();
        int[] subIdList = new int[]{getFirstSubId()};
        ParcelUuid groupUuid = mSubscriptionControllerUT.createSubscriptionGroup(subIdList,
                mCallingPackage);
        assertNotNull(groupUuid);
        return groupUuid;
    }

    private int getFirstSubId() throws Exception {
        return getSubIdAtIndex(0);
    }

    private int getSubIdAtIndex(int index) throws Exception {
        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
        assertTrue(subIds != null && subIds.length > index);
        return subIds[index];
    }

    @Test
    public void testGetEnabledSubscriptionIdSingleSIM() {
        // A single SIM device may have logical slot 0 mapped to physical slot 1
+9 −0

File changed.

Preview size limit exceeded, changes collapsed.