Loading src/java/com/android/internal/telephony/ims/ImsResolver.java +22 −2 Original line number Original line Diff line number Diff line Loading @@ -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) { Loading @@ -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; } } Loading Loading @@ -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) { Loading Loading @@ -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 { Loading Loading @@ -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 { Loading Loading @@ -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); } } Loading src/java/com/android/internal/telephony/ims/ImsServiceController.java +26 −1 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java +39 −0 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. * * Loading tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java +132 −5 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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() { Loading @@ -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)); } } /** /** Loading Loading @@ -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() { Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading
src/java/com/android/internal/telephony/ims/ImsResolver.java +22 −2 Original line number Original line Diff line number Diff line Loading @@ -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) { Loading @@ -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; } } Loading Loading @@ -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) { Loading Loading @@ -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 { Loading Loading @@ -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 { Loading Loading @@ -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); } } Loading
src/java/com/android/internal/telephony/ims/ImsServiceController.java +26 −1 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading
src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java +39 −0 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. * * Loading
tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java +132 −5 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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() { Loading @@ -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)); } } /** /** Loading Loading @@ -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() { Loading Loading @@ -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); Loading Loading @@ -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; Loading