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

Commit 97950468 authored by Xiangyu/Malcolm Chen's avatar Xiangyu/Malcolm Chen Committed by android-build-merger
Browse files

Merge "Add APIs to remove sub from a group and get subs in the same gorup." am: a72371ed

am: 656482b4

Change-Id: I286d57ffe8bdd5b9ee158918948442df2a5a3df6
parents da5110c4 656482b4
Loading
Loading
Loading
Loading
+180 −55
Original line number Original line Diff line number Diff line
@@ -2338,95 +2338,220 @@ public class SubscriptionController extends ISub.Stub {
     * Being in the same group means they might be activated or deactivated
     * Being in the same group means they might be activated or deactivated
     * together, some of them may be invisible to the users, etc.
     * together, some of them may be invisible to the users, etc.
     *
     *
     * Caller will either have {@link android.Manifest.permission.MODIFY_PHONE_STATE}
     * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
     * permission or can manage all subscriptions in the list, according to their
     * permission or had carrier privilege permission on the subscriptions:
     * access rules.
     * {@link TelephonyManager#hasCarrierPrivileges(int)} or
     * {@link SubscriptionManager#canManageSubscription(SubscriptionInfo)}
     *
     *
     * @throws SecurityException if the caller doesn't meet the requirements
     *             outlined above.
     *
     * @param subIdList list of subId that will be in the same group
     * @return groupUUID a UUID assigned to the subscription group. It returns
     * @return groupUUID a UUID assigned to the subscription group. It returns
     * null if fails.
     * null if fails.
     *
     *
     */
     */
    @Override
    @Override
    public String setSubscriptionGroup(int[] subIdList, String callingPackage) {
    public String setSubscriptionGroup(int[] subIdList, String callingPackage) {
        boolean hasModifyPermission = mContext.checkCallingOrSelfPermission(
        if (subIdList == null || subIdList.length == 0) {
                android.Manifest.permission.MODIFY_PHONE_STATE) == PERMISSION_GRANTED;
            return null;
        }
        // If it doesn't have modify phone state permission, or carrier privilege permission,
        // a SecurityException will be thrown. If it's due to invalid parameter or internal state,
        // it will return null.
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
                != PERMISSION_GRANTED && !checkCarrierPrivilegeOnSubList(
                        subIdList, callingPackage)) {
            return null;
        }

        long identity = Binder.clearCallingIdentity();

        try {
            // Generate a UUID.
            String groupUUID = UUID.randomUUID().toString();

            ContentValues value = new ContentValues();
            value.put(SubscriptionManager.GROUP_UUID, groupUUID);
            int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
                    value, getSelectionForSubIdList(subIdList), null);

            if (DBG) logdl("setSubscriptionGroup update DB result: " + result);


        // If caller doesn't have modify permission or carrier privilege permission on certain
            refreshCachedActiveSubscriptionInfoList();
        // subscriptions, maybe because the they are not active. So we keep them in a hashset and

        // later check access rules in our database to know whether they can manage them.
            return groupUUID;
        Set<Integer> subIdCheckList = new HashSet<>();
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    /**
     * Remove a list of subscriptions from their subscription group.
     * See {@link #setSubscriptionGroup(int[], String)} for more details.
     *
     * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
     * permission or had carrier privilege permission on the subscriptions:
     * {@link TelephonyManager#hasCarrierPrivileges(int)} or
     * {@link SubscriptionManager#canManageSubscription(SubscriptionInfo)}
     *
     * @throws SecurityException if the caller doesn't meet the requirements
     *             outlined above.
     *
     * @param subIdList list of subId that need removing from their groups.
     * @return whether the operation succeeds.
     *
     */
    public boolean removeSubscriptionsFromGroup(int[] subIdList, String callingPackage) {
        if (subIdList == null || subIdList.length == 0) {
            return false;
        }
        // If it doesn't have modify phone state permission, or carrier privilege permission,
        // a SecurityException will be thrown. If it's due to invalid parameter or internal state,
        // it will return null.
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
                != PERMISSION_GRANTED && !checkCarrierPrivilegeOnSubList(
                subIdList, callingPackage)) {
            return false;
        }

        long identity = Binder.clearCallingIdentity();

        try {
            ContentValues value = new ContentValues();
            value.put(SubscriptionManager.GROUP_UUID, (String) null);
            int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
                    value, getSelectionForSubIdList(subIdList), null);

            if (DBG) logdl("setSubscriptionGroup update DB result: " + result);

            refreshCachedActiveSubscriptionInfoList();

            return result != 0;
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    /**
     *  Helper function to check if the caller has carrier privilege permissions on a list of subId.
     *  The check can either be processed against access rules on currently active SIM cards, or
     *  the access rules we keep in our database for currently inactive eSIMs.
     *
     *  Throws {@link SecurityException} if it fails.
     *
     *  @return true if checking passes on all subId. false if subId is invalid or doesn't exist,
     *  or sub controller is not ready yet.
     */
    private boolean checkCarrierPrivilegeOnSubList(int[] subIdList, String callingPackage) {
        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
        // Check carrier privilege permission on active subscriptions first.
        // If it fails, they could be inactive. So keep them in a HashSet and later check
        // access rules in our database.
        Set<Integer> checkSubList = new HashSet<>();
        for (int subId : subIdList) {
        for (int subId : subIdList) {
            if (isActiveSubId(subId)) {
                if (!mTelephonyManager.hasCarrierPrivileges(subId)) {
                if (!mTelephonyManager.hasCarrierPrivileges(subId)) {
                subIdCheckList.add(subId);
                    throw new SecurityException("Need carrier privilege on subId " + subId);
                }
            } else {
                checkSubList.add(subId);
            }
            }
        }
        }


        if (checkSubList.isEmpty()) {
            return true;
        }

        long identity = Binder.clearCallingIdentity();
        long identity = Binder.clearCallingIdentity();


        try {
        try {
            if (!isSubInfoReady()) {
            if (!isSubInfoReady()) {
                if (DBG) logdl("[getSubscriptionInfoList] Sub Controller not ready");
                if (DBG) logdl("[getSubscriptionInfoList] Sub Controller not ready");
                return null;
                return false;
            }
            }


            // Check access rules for each sub info.
            SubscriptionManager subscriptionManager = (SubscriptionManager)
            SubscriptionManager subscriptionManager = (SubscriptionManager)
                    mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
                    mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
            List<SubscriptionInfo> subList = getSubInfo(null, null);
            List<SubscriptionInfo> subList = getSubInfo(getSelectionForSubIdList(subIdList), null);

            for (SubscriptionInfo subInfo : subList) {
            for (SubscriptionInfo subInfo : subList) {
                if (subIdCheckList.contains(subInfo.getSubscriptionId())) {
                if (checkSubList.contains(subInfo.getSubscriptionId())) {
                    // If caller doesn't have modify permission or privilege access to
                    if (subInfo.isEmbedded() && subscriptionManager.canManageSubscription(
                    // the subscription, operation is invalid and returns null.
                            subInfo, callingPackage)) {
                    if (hasModifyPermission || (subInfo.isEmbedded()
                        checkSubList.remove(subInfo.getSubscriptionId());
                            && subscriptionManager.canManageSubscription(
                                    subInfo, callingPackage))) {
                        subIdCheckList.remove(subInfo.getSubscriptionId());
                    } else {
                    } else {
                        if (DBG) {
                        throw new SecurityException("Need carrier privilege on subId "
                            logdl("setSubscriptionGroup doesn't have permission on"
                                + subInfo.getSubscriptionId());
                                    + " subInfo " + subInfo);
                    }
                    }
                        return null;
                }
                }
            }
            }

            return checkSubList.isEmpty();
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }
    }


            if (!subIdCheckList.isEmpty()) {
    /**
                // Some SubId not found.
     * Helper function to create selection argument of a list of subId.
                StringBuilder subIdNotFound = new StringBuilder();
     * The result should be: "in (subId1, subId2, ...)".
                for (int subId : subIdCheckList) {
     */
                    subIdNotFound.append(subId + " ");
    private String getSelectionForSubIdList(int[] subId) {
        StringBuilder selection = new StringBuilder();
        selection.append(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID);
        selection.append(" IN (");
        for (int i = 0; i < subId.length - 1; i++) {
            selection.append(subId[i] + ", ");
        }
        }
                if (DBG) {
        selection.append(subId[subId.length - 1]);
                    logdl("setSubscriptionGroup subId not existed: "
        selection.append(")");
                            + subIdNotFound.toString());

        return selection.toString();
    }
    }


    /**
     * Get subscriptionInfo list of subscriptions that are in the same group of given subId.
     * See {@link #setSubscriptionGroup(int[], String)} for more details.
     *
     * Caller will either have {@link android.Manifest.permission#READ_PHONE_STATE}
     * permission or had carrier privilege permission on the subscription.
     * {@link TelephonyManager#hasCarrierPrivileges(int)}
     *
     * @throws SecurityException if the caller doesn't meet the requirements
     *             outlined above.
     *
     * @param subId of which list of subInfo from the same group will be returned.
     * @return list of subscriptionInfo that belong to the same group, including the given
     * subscription itself. It will return null if the subscription doesn't exist or it
     * doesn't belong to any group.
     *
     */
    public List<SubscriptionInfo> getSubscriptionsInGroup(int subId, String callingPackage) {
        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
                mContext, subId, callingPackage, "getSubscriptionsInGroup")) {
            return null;
            return null;
        }
        }


            // Generate a UUID.
        long identity = Binder.clearCallingIdentity();
            String groupUUID = UUID.randomUUID().toString();


            // Selection should be: "in (subId1, subId2, ...)".
        try {
            StringBuilder selection = new StringBuilder();
            SubscriptionInfo info = getActiveSubscriptionInfo(subId, callingPackage);
            selection.append(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID);
            if (info == null || TextUtils.isEmpty(info.getGroupUuid())) {
            selection.append(" IN (");
                return null;
            for (int i = 0; i < subIdList.length - 1; i++) {
                selection.append(subIdList[i] + ", ");
            }
            }
            selection.append(subIdList[subIdList.length - 1]);
            selection.append(")");
            ContentValues value = new ContentValues();
            value.put(SubscriptionManager.GROUP_UUID, groupUUID);
            int result = mContext.getContentResolver().update(
                    SubscriptionManager.CONTENT_URI, value, selection.toString(), null);


            if (DBG) logdl("setSubscriptionGroup update DB result: " + result);
            String groupUuid = info.getGroupUuid();
            List<SubscriptionInfo> infoList = getAvailableSubscriptionInfoList(callingPackage);


            refreshCachedActiveSubscriptionInfoList();
            // Shouldn't happen because we've verified the subId belongs to an active subscription.
            if (infoList == null) {
                return null;
            }


            return groupUUID;
            return infoList.stream().filter(
                    subscriptionInfo -> groupUuid.equals(subscriptionInfo.getGroupUuid()))
                    .collect(Collectors.toList());
        } finally {
        } finally {
            Binder.restoreCallingIdentity(identity);
            Binder.restoreCallingIdentity(identity);
        }
        }
+2 −0
Original line number Original line Diff line number Diff line
@@ -590,6 +590,8 @@ public class ContextFixture implements TestFixture<Context> {
        //doReturn(mBundle).when(mCarrierConfigManager).getConfig(anyInt());
        //doReturn(mBundle).when(mCarrierConfigManager).getConfig(anyInt());
        doReturn(mBundle).when(mCarrierConfigManager).getConfig();
        doReturn(mBundle).when(mCarrierConfigManager).getConfig();


        doReturn(true).when(mEuiccManager).isEnabled();

        mConfiguration.locale = Locale.US;
        mConfiguration.locale = Locale.US;
        doReturn(mConfiguration).when(mResources).getConfiguration();
        doReturn(mConfiguration).when(mResources).getConfiguration();


+68 −23
Original line number Original line Diff line number Diff line
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doReturn;
@@ -417,14 +418,17 @@ public class SubscriptionControllerTest extends TelephonyTest {
        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);


        int[] subIdList = new int[] {1, 2};
        int[] subIdList = new int[] {1, 2};
        // It should fail since it has no permission.
        try {
        String groupId = mSubscriptionControllerUT.setSubscriptionGroup(
            mSubscriptionControllerUT.setSubscriptionGroup(
                    subIdList, mContext.getOpPackageName());
                    subIdList, mContext.getOpPackageName());
        assertEquals(null, groupId);
            fail("setSubscriptionGroup should fail with no permission.");
        } catch (SecurityException e) {
            // Expected result.
        }


        // With modify permission it should succeed.
        // With modify permission it should succeed.
        mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
        mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
        groupId = mSubscriptionControllerUT.setSubscriptionGroup(
        String groupId = mSubscriptionControllerUT.setSubscriptionGroup(
                subIdList, mContext.getOpPackageName());
                subIdList, mContext.getOpPackageName());
        assertNotEquals(null, groupId);
        assertNotEquals(null, groupId);


@@ -433,13 +437,6 @@ public class SubscriptionControllerTest extends TelephonyTest {
                subIdList, mContext.getOpPackageName());
                subIdList, mContext.getOpPackageName());
        assertNotEquals(null, newGroupId);
        assertNotEquals(null, newGroupId);
        assertNotEquals(groupId, newGroupId);
        assertNotEquals(groupId, newGroupId);

        // SubId 6 doesn't exist. Should fail.
        subIdList = new int[] {1, 6};
        mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
        groupId = mSubscriptionControllerUT.setSubscriptionGroup(
                subIdList, mContext.getOpPackageName());
        assertEquals(null, groupId);
    }
    }


    @Test
    @Test
@@ -447,7 +444,7 @@ public class SubscriptionControllerTest extends TelephonyTest {
    public void testSetSubscriptionGroupWithCarrierPrivilegePermission() throws Exception {
    public void testSetSubscriptionGroupWithCarrierPrivilegePermission() throws Exception {
        testInsertSim();
        testInsertSim();
        // Adding a second profile and mark as embedded.
        // Adding a second profile and mark as embedded.
        mSubscriptionControllerUT.addSubInfoRecord("test2", 0);
        mSubscriptionControllerUT.addSubInfoRecord("test2", 1);
        ContentValues values = new ContentValues();
        ContentValues values = new ContentValues();
        values.put(SubscriptionManager.IS_EMBEDDED, 1);
        values.put(SubscriptionManager.IS_EMBEDDED, 1);
        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
@@ -459,26 +456,38 @@ public class SubscriptionControllerTest extends TelephonyTest {


        int[] subIdList = new int[] {1, 2};
        int[] subIdList = new int[] {1, 2};
        // It should fail since it has no permission.
        // It should fail since it has no permission.
        String groupId = mSubscriptionControllerUT.setSubscriptionGroup(
        try {
            mSubscriptionControllerUT.setSubscriptionGroup(
                    subIdList, mContext.getOpPackageName());
                    subIdList, mContext.getOpPackageName());
        assertEquals(null, groupId);
            fail("setSubscriptionGroup should fail with no permission.");
        } catch (SecurityException e) {
            // Expected result.
        }


        // With modify permission it should succeed.
        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1);
        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1);
        groupId = mSubscriptionControllerUT.setSubscriptionGroup(
        try {
            mSubscriptionControllerUT.setSubscriptionGroup(
                    subIdList, mContext.getOpPackageName());
                    subIdList, mContext.getOpPackageName());
        assertEquals(null, groupId);
            fail("setSubscriptionGroup should fail with no permission on sub 2.");
        } catch (SecurityException e) {
            // Expected result.
        }


        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2);
        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2);
        groupId = mSubscriptionControllerUT.setSubscriptionGroup(
        String groupId = mSubscriptionControllerUT.setSubscriptionGroup(
                subIdList, mContext.getOpPackageName());
                subIdList, mContext.getOpPackageName());
        assertNotEquals(null, groupId);
        assertNotEquals(null, groupId);


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


        // Revoke carrier privilege of sub 2 but make it manageable by caller.
        // Put sub3 into slot 1 to make sub2 inactive.
        doReturn(false).when(mTelephonyManager).hasCarrierPrivileges(2);
        mContextFixture.addCallingOrSelfPermission(
                android.Manifest.permission.MODIFY_PHONE_STATE);
        mSubscriptionControllerUT.addSubInfoRecord("test3", 1);
        mContextFixture.removeCallingOrSelfPermission(
                android.Manifest.permission.MODIFY_PHONE_STATE);
        // As sub2 is inactive, it will checks carrier privilege against access rules in the db.
        doReturn(true).when(mSubscriptionManager).canManageSubscription(
        doReturn(true).when(mSubscriptionManager).canManageSubscription(
                eq(subInfoList.get(1)), anyString());
                eq(subInfoList.get(1)), anyString());


@@ -536,6 +545,42 @@ public class SubscriptionControllerTest extends TelephonyTest {
        assertEquals(true, opptSubList.get(0).isGroupDisabled());
        assertEquals(true, opptSubList.get(0).isGroupDisabled());
    }
    }


    @Test
    @SmallTest
    public void testSetSubscriptionGroup() throws Exception {
        testInsertSim();
        // Adding a second profile and mark as embedded.
        mSubscriptionControllerUT.addSubInfoRecord("test2", 0);
        ContentValues values = new ContentValues();
        values.put(SubscriptionManager.IS_EMBEDDED, 1);
        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null);
        mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();

        int[] subIdList = new int[] {1, 2};
        String groupUuid = mSubscriptionControllerUT.setSubscriptionGroup(
                subIdList, mContext.getOpPackageName());
        assertNotEquals(null, groupUuid);

        // Sub 1 and sub 2 should be in same group.
        List<SubscriptionInfo> infoList = mSubscriptionControllerUT
                .getSubscriptionsInGroup(1, mContext.getOpPackageName());
        assertNotEquals(null, infoList);
        assertEquals(2, infoList.size());
        assertEquals(1, infoList.get(0).getSubscriptionId());
        assertEquals(2, infoList.get(1).getSubscriptionId());

        // Remove group of sub 1.
        subIdList = new int[] {1};
        boolean result = mSubscriptionControllerUT.removeSubscriptionsFromGroup(
                subIdList, mContext.getOpPackageName());
        assertEquals(true, result);
        infoList = mSubscriptionControllerUT
                .getSubscriptionsInGroup(2, mContext.getOpPackageName());
        assertEquals(1, infoList.size());
        assertEquals(2, infoList.get(0).getSubscriptionId());
    }

    private void registerMockTelephonyRegistry() {
    private void registerMockTelephonyRegistry() {
        mServiceManagerMockedServices.put("telephony.registry", mTelephonyRegisteryMock);
        mServiceManagerMockedServices.put("telephony.registry", mTelephonyRegisteryMock);
        doReturn(mTelephonyRegisteryMock).when(mTelephonyRegisteryMock)
        doReturn(mTelephonyRegisteryMock).when(mTelephonyRegisteryMock)