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

Commit 1f6b16a8 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Integrates ImsService shouldProcessCall API"

parents ebbcd262 91cd5679
Loading
Loading
Loading
Loading
+22 −2
Original line number Diff line number Diff line
@@ -104,6 +104,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal

            ImsServiceInfo that = (ImsServiceInfo) o;

            if (supportsEmergencyMmTel != that.supportsEmergencyMmTel) return false;
            if (name != null ? !name.equals(that.name) : that.name != null) return false;
            if (supportedFeatures != null ? !supportedFeatures.equals(that.supportedFeatures)
                    : that.supportedFeatures != null) {
@@ -117,6 +118,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal
        public int hashCode() {
            int result = name != null ? name.hashCode() : 0;
            result = 31 * result + (supportedFeatures != null ? supportedFeatures.hashCode() : 0);
            result = 31 * result + (supportsEmergencyMmTel ? 1 : 0);
            result = 31 * result + (controllerFactory != null ? controllerFactory.hashCode() : 0);
            return result;
        }
@@ -447,6 +449,19 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal
        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
    public ImsServiceController getImsServiceController(int slotId, int feature) {
        if (slotId < 0 || slotId >= mNumSlots) {
@@ -545,7 +560,9 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal
                // update features in the cache
                Log.i(TAG, "Updating features in cached ImsService: " + info.name);
                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;
                updateImsServiceFeatures(info);
            } else {
@@ -638,6 +655,8 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal
        Optional<ImsServiceController> o = getControllerByServiceInfo(mActiveControllers,
                newInfo);
        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());
            HashSet<Pair<Integer, Integer>> features = calculateFeaturesToCreate(newInfo);
            try {
@@ -671,10 +690,11 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal
        }
        ImsServiceController controller = info.controllerFactory.create(mContext, info.name, this);
        HashSet<Pair<Integer, Integer>> features = calculateFeaturesToCreate(info);
        controller.setCanPlaceEmergencyCalls(info.supportsEmergencyMmTel);
        // Only bind if there are features that will be created by the service.
        if (features.size() > 0) {
            Log.i(TAG, "Binding ImsService: " + controller.getComponentName() + " with features: "
                    + features);
                    + features + ", supports emergency calling: " + info.supportsEmergencyMmTel);
            controller.bind(features);
            mActiveControllers.add(controller);
        }
+26 −1
Original line number Diff line number Diff line
@@ -179,13 +179,14 @@ public class ImsServiceController {
    // Binder interfaces to the features set in mImsFeatures;
    private HashSet<ImsFeatureContainer> mImsFeatureBinders = new HashSet<>();
    private IImsServiceController mIImsServiceController;
    // Easier for testing.
    private IBinder mImsServiceControllerBinder;
    private ImsServiceConnection mImsServiceConnection;
    private ImsDeathRecipient mImsDeathRecipient;
    private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = new HashSet<>();
    // Only added or removed, never accessed on purpose.
    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 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
    public IImsServiceController getImsServiceController() {
        return mIImsServiceController;
+39 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.internal.telephony.imsphone;

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

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -846,6 +848,11 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
        boolean isPhoneInEcmMode = isPhoneInEcbMode();
        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 videoState = dialArgs.videoState;

@@ -959,6 +966,38 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
        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.
     *
+132 −5
Original line number 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.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -42,10 +43,10 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.CarrierConfigManager;
import android.telephony.ims.ImsService;
import android.telephony.ims.feature.ImsFeature;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Pair;

@@ -114,7 +115,10 @@ public class ImsResolverTest extends ImsTestBase {
        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
        features.add(ImsResolver.METADATA_RCS_FEATURE);
        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());

        mTestImsResolver.populateCacheAndStartBind();
@@ -142,7 +146,55 @@ public class ImsResolverTest extends ImsTestBase {
        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
        features.add(ImsResolver.METADATA_RCS_FEATURE);
        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);
        mTestImsResolver.setImsServiceControllerFactory(
                new ImsResolver.ImsServiceControllerFactory() {
@@ -161,11 +213,78 @@ public class ImsResolverTest extends ImsTestBase {


        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);
        verify(controller).bind(convertToHashSet(features, 0));
        verify(controller, never()).unbind();
        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
        info.add(getResolveInfo(TEST_DEVICE_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() {
@@ -272,7 +394,10 @@ public class ImsResolverTest extends ImsTestBase {
        // 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_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 carrierController = mock(ImsServiceController.class);
        setImsServiceControllerFactory(deviceController, carrierController);
@@ -899,6 +1024,8 @@ public class ImsResolverTest extends ImsTestBase {

    private HashSet<Pair<Integer, Integer>> convertToHashSet(Set<String> features, int subId) {
        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)))
                .collect(Collectors.toCollection(HashSet::new));
        return featureSet;