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

Commit 441c0d9a authored by Brad Ebinger's avatar Brad Ebinger Committed by android-build-merger
Browse files

Merge "Integrates ImsService shouldProcessCall API" am: 1f6b16a8

am: cd71640a

Change-Id: I38eff5f91e034107b0c539bc49b6f1392fa91756
parents 5addc72c cd71640a
Loading
Loading
Loading
Loading
+22 −2
Original line number Original line Diff line number Diff line
@@ -104,6 +104,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal


            ImsServiceInfo that = (ImsServiceInfo) o;
            ImsServiceInfo that = (ImsServiceInfo) o;


            if (supportsEmergencyMmTel != that.supportsEmergencyMmTel) return false;
            if (name != null ? !name.equals(that.name) : that.name != null) return false;
            if (name != null ? !name.equals(that.name) : that.name != null) return false;
            if (supportedFeatures != null ? !supportedFeatures.equals(that.supportedFeatures)
            if (supportedFeatures != null ? !supportedFeatures.equals(that.supportedFeatures)
                    : that.supportedFeatures != null) {
                    : that.supportedFeatures != null) {
@@ -117,6 +118,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal
        public int hashCode() {
        public int hashCode() {
            int result = name != null ? name.hashCode() : 0;
            int result = name != null ? name.hashCode() : 0;
            result = 31 * result + (supportedFeatures != null ? supportedFeatures.hashCode() : 0);
            result = 31 * result + (supportedFeatures != null ? supportedFeatures.hashCode() : 0);
            result = 31 * result + (supportsEmergencyMmTel ? 1 : 0);
            result = 31 * result + (controllerFactory != null ? controllerFactory.hashCode() : 0);
            result = 31 * result + (controllerFactory != null ? controllerFactory.hashCode() : 0);
            return result;
            return result;
        }
        }
@@ -447,6 +449,19 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal
        return null;
        return null;
    }
    }


    /**
     * @return true if the ImsService associated with this slot supports emergency calling over IMS,
     * false if the call should be placed over circuit switch instead.
     */
    public boolean isEmergencyMmTelAvailable(int slotId) {
        ImsServiceController controller = getImsServiceController(slotId, ImsFeature.FEATURE_MMTEL);
        if (controller != null) {
            return controller.canPlaceEmergencyCalls();
        }
        Log.w(TAG, "isEmergencyMmTelAvailable: No controller found for slot " + slotId);
        return false;
    }

    @VisibleForTesting
    @VisibleForTesting
    public ImsServiceController getImsServiceController(int slotId, int feature) {
    public ImsServiceController getImsServiceController(int slotId, int feature) {
        if (slotId < 0 || slotId >= mNumSlots) {
        if (slotId < 0 || slotId >= mNumSlots) {
@@ -545,7 +560,9 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal
                // update features in the cache
                // update features in the cache
                Log.i(TAG, "Updating features in cached ImsService: " + info.name);
                Log.i(TAG, "Updating features in cached ImsService: " + info.name);
                Log.d(TAG, "Updating features - Old features: " + match.get().supportedFeatures
                Log.d(TAG, "Updating features - Old features: " + match.get().supportedFeatures
                        + " new features: " + info.supportedFeatures);
                        + " new features: " + info.supportedFeatures
                        + ", supports emergency: " + info.supportsEmergencyMmTel);
                match.get().supportsEmergencyMmTel = info.supportsEmergencyMmTel;
                match.get().supportedFeatures = info.supportedFeatures;
                match.get().supportedFeatures = info.supportedFeatures;
                updateImsServiceFeatures(info);
                updateImsServiceFeatures(info);
            } else {
            } else {
@@ -638,6 +655,8 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal
        Optional<ImsServiceController> o = getControllerByServiceInfo(mActiveControllers,
        Optional<ImsServiceController> o = getControllerByServiceInfo(mActiveControllers,
                newInfo);
                newInfo);
        if (o.isPresent()) {
        if (o.isPresent()) {
            Log.d(TAG, "Updating canPlaceEmergencyCalls: " + newInfo.supportsEmergencyMmTel);
            o.get().setCanPlaceEmergencyCalls(newInfo.supportsEmergencyMmTel);
            Log.i(TAG, "Updating features for ImsService: " + o.get().getComponentName());
            Log.i(TAG, "Updating features for ImsService: " + o.get().getComponentName());
            HashSet<Pair<Integer, Integer>> features = calculateFeaturesToCreate(newInfo);
            HashSet<Pair<Integer, Integer>> features = calculateFeaturesToCreate(newInfo);
            try {
            try {
@@ -671,10 +690,11 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal
        }
        }
        ImsServiceController controller = info.controllerFactory.create(mContext, info.name, this);
        ImsServiceController controller = info.controllerFactory.create(mContext, info.name, this);
        HashSet<Pair<Integer, Integer>> features = calculateFeaturesToCreate(info);
        HashSet<Pair<Integer, Integer>> features = calculateFeaturesToCreate(info);
        controller.setCanPlaceEmergencyCalls(info.supportsEmergencyMmTel);
        // Only bind if there are features that will be created by the service.
        // Only bind if there are features that will be created by the service.
        if (features.size() > 0) {
        if (features.size() > 0) {
            Log.i(TAG, "Binding ImsService: " + controller.getComponentName() + " with features: "
            Log.i(TAG, "Binding ImsService: " + controller.getComponentName() + " with features: "
                    + features);
                    + features + ", supports emergency calling: " + info.supportsEmergencyMmTel);
            controller.bind(features);
            controller.bind(features);
            mActiveControllers.add(controller);
            mActiveControllers.add(controller);
        }
        }
+26 −1
Original line number Original line Diff line number Diff line
@@ -179,13 +179,14 @@ public class ImsServiceController {
    // Binder interfaces to the features set in mImsFeatures;
    // Binder interfaces to the features set in mImsFeatures;
    private HashSet<ImsFeatureContainer> mImsFeatureBinders = new HashSet<>();
    private HashSet<ImsFeatureContainer> mImsFeatureBinders = new HashSet<>();
    private IImsServiceController mIImsServiceController;
    private IImsServiceController mIImsServiceController;
    // Easier for testing.
    private IBinder mImsServiceControllerBinder;
    private IBinder mImsServiceControllerBinder;
    private ImsServiceConnection mImsServiceConnection;
    private ImsServiceConnection mImsServiceConnection;
    private ImsDeathRecipient mImsDeathRecipient;
    private ImsDeathRecipient mImsDeathRecipient;
    private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = new HashSet<>();
    private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = new HashSet<>();
    // Only added or removed, never accessed on purpose.
    // Only added or removed, never accessed on purpose.
    private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>();
    private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>();
    // Determines whether or not emergency calls can be placed through this ImsService.
    private boolean mCanPlaceEmergencyCalls = false;


    protected final Object mLock = new Object();
    protected final Object mLock = new Object();
    protected final Context mContext;
    protected final Context mContext;
@@ -409,6 +410,30 @@ public class ImsServiceController {
        }
        }
    }
    }


    /**
     * Sets the ability for the ImsService to place emergency calls. This is controlled by the
     * {@link ImsFeature#FEATURE_EMERGENCY_MMTEL} feature attribute, which is set either as metadata
     * in the AndroidManifest service definition or via dynamic query in
     * {@link ImsService#querySupportedImsFeatures()}.
     */
    public void setCanPlaceEmergencyCalls(boolean canPlaceEmergencyCalls) {
        synchronized (mLock) {
            mCanPlaceEmergencyCalls = canPlaceEmergencyCalls;
        }
    }

    /**
     * Whether or not the ImsService connected to this controller is able to place emergency calls
     * over IMS.
     * @return true if this ImsService can place emergency calls over IMS, false if the framework
     * should instead place the emergency call over circuit switch.
     */
    public boolean canPlaceEmergencyCalls() {
        synchronized (mLock) {
            return mCanPlaceEmergencyCalls;
        }
    }

    @VisibleForTesting
    @VisibleForTesting
    public IImsServiceController getImsServiceController() {
    public IImsServiceController getImsServiceController() {
        return mIImsServiceController;
        return mIImsServiceController;
+39 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,8 @@


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


import static com.android.internal.telephony.Phone.CS_FALLBACK;

import android.content.BroadcastReceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
@@ -883,6 +885,11 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
        boolean isPhoneInEcmMode = isPhoneInEcbMode();
        boolean isPhoneInEcmMode = isPhoneInEcbMode();
        boolean isEmergencyNumber = mPhoneNumberUtilsProxy.isEmergencyNumber(dialString);
        boolean isEmergencyNumber = mPhoneNumberUtilsProxy.isEmergencyNumber(dialString);


        if (!shouldNumberBePlacedOnIms(isEmergencyNumber, dialString)) {
            Rlog.i(LOG_TAG, "dial: shouldNumberBePlacedOnIms = false");
            throw new CallStateException(CS_FALLBACK);
        }

        int clirMode = dialArgs.clirMode;
        int clirMode = dialArgs.clirMode;
        int videoState = dialArgs.videoState;
        int videoState = dialArgs.videoState;


@@ -996,6 +1003,38 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
        return mImsManager.isServiceReady();
        return mImsManager.isServiceReady();
    }
    }


    private boolean shouldNumberBePlacedOnIms(boolean isEmergency, String number) {
        int processCallResult;
        try {
            if (mImsManager != null) {
                processCallResult = mImsManager.shouldProcessCall(isEmergency,
                        new String[]{number});
                Rlog.i(LOG_TAG, "shouldProcessCall: number: " + Rlog.pii(LOG_TAG, number)
                        + ", result: " + processCallResult);
            } else {
                Rlog.w(LOG_TAG, "ImsManager unavailable, shouldProcessCall returning false.");
                return false;
            }
        } catch (ImsException e) {
            Rlog.w(LOG_TAG, "ImsService unavailable, shouldProcessCall returning false.");
            return false;
        }
        switch(processCallResult) {
            case MmTelFeature.PROCESS_CALL_IMS: {
                // The ImsService wishes to place the call over IMS
                return true;
            }
            case MmTelFeature.PROCESS_CALL_CSFB: {
                Rlog.i(LOG_TAG, "shouldProcessCall: place over CSFB instead.");
                return false;
            }
            default: {
                Rlog.w(LOG_TAG, "shouldProcessCall returned unknown result.");
                return false;
            }
        }
    }

    /**
    /**
     * Caches frequently used carrier configuration items locally.
     * Caches frequently used carrier configuration items locally.
     *
     *
+132 −5
Original line number Original line Diff line number Diff line
@@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;
@@ -42,10 +43,10 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.RemoteException;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.CarrierConfigManager;
import android.telephony.CarrierConfigManager;
import android.telephony.ims.ImsService;
import android.telephony.ims.ImsService;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.ImsFeature;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Pair;
import android.util.Pair;


@@ -114,7 +115,10 @@ public class ImsResolverTest extends ImsTestBase {
        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
        features.add(ImsResolver.METADATA_RCS_FEATURE);
        features.add(ImsResolver.METADATA_RCS_FEATURE);
        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
        when(mMockPM.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(info);
        // Only return info if not using the compat argument
        when(mMockPM.queryIntentServicesAsUser(
                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
                anyInt(), anyInt())).thenReturn(info);
        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());


        mTestImsResolver.populateCacheAndStartBind();
        mTestImsResolver.populateCacheAndStartBind();
@@ -142,7 +146,55 @@ public class ImsResolverTest extends ImsTestBase {
        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
        features.add(ImsResolver.METADATA_RCS_FEATURE);
        features.add(ImsResolver.METADATA_RCS_FEATURE);
        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, features, true));
        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, features, true));
        when(mMockPM.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(info);
        // Only return info if not using the compat argument
        when(mMockPM.queryIntentServicesAsUser(
                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
                anyInt(), anyInt())).thenReturn(info);
        ImsServiceController controller = mock(ImsServiceController.class);
        mTestImsResolver.setImsServiceControllerFactory(
                new ImsResolver.ImsServiceControllerFactory() {
                    @Override
                    public String getServiceInterface() {
                        return ImsService.SERVICE_INTERFACE;
                    }

                    @Override
                    public ImsServiceController create(Context context, ComponentName componentName,
                            ImsServiceController.ImsServiceControllerCallbacks callbacks) {
                        when(controller.getComponentName()).thenReturn(componentName);
                        return controller;
                    }
                });


        mTestImsResolver.populateCacheAndStartBind();

        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
        verify(controller).bind(convertToHashSet(features, 0));
        verify(controller, never()).unbind();
        assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName());
    }

    /**
     * Creates a carrier ImsService with a manifest that defines METADATA_EMERGENCY_MMTEL_FEATURE,
     * ensure that the controller sets this capability.
     */
    @Test
    @SmallTest
    public void testCarrierPackageBindWithEmergencyCalling() throws RemoteException {
        setupResolver(1/*numSlots*/);
        // Set CarrierConfig default package name and make it available to the package manager
        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
        List<ResolveInfo> info = new ArrayList<>();
        Set<String> features = new HashSet<>();
        features.add(ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE);
        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
        features.add(ImsResolver.METADATA_RCS_FEATURE);
        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, features, true));
        // Only return info if not using the compat argument
        when(mMockPM.queryIntentServicesAsUser(
                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
                anyInt(), anyInt())).thenReturn(info);
        ImsServiceController controller = mock(ImsServiceController.class);
        ImsServiceController controller = mock(ImsServiceController.class);
        mTestImsResolver.setImsServiceControllerFactory(
        mTestImsResolver.setImsServiceControllerFactory(
                new ImsResolver.ImsServiceControllerFactory() {
                new ImsResolver.ImsServiceControllerFactory() {
@@ -161,11 +213,78 @@ public class ImsResolverTest extends ImsTestBase {




        mTestImsResolver.populateCacheAndStartBind();
        mTestImsResolver.populateCacheAndStartBind();
        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
        verify(controller).bind(convertToHashSet(features, 0));
        verify(controller, never()).unbind();
        assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName());


        verify(controller).setCanPlaceEmergencyCalls(eq(true));
    }

    /**
     * Creates a carrier ImsService with a manifest that doesn't define
     * METADATA_EMERGENCY_MMTEL_FEATURE and then update the ImsService to define it. Ensure that the
     * controller sets this capability once enabled.
     */
    @Test
    @SmallTest
    public void testCarrierPackageChangeEmergencyCalling() throws RemoteException {
        setupResolver(1/*numSlots*/);
        // Set CarrierConfig default package name and make it available to the package manager
        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
        List<ResolveInfo> info = new ArrayList<>();
        Set<String> features = new HashSet<>();
        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, features, true));
        // Only return info if not using the compat argument
        when(mMockPM.queryIntentServicesAsUser(
                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
                anyInt(), anyInt())).thenReturn(info);
        ImsServiceController controller = mock(ImsServiceController.class);
        mTestImsResolver.setImsServiceControllerFactory(
                new ImsResolver.ImsServiceControllerFactory() {
                    @Override
                    public String getServiceInterface() {
                        return ImsService.SERVICE_INTERFACE;
                    }

                    @Override
                    public ImsServiceController create(Context context, ComponentName componentName,
                            ImsServiceController.ImsServiceControllerCallbacks callbacks) {
                        when(controller.getComponentName()).thenReturn(componentName);
                        return controller;
                    }
                });

        // Bind without emergency calling
        mTestImsResolver.populateCacheAndStartBind();
        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
        verify(controller).bind(convertToHashSet(features, 0));
        verify(controller).bind(convertToHashSet(features, 0));
        verify(controller, never()).unbind();
        verify(controller, never()).unbind();
        assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName());
        assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName());
        // ensure emergency calling is disabled
        verify(controller, never()).setCanPlaceEmergencyCalls(eq(true));

        // Tell package manager that app has changed and service now supports emergency calling
        Set<String> newFeatures = new HashSet<>();
        newFeatures.add(ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE);
        newFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
        info.clear();
        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, newFeatures, true));

        Intent addPackageIntent = new Intent();
        addPackageIntent.setAction(Intent.ACTION_PACKAGE_ADDED);
        addPackageIntent.setData(new Uri.Builder().scheme("package")
                .opaquePart(TEST_CARRIER_DEFAULT_NAME.getPackageName()).build());
        mTestPackageBroadcastReceiver.onReceive(null, addPackageIntent);
        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);

        //Verify new feature is added to the carrier override.
        // add all features for slot 0
        HashSet<Pair<Integer, Integer>> newCarrierFeatureSet =
                convertToHashSet(newFeatures, 0);
        verify(controller, atLeastOnce()).changeImsServiceFeatures(newCarrierFeatureSet);
        verify(controller).setCanPlaceEmergencyCalls(eq(true));
    }
    }


    /**
    /**
@@ -222,7 +341,10 @@ public class ImsResolverTest extends ImsTestBase {
        // Use device default package, which will load the ImsService that the device provides
        // Use device default package, which will load the ImsService that the device provides
        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, features, true));
        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, features, true));
        when(mMockPM.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(info);
        // Only return info if not using the compat argument
        when(mMockPM.queryIntentServicesAsUser(
                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
                anyInt(), anyInt())).thenReturn(info);
        ImsServiceController controller = mock(ImsServiceController.class);
        ImsServiceController controller = mock(ImsServiceController.class);
        mTestImsResolver.setImsServiceControllerFactory(
        mTestImsResolver.setImsServiceControllerFactory(
                new ImsResolver.ImsServiceControllerFactory() {
                new ImsResolver.ImsServiceControllerFactory() {
@@ -272,7 +394,10 @@ public class ImsResolverTest extends ImsTestBase {
        // Use device default package, which will load the ImsService that the device provides
        // Use device default package, which will load the ImsService that the device provides
        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, true));
        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, true));
        when(mMockPM.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(info);
        // Only return info if not using the compat argument
        when(mMockPM.queryIntentServicesAsUser(
                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
                anyInt(), anyInt())).thenReturn(info);
        ImsServiceController deviceController = mock(ImsServiceController.class);
        ImsServiceController deviceController = mock(ImsServiceController.class);
        ImsServiceController carrierController = mock(ImsServiceController.class);
        ImsServiceController carrierController = mock(ImsServiceController.class);
        setImsServiceControllerFactory(deviceController, carrierController);
        setImsServiceControllerFactory(deviceController, carrierController);
@@ -899,6 +1024,8 @@ public class ImsResolverTest extends ImsTestBase {


    private HashSet<Pair<Integer, Integer>> convertToHashSet(Set<String> features, int subId) {
    private HashSet<Pair<Integer, Integer>> convertToHashSet(Set<String> features, int subId) {
        HashSet<Pair<Integer, Integer>> featureSet = features.stream()
        HashSet<Pair<Integer, Integer>> featureSet = features.stream()
                // We do not count this as a valid feature set member.
                .filter(f -> !ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE.equals(f))
                .map(f -> new Pair<>(subId, metadataStringToFeature(f)))
                .map(f -> new Pair<>(subId, metadataStringToFeature(f)))
                .collect(Collectors.toCollection(HashSet::new));
                .collect(Collectors.toCollection(HashSet::new));
        return featureSet;
        return featureSet;