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

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

Merge "Add API to set subscription group." am: a0e9db2d

am: 2d1a9b21

Change-Id: Ic298b42b0c5b33d2016f333306ca206b7205ad81
parents a22d4757 2d1a9b21
Loading
Loading
Loading
Loading
+111 −41
Original line number Original line Diff line number Diff line
@@ -16,6 +16,8 @@


package com.android.internal.telephony;
package com.android.internal.telephony;


import static android.content.pm.PackageManager.PERMISSION_GRANTED;

import android.Manifest;
import android.Manifest;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.AppOpsManager;
@@ -63,6 +65,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Objects;
import java.util.Set;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Collectors;


@@ -313,8 +316,8 @@ public class SubscriptionController extends ISub.Stub {
        }
        }
        boolean isOpportunistic = cursor.getInt(cursor.getColumnIndexOrThrow(
        boolean isOpportunistic = cursor.getInt(cursor.getColumnIndexOrThrow(
                SubscriptionManager.IS_OPPORTUNISTIC)) == 1;
                SubscriptionManager.IS_OPPORTUNISTIC)) == 1;
        int parentSubId = cursor.getInt(cursor.getColumnIndexOrThrow(
        String groupUUID = cursor.getString(cursor.getColumnIndexOrThrow(
                SubscriptionManager.PARENT_SUB_ID));
                SubscriptionManager.GROUP_UUID));


        if (VDBG) {
        if (VDBG) {
            String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId);
            String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId);
@@ -325,7 +328,7 @@ public class SubscriptionController extends ISub.Stub {
                    + " mcc:" + mcc + " mnc:" + mnc + " countIso:" + countryIso + " isEmbedded:"
                    + " mcc:" + mcc + " mnc:" + mnc + " countIso:" + countryIso + " isEmbedded:"
                    + isEmbedded + " accessRules:" + Arrays.toString(accessRules)
                    + isEmbedded + " accessRules:" + Arrays.toString(accessRules)
                    + " cardId:" + cardIdToPrint + " isOpportunistic:" + isOpportunistic
                    + " cardId:" + cardIdToPrint + " isOpportunistic:" + isOpportunistic
                    + " parentSubId:" + parentSubId);
                    + " groupUUID:" + groupUUID);
        }
        }


        // If line1number has been set to a different number, use it instead.
        // If line1number has been set to a different number, use it instead.
@@ -335,7 +338,7 @@ public class SubscriptionController extends ISub.Stub {
        }
        }
        return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
        return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
                nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
                nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
                isEmbedded, accessRules, cardId, isOpportunistic, parentSubId);
                isEmbedded, accessRules, cardId, isOpportunistic, groupUUID);
    }
    }


    /**
    /**
@@ -2025,7 +2028,6 @@ public class SubscriptionController extends ISub.Stub {
            case SubscriptionManager.CB_OPT_OUT_DIALOG:
            case SubscriptionManager.CB_OPT_OUT_DIALOG:
            case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
            case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
            case SubscriptionManager.IS_OPPORTUNISTIC:
            case SubscriptionManager.IS_OPPORTUNISTIC:
            case SubscriptionManager.PARENT_SUB_ID:
            case SubscriptionManager.VT_IMS_ENABLED:
            case SubscriptionManager.VT_IMS_ENABLED:
            case SubscriptionManager.WFC_IMS_ENABLED:
            case SubscriptionManager.WFC_IMS_ENABLED:
            case SubscriptionManager.WFC_IMS_MODE:
            case SubscriptionManager.WFC_IMS_MODE:
@@ -2085,7 +2087,7 @@ public class SubscriptionController extends ISub.Stub {
                        case SubscriptionManager.WFC_IMS_ROAMING_MODE:
                        case SubscriptionManager.WFC_IMS_ROAMING_MODE:
                        case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
                        case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
                        case SubscriptionManager.IS_OPPORTUNISTIC:
                        case SubscriptionManager.IS_OPPORTUNISTIC:
                        case SubscriptionManager.PARENT_SUB_ID:
                        case SubscriptionManager.GROUP_UUID:
                            resultValue = cursor.getInt(0) + "";
                            resultValue = cursor.getInt(0) + "";
                            break;
                            break;
                        default:
                        default:
@@ -2237,40 +2239,6 @@ public class SubscriptionController extends ISub.Stub {
                String.valueOf(opportunistic ? 1 : 0));
                String.valueOf(opportunistic ? 1 : 0));
    }
    }


    /**
     * Set parent subId (parentSubId) of another subscription (subId).
     * It's used in scenarios where a child ubscription is bundled with a
     * primary parent subscription. The child subscription will typically be opportunistic
     * and will be used to provide data services where available, with the parent being
     * the primary fallback subscription.
     *
     * @param parentSubId subId of its parent subscription.
     * @param subId the unique SubscriptionInfo index in database
     * @return the number of records updated
     */
    @Override
    public int setParentSubId(int parentSubId, int subId) {
        enforceModifyPhoneState("setParentSubId");
        final long token = Binder.clearCallingIdentity();

        try {
            if (!SubscriptionManager.isUsableSubIdValue(parentSubId)
                    || parentSubId > getAllSubInfoCount(mContext.getOpPackageName())
                    || parentSubId == subId) {
                if (DBG) {
                    logd("[setParentSubId]- fail with parentSubId " + parentSubId
                            + " subId " + subId);
                }
                return -1;
            }

            return setSubscriptionProperty(subId, SubscriptionManager.PARENT_SUB_ID,
                    String.valueOf(parentSubId));
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    @Override
    @Override
    public int setPreferredData(int subId) {
    public int setPreferredData(int subId) {
        enforceModifyPhoneState("setPreferredData");
        enforceModifyPhoneState("setPreferredData");
@@ -2306,6 +2274,108 @@ public class SubscriptionController extends ISub.Stub {
                callingPackage, mCacheOpportunisticSubInfoList);
                callingPackage, mCacheOpportunisticSubInfoList);
    }
    }


    /**
     * Inform SubscriptionManager that subscriptions in the list are bundled
     * as a group. Typically it's a primary subscription and an opportunistic
     * subscription. It should only affect multi-SIM scenarios where primary
     * and opportunistic subscriptions can be activated together.
     * Being in the same group means they might be activated or deactivated
     * together, some of them may be invisible to the users, etc.
     *
     * Caller will either have {@link android.Manifest.permission.MODIFY_PHONE_STATE}
     * permission or can manage all subscriptions in the list, according to their
     * access rules.
     *
     * @return groupUUID a UUID assigned to the subscription group. It returns
     * null if fails.
     *
     */
    @Override
    public String setSubscriptionGroup(int[] subIdList, String callingPackage) {
        boolean hasModifyPermission = mContext.checkCallingOrSelfPermission(
                android.Manifest.permission.MODIFY_PHONE_STATE) == PERMISSION_GRANTED;

        // If caller doesn't have modify permission or carrier privilege permission on certain
        // 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.
        Set<Integer> subIdCheckList = new HashSet<>();
        for (int subId : subIdList) {
            if (!mTelephonyManager.hasCarrierPrivileges(subId)) {
                subIdCheckList.add(subId);
            }
        }

        long identity = Binder.clearCallingIdentity();

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

            SubscriptionManager subscriptionManager = (SubscriptionManager)
                    mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
            List<SubscriptionInfo> subList = getSubInfo(null, null);

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

            if (!subIdCheckList.isEmpty()) {
                // Some SubId not found.
                StringBuilder subIdNotFound = new StringBuilder();
                for (int subId : subIdCheckList) {
                    subIdNotFound.append(subId + " ");
                }
                if (DBG) {
                    logdl("setSubscriptionGroup subId not existed: "
                            + subIdNotFound.toString());
                }

                return null;
            }

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

            // Selection should be: "in (subId1, subId2, ...)".
            StringBuilder selection = new StringBuilder();
            selection.append(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID);
            selection.append(" IN (");
            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);

            refreshCachedActiveSubscriptionInfoList();

            return groupUUID;
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    // Helper function of getOpportunisticSubscriptions and getActiveSubscriptionInfoList.
    // Helper function of getOpportunisticSubscriptions and getActiveSubscriptionInfoList.
    // They are doing similar things except operating on different cache.
    // They are doing similar things except operating on different cache.
    private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper(
    private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper(
@@ -2336,7 +2406,7 @@ public class SubscriptionController extends ISub.Stub {
                        try {
                        try {
                            return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext,
                            return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext,
                                    subscriptionInfo.getSubscriptionId(), callingPackage,
                                    subscriptionInfo.getSubscriptionId(), callingPackage,
                                    "getOpportunisticSubscriptions");
                                    "getSubscriptionInfoList");
                        } catch (SecurityException e) {
                        } catch (SecurityException e) {
                            return false;
                            return false;
                        }
                        }
+1 −1
Original line number Original line Diff line number Diff line
@@ -97,7 +97,7 @@ public class FakeTelephonyProvider extends MockContentProvider {
                    + SubscriptionManager.WFC_IMS_ROAMING_MODE + " INTEGER DEFAULT -1,"
                    + SubscriptionManager.WFC_IMS_ROAMING_MODE + " INTEGER DEFAULT -1,"
                    + SubscriptionManager.WFC_IMS_ROAMING_ENABLED + " INTEGER DEFAULT -1,"
                    + SubscriptionManager.WFC_IMS_ROAMING_ENABLED + " INTEGER DEFAULT -1,"
                    + SubscriptionManager.IS_OPPORTUNISTIC + " INTEGER DEFAULT 0,"
                    + SubscriptionManager.IS_OPPORTUNISTIC + " INTEGER DEFAULT 0,"
                    + SubscriptionManager.PARENT_SUB_ID + " INTEGER DEFAULT -1"
                    + SubscriptionManager.GROUP_UUID + " TEXT"
                    + ");";
                    + ");";
        }
        }


+85 −11
Original line number Original line Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.internal.telephony;


import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertFalse;
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;
@@ -27,6 +28,8 @@ import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;


import android.Manifest;
import android.content.ContentValues;
import android.content.Intent;
import android.content.Intent;
import android.os.Bundle;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserHandle;
@@ -49,6 +52,7 @@ public class SubscriptionControllerTest extends TelephonyTest {
    private String mCallingPackage;
    private String mCallingPackage;
    private SubscriptionController mSubscriptionControllerUT;
    private SubscriptionController mSubscriptionControllerUT;
    private MockContentResolver mMockContentResolver;
    private MockContentResolver mMockContentResolver;
    private FakeTelephonyProvider mFakeTelephonyProvider;
    @Mock
    @Mock
    private ITelephonyRegistry.Stub mTelephonyRegisteryMock;
    private ITelephonyRegistry.Stub mTelephonyRegisteryMock;


@@ -70,13 +74,15 @@ public class SubscriptionControllerTest extends TelephonyTest {


        mSubscriptionControllerUT.getInstance().updatePhonesAvailability(new Phone[]{mPhone});
        mSubscriptionControllerUT.getInstance().updatePhonesAvailability(new Phone[]{mPhone});
        mMockContentResolver = (MockContentResolver) mContext.getContentResolver();
        mMockContentResolver = (MockContentResolver) mContext.getContentResolver();
        mFakeTelephonyProvider = new FakeTelephonyProvider();
        mMockContentResolver.addProvider(SubscriptionManager.CONTENT_URI.getAuthority(),
        mMockContentResolver.addProvider(SubscriptionManager.CONTENT_URI.getAuthority(),
                new FakeTelephonyProvider());
                mFakeTelephonyProvider);


    }
    }


    @After
    @After
    public void tearDown() throws Exception {
    public void tearDown() throws Exception {
        mContextFixture.addCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
        /* should clear fake content provider and resolver here */
        /* should clear fake content provider and resolver here */
        mContext.getContentResolver().delete(SubscriptionManager.CONTENT_URI, null, null);
        mContext.getContentResolver().delete(SubscriptionManager.CONTENT_URI, null, null);


@@ -343,7 +349,7 @@ public class SubscriptionControllerTest extends TelephonyTest {
                .notifyOpportunisticSubscriptionInfoChanged();
                .notifyOpportunisticSubscriptionInfoChanged();


        testInsertSim();
        testInsertSim();
        testInsertSim2();
        mSubscriptionControllerUT.addSubInfoRecord("test2", 0);


        // Neither sub1 or sub2 are opportunistic. So getOpportunisticSubscriptions
        // Neither sub1 or sub2 are opportunistic. So getOpportunisticSubscriptions
        // should return empty list and no callback triggered.
        // should return empty list and no callback triggered.
@@ -374,16 +380,84 @@ public class SubscriptionControllerTest extends TelephonyTest {
                .notifyOpportunisticSubscriptionInfoChanged();
                .notifyOpportunisticSubscriptionInfoChanged();
    }
    }


    private void testInsertSim2() {
    @Test
        // verify there's already a SIM profile added.
    @SmallTest
        assertEquals(1, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage));
    public void testSetSubscriptionGroupWithModifyPermission() throws Exception {

        testInsertSim();
        int slotID = 0;
        mSubscriptionControllerUT.addSubInfoRecord("test2", 0);
        //insert one Subscription Info
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
        mSubscriptionControllerUT.addSubInfoRecord("test2", slotID);
        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);

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

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

        // Calling it again should generate a new group ID.
        String newGroupId = mSubscriptionControllerUT.setSubscriptionGroup(
                subIdList, mContext.getOpPackageName());
        assertNotEquals(null, 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);
    }


        //verify there is one sim
    @Test
        assertEquals(2, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage));
    @SmallTest
    public void testSetSubscriptionGroupWithCarrierPrivilegePermission() 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();

        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);

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

        // With modify permission it should succeed.
        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1);
        groupId = mSubscriptionControllerUT.setSubscriptionGroup(
                subIdList, mContext.getOpPackageName());
        assertEquals(null, groupId);

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

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

        // Revoke carrier privilege of sub 2 but make it manageable by caller.
        doReturn(false).when(mTelephonyManager).hasCarrierPrivileges(2);
        doReturn(true).when(mSubscriptionManager).canManageSubscription(
                eq(subInfoList.get(1)), anyString());

        String newGroupId = mSubscriptionControllerUT.setSubscriptionGroup(
                subIdList, mContext.getOpPackageName());
        assertNotEquals(null, newGroupId);
        assertNotEquals(groupId, newGroupId);
    }
    }


    private void registerMockTelephonyRegistry() {
    private void registerMockTelephonyRegistry() {