Loading src/java/com/android/internal/telephony/ims/ImsResolver.java +21 −44 Original line number Diff line number Diff line Loading @@ -102,7 +102,6 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal @VisibleForTesting public static class ImsServiceInfo { public ComponentName name; public boolean supportsEmergencyMmTel = false; // Determines if features were created from metadata in the manifest or through dynamic // query. public boolean featureFromMetadata = true; Loading @@ -124,19 +123,6 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal } void replaceFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> newFeatures) { // Emergency is a special case, filter out and if any slot supports it, set it to true. // TODO: support per-slot emergency call filtering List<ImsFeatureConfiguration.FeatureSlotPair> emergencyFeatures = newFeatures.stream() .filter(f -> f.featureType == ImsFeature.FEATURE_EMERGENCY_MMTEL) .collect(Collectors.toList()); if (!emergencyFeatures.isEmpty()) { Log.i(TAG, "replaceFeatures: emergency calls enabled."); supportsEmergencyMmTel = true; newFeatures.removeAll(emergencyFeatures); } else { Log.i(TAG, "replaceFeatures: emergency calls disabled."); supportsEmergencyMmTel = false; } mSupportedFeatures.clear(); mSupportedFeatures.addAll(newFeatures); } Loading @@ -153,7 +139,6 @@ 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 (!mSupportedFeatures.equals(that.mSupportedFeatures)) { return false; Loading @@ -167,7 +152,6 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal // We do not include mSupportedFeatures in hashcode because the internal structure // changes after adding. int result = name != null ? name.hashCode() : 0; result = 31 * result + (supportsEmergencyMmTel ? 1 : 0); result = 31 * result + (controllerFactory != null ? controllerFactory.hashCode() : 0); return result; } Loading @@ -185,8 +169,6 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal res.append(feature.featureType); res.append(") "); } res.append("], supportsEmergency="); res.append(supportsEmergencyMmTel); return res.toString(); } } Loading Loading @@ -571,19 +553,6 @@ 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) { Loading Loading @@ -619,7 +588,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal ImsServiceController controller = getImsServiceController(slotId, feature); if (controller != null) { controller.addImsServiceFeatureListener(callback); controller.addImsServiceFeatureCallback(callback); return controller; } return null; Loading Loading @@ -791,14 +760,11 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal // features because it is not carrier/device. HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = calculateFeaturesToCreate(newInfo); if (features.size() > 0) { if (shouldFeaturesCauseBind(features)) { try { if (controller != null) { Log.i(TAG, "Updating features for ImsService: " + controller.getComponentName()); Log.d(TAG, "Updating canPlaceEmergencyCalls: " + newInfo.supportsEmergencyMmTel); controller.setCanPlaceEmergencyCalls(newInfo.supportsEmergencyMmTel); Log.d(TAG, "Updating Features - New Features: " + features); controller.changeImsServiceFeatures(features); } else { Loading Loading @@ -835,12 +801,11 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal private void bindImsServiceWithFeatures(ImsServiceInfo info, HashSet<ImsFeatureConfiguration.FeatureSlotPair> features) { // Only bind if there are features that will be created by the service. if (features.size() > 0) { if (shouldFeaturesCauseBind(features)) { // Check to see if an active controller already exists ImsServiceController controller = getControllerByServiceInfo(mActiveControllers, info); if (controller != null) { Log.i(TAG, "ImsService connection exists, updating features " + features + ", updating supports emergency calling: " + info.supportsEmergencyMmTel); Log.i(TAG, "ImsService connection exists, updating features " + features); try { controller.changeImsServiceFeatures(features); // Features have been set, there was an error adding/removing. When the Loading @@ -851,11 +816,9 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal } else { controller = info.controllerFactory.create(mContext, info.name, this); Log.i(TAG, "Binding ImsService: " + controller.getComponentName() + " with features: " + features + ", supports emergency calling: " + info.supportsEmergencyMmTel); + " with features: " + features); controller.bind(features); } controller.setCanPlaceEmergencyCalls(info.supportsEmergencyMmTel); mActiveControllers.put(info.name, controller); } } Loading Loading @@ -937,6 +900,20 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal removeImsController(slotId, feature); } /** * Determines if the features specified should cause a bind or keep a binding active to an * ImsService. * @return true if MMTEL or RCS features are present, false if they are not or only * EMERGENCY_MMTEL is specified. */ private boolean shouldFeaturesCauseBind( HashSet<ImsFeatureConfiguration.FeatureSlotPair> features) { long bindableFeatures = features.stream() // remove all emergency features .filter(f -> f.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL).count(); return bindableFeatures > 0; } // Possibly rebind to another ImsService if currently installed ImsServices were changed or if // the SIM card has changed. // Called from the handler ONLY Loading Loading @@ -1129,7 +1106,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal ImsServiceInfo info = new ImsServiceInfo(mNumSlots); info.name = mStaticComponent; info.controllerFactory = mImsServiceControllerFactoryStaticBindingCompat; info.supportsEmergencyMmTel = true; info.addFeatureForAllSlots(ImsFeature.FEATURE_EMERGENCY_MMTEL); info.addFeatureForAllSlots(ImsFeature.FEATURE_MMTEL); infos.add(info); return infos; Loading Loading @@ -1164,7 +1141,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal if (serviceInfo.metaData != null) { if (serviceInfo.metaData.getBoolean(METADATA_EMERGENCY_MMTEL_FEATURE, false)) { info.supportsEmergencyMmTel = true; info.addFeatureForAllSlots(ImsFeature.FEATURE_EMERGENCY_MMTEL); } if (serviceInfo.metaData.getBoolean(METADATA_MMTEL_FEATURE, false)) { info.addFeatureForAllSlots(ImsFeature.FEATURE_MMTEL); Loading src/java/com/android/internal/telephony/ims/ImsServiceController.java +53 −53 Original line number Diff line number Diff line Loading @@ -154,11 +154,11 @@ public class ImsServiceController { */ public interface ImsServiceControllerCallbacks { /** * Called by ImsServiceController when a new feature has been created. * Called by ImsServiceController when a new MMTEL or RCS feature has been created. */ void imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller); /** * Called by ImsServiceController when a new feature has been removed. * Called by ImsServiceController when a new MMTEL or RCS feature has been removed. */ void imsServiceFeatureRemoved(int slotId, int feature, ImsServiceController controller); } Loading Loading @@ -203,8 +203,6 @@ public class ImsServiceController { 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; Loading Loading @@ -393,7 +391,7 @@ public class ImsServiceController { } // Clean up all features changeImsServiceFeatures(new HashSet<>()); removeImsServiceFeatureListener(); removeImsServiceFeatureCallbacks(); mImsServiceControllerBinder.unlinkToDeath(mImsDeathRecipient, 0); Log.i(LOG_TAG, "Unbinding ImsService: " + mComponentName); mContext.unbindService(mImsServiceConnection); Loading Loading @@ -434,30 +432,6 @@ 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; Loading @@ -480,9 +454,20 @@ public class ImsServiceController { /** * Add a callback to ImsManager that signals a new feature that the ImsServiceProxy can handle. */ public void addImsServiceFeatureListener(IImsServiceFeatureCallback callback) { public void addImsServiceFeatureCallback(IImsServiceFeatureCallback callback) { synchronized (mLock) { mImsStatusCallbacks.add(callback); if (mImsFeatures == null || mImsFeatures.isEmpty()) { return; } // notify the new status callback of the features that are available. try { for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) { callback.imsFeatureCreated(i.slotId, i.featureType); } } catch (RemoteException e) { Log.w(LOG_TAG, "addImsServiceFeatureCallback: exception notifying callback"); } } } Loading Loading @@ -587,7 +572,8 @@ public class ImsServiceController { return mIImsServiceController != null; } private void removeImsServiceFeatureListener() { @VisibleForTesting public void removeImsServiceFeatureCallbacks() { synchronized (mLock) { mImsStatusCallbacks.clear(); } Loading Loading @@ -671,6 +657,7 @@ public class ImsServiceController { Log.w(LOG_TAG, "addImsServiceFeature called with null values."); return; } if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) { ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.slotId, featurePair.featureType); mFeatureStatusCallbacks.add(c); Loading @@ -679,7 +666,12 @@ public class ImsServiceController { addImsFeatureBinder(featurePair.slotId, featurePair.featureType, f); // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController mCallbacks.imsServiceFeatureCreated(featurePair.slotId, featurePair.featureType, this); // Send callback to ImsServiceProxy to change supported ImsFeatures } else { // Don't update ImsService for emergency MMTEL feature. Log.i(LOG_TAG, "supports emergency calling on slot " + featurePair.slotId); } // Send callback to ImsServiceProxy to change supported ImsFeatures including emergency // MMTEL state. sendImsFeatureCreatedCallback(featurePair.slotId, featurePair.featureType); } Loading @@ -690,6 +682,7 @@ public class ImsServiceController { Log.w(LOG_TAG, "removeImsServiceFeature called with null values."); return; } if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) { ImsFeatureStatusCallback callbackToRemove = mFeatureStatusCallbacks.stream().filter(c -> c.mSlotId == featurePair.slotId && c.mFeatureType == featurePair.featureType) .findFirst().orElse(null); Loading @@ -702,6 +695,10 @@ public class ImsServiceController { removeImsFeatureBinder(featurePair.slotId, featurePair.featureType); // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController mCallbacks.imsServiceFeatureRemoved(featurePair.slotId, featurePair.featureType, this); } else { // Don't update ImsService for emergency MMTEL feature. Log.i(LOG_TAG, "doesn't support emergency calling on slot " + featurePair.slotId); } // Send callback to ImsServiceProxy to change supported ImsFeatures // Ensure that ImsServiceProxy callback occurs after ImsResolver callback. If an // ImsManager requests the ImsService while it is being removed in ImsResolver, this Loading Loading @@ -759,7 +756,10 @@ public class ImsServiceController { } synchronized (mLock) { for (ImsFeatureConfiguration.FeatureSlotPair feature : mImsFeatures) { if (feature.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) { // don't update ImsServiceController for emergency MMTEL. mCallbacks.imsServiceFeatureRemoved(feature.slotId, feature.featureType, this); } sendImsFeatureRemovedCallback(feature.slotId, feature.featureType); } } Loading tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java +3 −8 Original line number Diff line number Diff line Loading @@ -202,8 +202,6 @@ public class ImsResolverTest extends ImsTestBase { // Set CarrierConfig default package name and make it available to the package manager setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName()); HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = new HashSet<>(); features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_EMERGENCY_MMTEL)); features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_MMTEL)); features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS)); setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true); Loading @@ -215,8 +213,6 @@ public class ImsResolverTest extends ImsTestBase { verify(controller).bind(features); verify(controller, never()).unbind(); assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName()); verify(controller).setCanPlaceEmergencyCalls(eq(true)); } /** Loading @@ -240,8 +236,6 @@ public class ImsResolverTest extends ImsTestBase { verify(controller).bind(features); verify(controller, never()).unbind(); assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName()); // ensure emergency calling is disabled verify(controller, never()).setCanPlaceEmergencyCalls(eq(true)); packageChanged(TEST_CARRIER_DEFAULT_NAME.getPackageName()); HashSet<ImsFeatureConfiguration.FeatureSlotPair> newFeatures = new HashSet<>(); Loading @@ -254,7 +248,6 @@ public class ImsResolverTest extends ImsTestBase { //Verify new feature is added to the carrier override. // add all features for slot 0 verify(controller, atLeastOnce()).changeImsServiceFeatures(newFeatures); verify(controller).setCanPlaceEmergencyCalls(eq(true)); } /** Loading Loading @@ -998,7 +991,9 @@ public class ImsResolverTest extends ImsTestBase { for (String f : features) { switch (f) { case ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE: if (!sInfo.supportsEmergencyMmTel) { if (!sInfo.getSupportedFeatures().contains( new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_EMERGENCY_MMTEL))) { return false; } break; Loading tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java +57 −1 Original line number Diff line number Diff line Loading @@ -89,7 +89,7 @@ public class ImsServiceControllerTest extends ImsTestBase { super.setUp(); mTestImsServiceController = new ImsServiceController(mMockContext, mTestComponentName, mMockCallbacks, mHandler, REBIND_RETRY); mTestImsServiceController.addImsServiceFeatureListener(mMockProxyCallbacks); mTestImsServiceController.addImsServiceFeatureCallback(mMockProxyCallbacks); when(mMockContext.bindService(any(), any(), anyInt())).thenReturn(true); } Loading Loading @@ -171,6 +171,62 @@ public class ImsServiceControllerTest extends ImsTestBase { mTestImsServiceController.getImsServiceControllerBinder()); } /** * Tests Emergency MMTEL ImsServiceController callbacks are properly called when an ImsService * is bound and connected. */ @FlakyTest @Test public void testBindEmergencyMmTel() throws RemoteException { HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>(); // Slot 1, Emergency MMTel testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 0)); // Slot 1, MmTel testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1)); bindAndConnectService(testFeatures); IBinder binder = mMockServiceControllerBinder.getBinder().asBinder(); verify(binder).linkToDeath(any(), anyInt()); verify(mMockServiceControllerBinder).createMMTelFeature(eq(1)); // We do not want this callback to happen for emergency MMTEL verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(1), eq(0), eq(mTestImsServiceController)); verify(mMockCallbacks).imsServiceFeatureCreated(eq(1), eq(1), eq(mTestImsServiceController)); // Make sure this callback happens, which will notify the framework of emergency calling // availability. verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(0)); verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(1)); assertEquals(mMockServiceControllerBinder.getBinder(), mTestImsServiceController.getImsServiceControllerBinder()); } /** * Tests that if a callback is added after the ImsServiceController is already bound, we get a * imsFeatureCreated callback. */ @FlakyTest @Test public void testCallbacksHappenWhenAddedAfterBind() throws RemoteException { HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>(); // Slot 1, Emergency MMTel testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 0)); // Slot 1, MmTel testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1)); mTestImsServiceController.removeImsServiceFeatureCallbacks(); bindAndConnectService(testFeatures); // add the callback after bind mTestImsServiceController.addImsServiceFeatureCallback(mMockProxyCallbacks); // Make sure this callback happens for Emergency MMTEL and MMTEL verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(0)); verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(1)); assertEquals(mMockServiceControllerBinder.getBinder(), mTestImsServiceController.getImsServiceControllerBinder()); } /** * Tests ImsServiceController callbacks are properly called when an ImsService is bound and * connected. Loading Loading
src/java/com/android/internal/telephony/ims/ImsResolver.java +21 −44 Original line number Diff line number Diff line Loading @@ -102,7 +102,6 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal @VisibleForTesting public static class ImsServiceInfo { public ComponentName name; public boolean supportsEmergencyMmTel = false; // Determines if features were created from metadata in the manifest or through dynamic // query. public boolean featureFromMetadata = true; Loading @@ -124,19 +123,6 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal } void replaceFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> newFeatures) { // Emergency is a special case, filter out and if any slot supports it, set it to true. // TODO: support per-slot emergency call filtering List<ImsFeatureConfiguration.FeatureSlotPair> emergencyFeatures = newFeatures.stream() .filter(f -> f.featureType == ImsFeature.FEATURE_EMERGENCY_MMTEL) .collect(Collectors.toList()); if (!emergencyFeatures.isEmpty()) { Log.i(TAG, "replaceFeatures: emergency calls enabled."); supportsEmergencyMmTel = true; newFeatures.removeAll(emergencyFeatures); } else { Log.i(TAG, "replaceFeatures: emergency calls disabled."); supportsEmergencyMmTel = false; } mSupportedFeatures.clear(); mSupportedFeatures.addAll(newFeatures); } Loading @@ -153,7 +139,6 @@ 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 (!mSupportedFeatures.equals(that.mSupportedFeatures)) { return false; Loading @@ -167,7 +152,6 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal // We do not include mSupportedFeatures in hashcode because the internal structure // changes after adding. int result = name != null ? name.hashCode() : 0; result = 31 * result + (supportsEmergencyMmTel ? 1 : 0); result = 31 * result + (controllerFactory != null ? controllerFactory.hashCode() : 0); return result; } Loading @@ -185,8 +169,6 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal res.append(feature.featureType); res.append(") "); } res.append("], supportsEmergency="); res.append(supportsEmergencyMmTel); return res.toString(); } } Loading Loading @@ -571,19 +553,6 @@ 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) { Loading Loading @@ -619,7 +588,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal ImsServiceController controller = getImsServiceController(slotId, feature); if (controller != null) { controller.addImsServiceFeatureListener(callback); controller.addImsServiceFeatureCallback(callback); return controller; } return null; Loading Loading @@ -791,14 +760,11 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal // features because it is not carrier/device. HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = calculateFeaturesToCreate(newInfo); if (features.size() > 0) { if (shouldFeaturesCauseBind(features)) { try { if (controller != null) { Log.i(TAG, "Updating features for ImsService: " + controller.getComponentName()); Log.d(TAG, "Updating canPlaceEmergencyCalls: " + newInfo.supportsEmergencyMmTel); controller.setCanPlaceEmergencyCalls(newInfo.supportsEmergencyMmTel); Log.d(TAG, "Updating Features - New Features: " + features); controller.changeImsServiceFeatures(features); } else { Loading Loading @@ -835,12 +801,11 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal private void bindImsServiceWithFeatures(ImsServiceInfo info, HashSet<ImsFeatureConfiguration.FeatureSlotPair> features) { // Only bind if there are features that will be created by the service. if (features.size() > 0) { if (shouldFeaturesCauseBind(features)) { // Check to see if an active controller already exists ImsServiceController controller = getControllerByServiceInfo(mActiveControllers, info); if (controller != null) { Log.i(TAG, "ImsService connection exists, updating features " + features + ", updating supports emergency calling: " + info.supportsEmergencyMmTel); Log.i(TAG, "ImsService connection exists, updating features " + features); try { controller.changeImsServiceFeatures(features); // Features have been set, there was an error adding/removing. When the Loading @@ -851,11 +816,9 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal } else { controller = info.controllerFactory.create(mContext, info.name, this); Log.i(TAG, "Binding ImsService: " + controller.getComponentName() + " with features: " + features + ", supports emergency calling: " + info.supportsEmergencyMmTel); + " with features: " + features); controller.bind(features); } controller.setCanPlaceEmergencyCalls(info.supportsEmergencyMmTel); mActiveControllers.put(info.name, controller); } } Loading Loading @@ -937,6 +900,20 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal removeImsController(slotId, feature); } /** * Determines if the features specified should cause a bind or keep a binding active to an * ImsService. * @return true if MMTEL or RCS features are present, false if they are not or only * EMERGENCY_MMTEL is specified. */ private boolean shouldFeaturesCauseBind( HashSet<ImsFeatureConfiguration.FeatureSlotPair> features) { long bindableFeatures = features.stream() // remove all emergency features .filter(f -> f.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL).count(); return bindableFeatures > 0; } // Possibly rebind to another ImsService if currently installed ImsServices were changed or if // the SIM card has changed. // Called from the handler ONLY Loading Loading @@ -1129,7 +1106,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal ImsServiceInfo info = new ImsServiceInfo(mNumSlots); info.name = mStaticComponent; info.controllerFactory = mImsServiceControllerFactoryStaticBindingCompat; info.supportsEmergencyMmTel = true; info.addFeatureForAllSlots(ImsFeature.FEATURE_EMERGENCY_MMTEL); info.addFeatureForAllSlots(ImsFeature.FEATURE_MMTEL); infos.add(info); return infos; Loading Loading @@ -1164,7 +1141,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal if (serviceInfo.metaData != null) { if (serviceInfo.metaData.getBoolean(METADATA_EMERGENCY_MMTEL_FEATURE, false)) { info.supportsEmergencyMmTel = true; info.addFeatureForAllSlots(ImsFeature.FEATURE_EMERGENCY_MMTEL); } if (serviceInfo.metaData.getBoolean(METADATA_MMTEL_FEATURE, false)) { info.addFeatureForAllSlots(ImsFeature.FEATURE_MMTEL); Loading
src/java/com/android/internal/telephony/ims/ImsServiceController.java +53 −53 Original line number Diff line number Diff line Loading @@ -154,11 +154,11 @@ public class ImsServiceController { */ public interface ImsServiceControllerCallbacks { /** * Called by ImsServiceController when a new feature has been created. * Called by ImsServiceController when a new MMTEL or RCS feature has been created. */ void imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller); /** * Called by ImsServiceController when a new feature has been removed. * Called by ImsServiceController when a new MMTEL or RCS feature has been removed. */ void imsServiceFeatureRemoved(int slotId, int feature, ImsServiceController controller); } Loading Loading @@ -203,8 +203,6 @@ public class ImsServiceController { 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; Loading Loading @@ -393,7 +391,7 @@ public class ImsServiceController { } // Clean up all features changeImsServiceFeatures(new HashSet<>()); removeImsServiceFeatureListener(); removeImsServiceFeatureCallbacks(); mImsServiceControllerBinder.unlinkToDeath(mImsDeathRecipient, 0); Log.i(LOG_TAG, "Unbinding ImsService: " + mComponentName); mContext.unbindService(mImsServiceConnection); Loading Loading @@ -434,30 +432,6 @@ 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; Loading @@ -480,9 +454,20 @@ public class ImsServiceController { /** * Add a callback to ImsManager that signals a new feature that the ImsServiceProxy can handle. */ public void addImsServiceFeatureListener(IImsServiceFeatureCallback callback) { public void addImsServiceFeatureCallback(IImsServiceFeatureCallback callback) { synchronized (mLock) { mImsStatusCallbacks.add(callback); if (mImsFeatures == null || mImsFeatures.isEmpty()) { return; } // notify the new status callback of the features that are available. try { for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) { callback.imsFeatureCreated(i.slotId, i.featureType); } } catch (RemoteException e) { Log.w(LOG_TAG, "addImsServiceFeatureCallback: exception notifying callback"); } } } Loading Loading @@ -587,7 +572,8 @@ public class ImsServiceController { return mIImsServiceController != null; } private void removeImsServiceFeatureListener() { @VisibleForTesting public void removeImsServiceFeatureCallbacks() { synchronized (mLock) { mImsStatusCallbacks.clear(); } Loading Loading @@ -671,6 +657,7 @@ public class ImsServiceController { Log.w(LOG_TAG, "addImsServiceFeature called with null values."); return; } if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) { ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.slotId, featurePair.featureType); mFeatureStatusCallbacks.add(c); Loading @@ -679,7 +666,12 @@ public class ImsServiceController { addImsFeatureBinder(featurePair.slotId, featurePair.featureType, f); // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController mCallbacks.imsServiceFeatureCreated(featurePair.slotId, featurePair.featureType, this); // Send callback to ImsServiceProxy to change supported ImsFeatures } else { // Don't update ImsService for emergency MMTEL feature. Log.i(LOG_TAG, "supports emergency calling on slot " + featurePair.slotId); } // Send callback to ImsServiceProxy to change supported ImsFeatures including emergency // MMTEL state. sendImsFeatureCreatedCallback(featurePair.slotId, featurePair.featureType); } Loading @@ -690,6 +682,7 @@ public class ImsServiceController { Log.w(LOG_TAG, "removeImsServiceFeature called with null values."); return; } if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) { ImsFeatureStatusCallback callbackToRemove = mFeatureStatusCallbacks.stream().filter(c -> c.mSlotId == featurePair.slotId && c.mFeatureType == featurePair.featureType) .findFirst().orElse(null); Loading @@ -702,6 +695,10 @@ public class ImsServiceController { removeImsFeatureBinder(featurePair.slotId, featurePair.featureType); // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController mCallbacks.imsServiceFeatureRemoved(featurePair.slotId, featurePair.featureType, this); } else { // Don't update ImsService for emergency MMTEL feature. Log.i(LOG_TAG, "doesn't support emergency calling on slot " + featurePair.slotId); } // Send callback to ImsServiceProxy to change supported ImsFeatures // Ensure that ImsServiceProxy callback occurs after ImsResolver callback. If an // ImsManager requests the ImsService while it is being removed in ImsResolver, this Loading Loading @@ -759,7 +756,10 @@ public class ImsServiceController { } synchronized (mLock) { for (ImsFeatureConfiguration.FeatureSlotPair feature : mImsFeatures) { if (feature.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) { // don't update ImsServiceController for emergency MMTEL. mCallbacks.imsServiceFeatureRemoved(feature.slotId, feature.featureType, this); } sendImsFeatureRemovedCallback(feature.slotId, feature.featureType); } } Loading
tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java +3 −8 Original line number Diff line number Diff line Loading @@ -202,8 +202,6 @@ public class ImsResolverTest extends ImsTestBase { // Set CarrierConfig default package name and make it available to the package manager setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName()); HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = new HashSet<>(); features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_EMERGENCY_MMTEL)); features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_MMTEL)); features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS)); setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true); Loading @@ -215,8 +213,6 @@ public class ImsResolverTest extends ImsTestBase { verify(controller).bind(features); verify(controller, never()).unbind(); assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName()); verify(controller).setCanPlaceEmergencyCalls(eq(true)); } /** Loading @@ -240,8 +236,6 @@ public class ImsResolverTest extends ImsTestBase { verify(controller).bind(features); verify(controller, never()).unbind(); assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName()); // ensure emergency calling is disabled verify(controller, never()).setCanPlaceEmergencyCalls(eq(true)); packageChanged(TEST_CARRIER_DEFAULT_NAME.getPackageName()); HashSet<ImsFeatureConfiguration.FeatureSlotPair> newFeatures = new HashSet<>(); Loading @@ -254,7 +248,6 @@ public class ImsResolverTest extends ImsTestBase { //Verify new feature is added to the carrier override. // add all features for slot 0 verify(controller, atLeastOnce()).changeImsServiceFeatures(newFeatures); verify(controller).setCanPlaceEmergencyCalls(eq(true)); } /** Loading Loading @@ -998,7 +991,9 @@ public class ImsResolverTest extends ImsTestBase { for (String f : features) { switch (f) { case ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE: if (!sInfo.supportsEmergencyMmTel) { if (!sInfo.getSupportedFeatures().contains( new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_EMERGENCY_MMTEL))) { return false; } break; Loading
tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java +57 −1 Original line number Diff line number Diff line Loading @@ -89,7 +89,7 @@ public class ImsServiceControllerTest extends ImsTestBase { super.setUp(); mTestImsServiceController = new ImsServiceController(mMockContext, mTestComponentName, mMockCallbacks, mHandler, REBIND_RETRY); mTestImsServiceController.addImsServiceFeatureListener(mMockProxyCallbacks); mTestImsServiceController.addImsServiceFeatureCallback(mMockProxyCallbacks); when(mMockContext.bindService(any(), any(), anyInt())).thenReturn(true); } Loading Loading @@ -171,6 +171,62 @@ public class ImsServiceControllerTest extends ImsTestBase { mTestImsServiceController.getImsServiceControllerBinder()); } /** * Tests Emergency MMTEL ImsServiceController callbacks are properly called when an ImsService * is bound and connected. */ @FlakyTest @Test public void testBindEmergencyMmTel() throws RemoteException { HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>(); // Slot 1, Emergency MMTel testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 0)); // Slot 1, MmTel testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1)); bindAndConnectService(testFeatures); IBinder binder = mMockServiceControllerBinder.getBinder().asBinder(); verify(binder).linkToDeath(any(), anyInt()); verify(mMockServiceControllerBinder).createMMTelFeature(eq(1)); // We do not want this callback to happen for emergency MMTEL verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(1), eq(0), eq(mTestImsServiceController)); verify(mMockCallbacks).imsServiceFeatureCreated(eq(1), eq(1), eq(mTestImsServiceController)); // Make sure this callback happens, which will notify the framework of emergency calling // availability. verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(0)); verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(1)); assertEquals(mMockServiceControllerBinder.getBinder(), mTestImsServiceController.getImsServiceControllerBinder()); } /** * Tests that if a callback is added after the ImsServiceController is already bound, we get a * imsFeatureCreated callback. */ @FlakyTest @Test public void testCallbacksHappenWhenAddedAfterBind() throws RemoteException { HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>(); // Slot 1, Emergency MMTel testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 0)); // Slot 1, MmTel testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1)); mTestImsServiceController.removeImsServiceFeatureCallbacks(); bindAndConnectService(testFeatures); // add the callback after bind mTestImsServiceController.addImsServiceFeatureCallback(mMockProxyCallbacks); // Make sure this callback happens for Emergency MMTEL and MMTEL verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(0)); verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(1)); assertEquals(mMockServiceControllerBinder.getBinder(), mTestImsServiceController.getImsServiceControllerBinder()); } /** * Tests ImsServiceController callbacks are properly called when an ImsService is bound and * connected. Loading