Loading src/java/com/android/internal/telephony/ims/ImsResolver.java +54 −16 Original line number Diff line number Diff line Loading @@ -38,8 +38,9 @@ import android.util.Log; import android.util.Pair; import android.util.SparseArray; import com.android.ims.internal.IImsServiceController; import com.android.ims.internal.IImsServiceFeatureListener; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsRcsFeature; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.PhoneConstants; Loading Loading @@ -295,21 +296,58 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal } /** * Returns the {@link IImsServiceController} that corresponds to the given slot Id and IMS * feature or {@link null} if the service is not available. If an ImsServiceController is * available, the {@link IImsServiceFeatureListener} callback is registered as a listener for * feature updates. * @param slotId The SIM slot that we are requesting the {@link IImsServiceController} for. * @param feature The IMS Feature we are requesting. * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id or {@link null} if * the service is not available. If an IImsMMTelFeature is available, the * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates. * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for. * @param callback Listener that will send updates to ImsManager when there are updates to * ImsServiceController. * @return {@link IImsServiceController} interface for the feature specified or {@link null} if * it is unavailable. * the feature. * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable. */ public IImsServiceController getImsServiceControllerAndListen(int slotId, int feature, IImsServiceFeatureListener callback) { if (slotId < 0 || slotId >= mNumSlots || feature <= ImsFeature.INVALID || feature >= ImsFeature.MAX) { public IImsMMTelFeature getMMTelFeatureAndListen(int slotId, IImsServiceFeatureCallback callback) { ImsServiceController controller = getImsServiceControllerAndListen(slotId, ImsFeature.MMTEL, callback); return (controller != null) ? controller.getMMTelFeature(slotId) : null; } /** * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id for emergency * calling or {@link null} if the service is not available. If an IImsMMTelFeature is * available, the {@link IImsServiceFeatureCallback} callback is registered as a listener for * feature updates. * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for. * @param callback listener that will send updates to ImsManager when there are updates to * the feature. * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable. */ public IImsMMTelFeature getEmergencyMMTelFeatureAndListen(int slotId, IImsServiceFeatureCallback callback) { ImsServiceController controller = getImsServiceControllerAndListen(slotId, ImsFeature.EMERGENCY_MMTEL, callback); return (controller != null) ? controller.getEmergencyMMTelFeature(slotId) : null; } /** * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id for emergency * calling or {@link null} if the service is not available. If an IImsMMTelFeature is * available, the {@link IImsServiceFeatureCallback} callback is registered as a listener for * feature updates. * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for. * @param callback listener that will send updates to ImsManager when there are updates to * the feature. * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable. */ public IImsRcsFeature getRcsFeatureAndListen(int slotId, IImsServiceFeatureCallback callback) { ImsServiceController controller = getImsServiceControllerAndListen(slotId, ImsFeature.RCS, callback); return (controller != null) ? controller.getRcsFeature(slotId) : null; } @VisibleForTesting public ImsServiceController getImsServiceControllerAndListen(int slotId, int feature, IImsServiceFeatureCallback callback) { if (slotId < 0 || slotId >= mNumSlots) { return null; } ImsServiceController controller; Loading @@ -322,7 +360,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal } if (controller != null) { controller.addImsServiceFeatureListener(callback); return controller.getImsServiceController(); return controller; } return null; } Loading src/java/com/android/internal/telephony/ims/ImsServiceController.java +138 −11 Original line number Diff line number Diff line Loading @@ -24,14 +24,18 @@ import android.content.pm.IPackageManager; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.IInterface; import android.os.RemoteException; import android.os.ServiceManager; import android.telephony.ims.feature.ImsFeature; import android.util.Log; import android.util.Pair; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsRcsFeature; import com.android.ims.internal.IImsServiceController; import com.android.ims.internal.IImsServiceFeatureListener; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.ExponentialBackoff; Loading Loading @@ -171,15 +175,54 @@ public class ImsServiceController { private boolean mIsBinding = false; // Set of a pair of slotId->feature private HashSet<Pair<Integer, Integer>> mImsFeatures; // 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<IImsServiceFeatureListener> mImsStatusCallbacks = new HashSet<>(); private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = new HashSet<>(); // Only added or removed, never accessed on purpose. private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>(); private class ImsFeatureContainer { public int slotId; public int featureType; private IInterface mBinder; ImsFeatureContainer(int slotId, int featureType, IInterface binder) { this.slotId = slotId; this.featureType = featureType; this.mBinder = binder; } // Casts the IInterface into the binder class we are looking for. public <T extends IInterface> T resolve(Class<T> className) { return className.cast(mBinder); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ImsFeatureContainer that = (ImsFeatureContainer) o; if (slotId != that.slotId) return false; if (featureType != that.featureType) return false; return mBinder != null ? mBinder.equals(that.mBinder) : that.mBinder == null; } @Override public int hashCode() { int result = slotId; result = 31 * result + featureType; result = 31 * result + (mBinder != null ? mBinder.hashCode() : 0); return result; } } /** * Container class for the IImsFeatureStatusCallback callback implementation. This class is * never used directly, but we need to keep track of the IImsFeatureStatusCallback Loading Loading @@ -373,12 +416,56 @@ public class ImsServiceController { /** * Add a callback to ImsManager that signals a new feature that the ImsServiceProxy can handle. */ public void addImsServiceFeatureListener(IImsServiceFeatureListener callback) { public void addImsServiceFeatureListener(IImsServiceFeatureCallback callback) { synchronized (mLock) { mImsStatusCallbacks.add(callback); } } /** * Return the {@Link MMTelFeature} binder on the slot associated with the slotId. * Used for normal calling. */ public IImsMMTelFeature getMMTelFeature(int slotId) { synchronized (mLock) { ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.MMTEL); if (f == null) { Log.w(LOG_TAG, "Requested null MMTelFeature on slot " + slotId); return null; } return f.resolve(IImsMMTelFeature.class); } } /** * Return the {@Link MMTelFeature} binder on the slot associated with the slotId. * Used for emergency calling only. */ public IImsMMTelFeature getEmergencyMMTelFeature(int slotId) { synchronized (mLock) { ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.EMERGENCY_MMTEL); if (f == null) { Log.w(LOG_TAG, "Requested null Emergency MMTelFeature on slot " + slotId); return null; } return f.resolve(IImsMMTelFeature.class); } } /** * Return the {@Link RcsFeature} binder on the slot associated with the slotId. */ public IImsRcsFeature getRcsFeature(int slotId) { synchronized (mLock) { ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.RCS); if (f == null) { Log.w(LOG_TAG, "Requested null RcsFeature on slot " + slotId); return null; } return f.resolve(IImsRcsFeature.class); } } private void removeImsServiceFeatureListener() { synchronized (mLock) { mImsStatusCallbacks.clear(); Loading Loading @@ -407,9 +494,9 @@ public class ImsServiceController { private void sendImsFeatureCreatedCallback(int slot, int feature) { synchronized (mLock) { for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator(); for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator(); i.hasNext(); ) { IImsServiceFeatureListener callbacks = i.next(); IImsServiceFeatureCallback callbacks = i.next(); try { callbacks.imsFeatureCreated(slot, feature); } catch (RemoteException e) { Loading @@ -424,9 +511,9 @@ public class ImsServiceController { private void sendImsFeatureRemovedCallback(int slot, int feature) { synchronized (mLock) { for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator(); for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator(); i.hasNext(); ) { IImsServiceFeatureListener callbacks = i.next(); IImsServiceFeatureCallback callbacks = i.next(); try { callbacks.imsFeatureRemoved(slot, feature); } catch (RemoteException e) { Loading @@ -441,9 +528,9 @@ public class ImsServiceController { private void sendImsFeatureStatusChanged(int slot, int feature, int status) { synchronized (mLock) { for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator(); for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator(); i.hasNext(); ) { IImsServiceFeatureListener callbacks = i.next(); IImsServiceFeatureCallback callbacks = i.next(); try { callbacks.imsStatusChanged(slot, feature, status); } catch (RemoteException e) { Loading @@ -465,8 +552,8 @@ public class ImsServiceController { ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.first, featurePair.second); mFeatureStatusCallbacks.add(c); mIImsServiceController.createImsFeature(featurePair.first, featurePair.second, c.getCallback()); IInterface f = createImsFeature(featurePair.first, featurePair.second, c.getCallback()); addImsFeatureBinder(featurePair.first, featurePair.second, f); // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController mCallbacks.imsServiceFeatureCreated(featurePair.first, featurePair.second, this); // Send callback to ImsServiceProxy to change supported ImsFeatures Loading @@ -489,6 +576,7 @@ public class ImsServiceController { } mIImsServiceController.removeImsFeature(featurePair.first, featurePair.second, (callbackToRemove != null ? callbackToRemove.getCallback() : null)); removeImsFeatureBinder(featurePair.first, featurePair.second); // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController mCallbacks.imsServiceFeatureRemoved(featurePair.first, featurePair.second, this); // Send callback to ImsServiceProxy to change supported ImsFeatures Loading @@ -498,6 +586,45 @@ public class ImsServiceController { sendImsFeatureRemovedCallback(featurePair.first, featurePair.second); } // This method should only be called when already synchronized on mLock; private IInterface createImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) throws RemoteException { switch (featureType) { case ImsFeature.EMERGENCY_MMTEL: { return mIImsServiceController.createEmergencyMMTelFeature(slotId, c); } case ImsFeature.MMTEL: { return mIImsServiceController.createMMTelFeature(slotId, c); } case ImsFeature.RCS: { return mIImsServiceController.createRcsFeature(slotId, c); } default: return null; } } // This method should only be called when synchronized on mLock private void addImsFeatureBinder(int slotId, int featureType, IInterface b) { mImsFeatureBinders.add(new ImsFeatureContainer(slotId, featureType, b)); } // This method should only be called when synchronized on mLock private void removeImsFeatureBinder(int slotId, int featureType) { ImsFeatureContainer container = mImsFeatureBinders.stream() .filter(f-> (f.slotId == slotId && f.featureType == featureType)) .findFirst().orElse(null); if (container != null) { mImsFeatureBinders.remove(container); } } private ImsFeatureContainer getImsFeatureContainer(int slotId, int featureType) { return mImsFeatureBinders.stream() .filter(f-> (f.slotId == slotId && f.featureType == featureType)) .findFirst().orElse(null); } private void notifyAllFeaturesRemoved() { if (mCallbacks == null) { Log.w(LOG_TAG, "notifyAllFeaturesRemoved called with invalid callbacks."); Loading tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java +8 −31 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.telephony.ims; import android.os.IInterface; import android.support.test.runner.AndroidJUnit4; import android.telephony.ims.feature.ImsFeature; import android.test.suitebuilder.annotation.SmallTest; Loading @@ -30,9 +31,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @RunWith(AndroidJUnit4.class) Loading @@ -44,20 +43,21 @@ public class ImsFeatureTest { private IImsFeatureStatusCallback mTestStatusCallback; @Mock private IImsFeatureStatusCallback mTestStatusCallback2; @Mock private ImsFeature.INotifyFeatureRemoved mTestRemovedCallback; private class TestImsFeature extends ImsFeature { public boolean featureRemovedCalled = false; public void testSetFeatureState(int featureState) { setFeatureState(featureState); } @Override public void onFeatureRemoved() { featureRemovedCalled = true; } public void testSetFeatureState(int featureState) { setFeatureState(featureState); @Override public IInterface getBinder() { return null; } } Loading Loading @@ -94,27 +94,4 @@ public class ImsFeatureTest { verify(mTestStatusCallback2).notifyImsFeatureStatus(eq(ImsFeature.STATE_READY)); assertEquals(ImsFeature.STATE_READY, mTestImsService.getFeatureState()); } @Test @SmallTest public void testRegisterAndNotifyRemoveFeature() { mTestImsService.addFeatureRemovedListener(mTestRemovedCallback); mTestImsService.notifyFeatureRemoved(0); verify(mTestRemovedCallback).onFeatureRemoved(eq(0)); assertTrue(mTestImsService.featureRemovedCalled); } @Test @SmallTest public void testRegisterAndUnregisterNotify() { mTestImsService.addFeatureRemovedListener(mTestRemovedCallback); mTestImsService.removeFeatureRemovedListener(mTestRemovedCallback); mTestImsService.notifyFeatureRemoved(0); verify(mTestRemovedCallback, never()).onFeatureRemoved(eq(0)); assertTrue(mTestImsService.featureRemovedCalled); } } tests/telephonytests/src/android/telephony/ims/ImsServiceTest.java +25 −79 Original line number Diff line number Diff line Loading @@ -16,44 +16,33 @@ package android.telephony.ims; import static android.Manifest.permission.MODIFY_PHONE_STATE; import static android.Manifest.permission.READ_PHONE_STATE; import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE; import static com.android.internal.telephony.ims.ImsResolver.SERVICE_INTERFACE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.nullable; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.RemoteException; import android.support.test.filters.FlakyTest; import android.support.test.runner.AndroidJUnit4; import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.feature.MMTelFeature; import android.test.suitebuilder.annotation.SmallTest; import android.util.SparseArray; import com.android.ims.ImsManager; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsServiceController; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; Loading Loading @@ -94,88 +83,45 @@ public class ImsServiceTest { @Test @SmallTest public void testCreateMMTelFeature() throws RemoteException { mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); when(mTestImsService.mSpyMMTelFeature.getFeatureState()).thenReturn( ImsFeature.STATE_READY); SparseArray<ImsFeature> features = mTestImsService.getImsFeatureMap(TEST_SLOT_0); assertEquals(mTestImsService.mSpyMMTelFeature, mTestImsService.getImsFeatureFromType(features, ImsFeature.MMTEL)); IImsMMTelFeature f = mTestImsServiceBinder.createMMTelFeature(TEST_SLOT_0, mTestCallback); mTestImsService.mTestMMTelFeature.sendSetFeatureState(ImsFeature.STATE_READY); SparseArray<ImsFeature> features = mTestImsService.getFeatures(TEST_SLOT_0); ImsFeature featureToVerify = features.get(ImsFeature.MMTEL); MMTelFeature testMMTelFeature = null; if (featureToVerify instanceof MMTelFeature) { testMMTelFeature = (MMTelFeature) featureToVerify; } else { fail(); } assertEquals(mTestImsService.mSpyMMTelFeature, testMMTelFeature); // Verify that upon creating a feature, we assign the callback and get the set feature state // when querying it. verify(mTestImsService.mSpyMMTelFeature).addImsFeatureStatusCallback(eq(mTestCallback)); assertEquals(ImsFeature.STATE_READY, mTestImsServiceBinder.getFeatureStatus(TEST_SLOT_0, ImsFeature.MMTEL)); assertEquals(ImsFeature.STATE_READY, f.getFeatureStatus()); } @Test @SmallTest public void testRemoveMMTelFeature() throws RemoteException { mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); mTestImsServiceBinder.createMMTelFeature(TEST_SLOT_0, mTestCallback); mTestImsServiceBinder.removeImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); verify(mTestImsService.mSpyMMTelFeature).notifyFeatureRemoved(eq(0)); verify(mTestImsService.mSpyMMTelFeature).onFeatureRemoved(); verify(mTestImsService.mSpyMMTelFeature).removeImsFeatureStatusCallback(mTestCallback); SparseArray<ImsFeature> features = mTestImsService.getImsFeatureMap(TEST_SLOT_0); assertNull(mTestImsService.getImsFeatureFromType(features, ImsFeature.MMTEL)); SparseArray<ImsFeature> features = mTestImsService.getFeatures(TEST_SLOT_0); assertNull(features.get(ImsFeature.MMTEL)); } @Test @SmallTest public void testCallMethodOnCreatedFeature() throws RemoteException { mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); mTestImsServiceBinder.isConnected(TEST_SLOT_0, ImsFeature.MMTEL, 0 /*callSessionType*/, 0 /*callType*/); verify(mTestImsService.mSpyMMTelFeature).isConnected(anyInt(), anyInt()); } @Test @SmallTest public void testCallMethodWithNoCreatedFeature() throws RemoteException { mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); mTestImsServiceBinder.isConnected(TEST_SLOT_1, ImsFeature.MMTEL, 0 /*callSessionType*/, 0 /*callType*/); verify(mTestImsService.mSpyMMTelFeature, never()).isConnected(anyInt(), anyInt()); } IImsMMTelFeature f = mTestImsServiceBinder.createMMTelFeature(TEST_SLOT_0, mTestCallback); @Test @SmallTest public void testCreateFeatureWithNoPermissions() throws RemoteException { doThrow(new SecurityException()).when(mMockContext).enforceCallingOrSelfPermission( eq(MODIFY_PHONE_STATE), anyString()); try { mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); fail(); } catch (SecurityException e) { // Expected } } @FlakyTest @Ignore @Test public void testMethodWithNoPermissions() throws RemoteException { when(mMockContext.checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE)).thenReturn( PackageManager.PERMISSION_DENIED); doThrow(new SecurityException()).when(mMockContext).enforceCallingOrSelfPermission( eq(READ_PHONE_STATE), nullable(String.class)); mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); try { mTestImsServiceBinder.isConnected(TEST_SLOT_1, ImsFeature.MMTEL, 0 /*callSessionType*/, 0 /*callType*/); fail(); } catch (SecurityException e) { // Expected } f.isConnected(0/*callSessionType*/, 0 /*callType*/); verify(mTestImsService.mSpyMMTelFeature, never()).isConnected(anyInt(), anyInt()); assertTrue(mTestImsService.mTestMMTelFeature.isConnectedCalled); } /** Loading @@ -185,7 +131,7 @@ public class ImsServiceTest { @Test @SmallTest public void testImsServiceUpSentCompat() throws RemoteException { mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); mTestImsServiceBinder.createMMTelFeature(TEST_SLOT_0, mTestCallback); mTestImsService.mSpyMMTelFeature.sendSetFeatureState(ImsFeature.STATE_READY); Loading @@ -207,7 +153,7 @@ public class ImsServiceTest { @Test @SmallTest public void testImsServiceDownSentCompatInitializing() throws RemoteException { mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); mTestImsServiceBinder.createMMTelFeature(TEST_SLOT_0, mTestCallback); mTestImsService.mSpyMMTelFeature.sendSetFeatureState(ImsFeature.STATE_INITIALIZING); Loading tests/telephonytests/src/android/telephony/ims/TestImsService.java +1 −2 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import android.telephony.ims.feature.MMTelFeature; import android.telephony.ims.feature.RcsFeature; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import static org.mockito.Mockito.spy; Loading @@ -32,7 +31,7 @@ import static org.mockito.Mockito.spy; public class TestImsService extends ImsService { public TestMMTelFeature mSpyMMTelFeature; private TestMMTelFeature mTestMMTelFeature; public TestMMTelFeature mTestMMTelFeature; public TestImsService(Context context) { attachBaseContext(context); Loading Loading
src/java/com/android/internal/telephony/ims/ImsResolver.java +54 −16 Original line number Diff line number Diff line Loading @@ -38,8 +38,9 @@ import android.util.Log; import android.util.Pair; import android.util.SparseArray; import com.android.ims.internal.IImsServiceController; import com.android.ims.internal.IImsServiceFeatureListener; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsRcsFeature; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.PhoneConstants; Loading Loading @@ -295,21 +296,58 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal } /** * Returns the {@link IImsServiceController} that corresponds to the given slot Id and IMS * feature or {@link null} if the service is not available. If an ImsServiceController is * available, the {@link IImsServiceFeatureListener} callback is registered as a listener for * feature updates. * @param slotId The SIM slot that we are requesting the {@link IImsServiceController} for. * @param feature The IMS Feature we are requesting. * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id or {@link null} if * the service is not available. If an IImsMMTelFeature is available, the * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates. * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for. * @param callback Listener that will send updates to ImsManager when there are updates to * ImsServiceController. * @return {@link IImsServiceController} interface for the feature specified or {@link null} if * it is unavailable. * the feature. * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable. */ public IImsServiceController getImsServiceControllerAndListen(int slotId, int feature, IImsServiceFeatureListener callback) { if (slotId < 0 || slotId >= mNumSlots || feature <= ImsFeature.INVALID || feature >= ImsFeature.MAX) { public IImsMMTelFeature getMMTelFeatureAndListen(int slotId, IImsServiceFeatureCallback callback) { ImsServiceController controller = getImsServiceControllerAndListen(slotId, ImsFeature.MMTEL, callback); return (controller != null) ? controller.getMMTelFeature(slotId) : null; } /** * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id for emergency * calling or {@link null} if the service is not available. If an IImsMMTelFeature is * available, the {@link IImsServiceFeatureCallback} callback is registered as a listener for * feature updates. * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for. * @param callback listener that will send updates to ImsManager when there are updates to * the feature. * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable. */ public IImsMMTelFeature getEmergencyMMTelFeatureAndListen(int slotId, IImsServiceFeatureCallback callback) { ImsServiceController controller = getImsServiceControllerAndListen(slotId, ImsFeature.EMERGENCY_MMTEL, callback); return (controller != null) ? controller.getEmergencyMMTelFeature(slotId) : null; } /** * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id for emergency * calling or {@link null} if the service is not available. If an IImsMMTelFeature is * available, the {@link IImsServiceFeatureCallback} callback is registered as a listener for * feature updates. * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for. * @param callback listener that will send updates to ImsManager when there are updates to * the feature. * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable. */ public IImsRcsFeature getRcsFeatureAndListen(int slotId, IImsServiceFeatureCallback callback) { ImsServiceController controller = getImsServiceControllerAndListen(slotId, ImsFeature.RCS, callback); return (controller != null) ? controller.getRcsFeature(slotId) : null; } @VisibleForTesting public ImsServiceController getImsServiceControllerAndListen(int slotId, int feature, IImsServiceFeatureCallback callback) { if (slotId < 0 || slotId >= mNumSlots) { return null; } ImsServiceController controller; Loading @@ -322,7 +360,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal } if (controller != null) { controller.addImsServiceFeatureListener(callback); return controller.getImsServiceController(); return controller; } return null; } Loading
src/java/com/android/internal/telephony/ims/ImsServiceController.java +138 −11 Original line number Diff line number Diff line Loading @@ -24,14 +24,18 @@ import android.content.pm.IPackageManager; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.IInterface; import android.os.RemoteException; import android.os.ServiceManager; import android.telephony.ims.feature.ImsFeature; import android.util.Log; import android.util.Pair; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsRcsFeature; import com.android.ims.internal.IImsServiceController; import com.android.ims.internal.IImsServiceFeatureListener; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.ExponentialBackoff; Loading Loading @@ -171,15 +175,54 @@ public class ImsServiceController { private boolean mIsBinding = false; // Set of a pair of slotId->feature private HashSet<Pair<Integer, Integer>> mImsFeatures; // 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<IImsServiceFeatureListener> mImsStatusCallbacks = new HashSet<>(); private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = new HashSet<>(); // Only added or removed, never accessed on purpose. private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>(); private class ImsFeatureContainer { public int slotId; public int featureType; private IInterface mBinder; ImsFeatureContainer(int slotId, int featureType, IInterface binder) { this.slotId = slotId; this.featureType = featureType; this.mBinder = binder; } // Casts the IInterface into the binder class we are looking for. public <T extends IInterface> T resolve(Class<T> className) { return className.cast(mBinder); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ImsFeatureContainer that = (ImsFeatureContainer) o; if (slotId != that.slotId) return false; if (featureType != that.featureType) return false; return mBinder != null ? mBinder.equals(that.mBinder) : that.mBinder == null; } @Override public int hashCode() { int result = slotId; result = 31 * result + featureType; result = 31 * result + (mBinder != null ? mBinder.hashCode() : 0); return result; } } /** * Container class for the IImsFeatureStatusCallback callback implementation. This class is * never used directly, but we need to keep track of the IImsFeatureStatusCallback Loading Loading @@ -373,12 +416,56 @@ public class ImsServiceController { /** * Add a callback to ImsManager that signals a new feature that the ImsServiceProxy can handle. */ public void addImsServiceFeatureListener(IImsServiceFeatureListener callback) { public void addImsServiceFeatureListener(IImsServiceFeatureCallback callback) { synchronized (mLock) { mImsStatusCallbacks.add(callback); } } /** * Return the {@Link MMTelFeature} binder on the slot associated with the slotId. * Used for normal calling. */ public IImsMMTelFeature getMMTelFeature(int slotId) { synchronized (mLock) { ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.MMTEL); if (f == null) { Log.w(LOG_TAG, "Requested null MMTelFeature on slot " + slotId); return null; } return f.resolve(IImsMMTelFeature.class); } } /** * Return the {@Link MMTelFeature} binder on the slot associated with the slotId. * Used for emergency calling only. */ public IImsMMTelFeature getEmergencyMMTelFeature(int slotId) { synchronized (mLock) { ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.EMERGENCY_MMTEL); if (f == null) { Log.w(LOG_TAG, "Requested null Emergency MMTelFeature on slot " + slotId); return null; } return f.resolve(IImsMMTelFeature.class); } } /** * Return the {@Link RcsFeature} binder on the slot associated with the slotId. */ public IImsRcsFeature getRcsFeature(int slotId) { synchronized (mLock) { ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.RCS); if (f == null) { Log.w(LOG_TAG, "Requested null RcsFeature on slot " + slotId); return null; } return f.resolve(IImsRcsFeature.class); } } private void removeImsServiceFeatureListener() { synchronized (mLock) { mImsStatusCallbacks.clear(); Loading Loading @@ -407,9 +494,9 @@ public class ImsServiceController { private void sendImsFeatureCreatedCallback(int slot, int feature) { synchronized (mLock) { for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator(); for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator(); i.hasNext(); ) { IImsServiceFeatureListener callbacks = i.next(); IImsServiceFeatureCallback callbacks = i.next(); try { callbacks.imsFeatureCreated(slot, feature); } catch (RemoteException e) { Loading @@ -424,9 +511,9 @@ public class ImsServiceController { private void sendImsFeatureRemovedCallback(int slot, int feature) { synchronized (mLock) { for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator(); for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator(); i.hasNext(); ) { IImsServiceFeatureListener callbacks = i.next(); IImsServiceFeatureCallback callbacks = i.next(); try { callbacks.imsFeatureRemoved(slot, feature); } catch (RemoteException e) { Loading @@ -441,9 +528,9 @@ public class ImsServiceController { private void sendImsFeatureStatusChanged(int slot, int feature, int status) { synchronized (mLock) { for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator(); for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator(); i.hasNext(); ) { IImsServiceFeatureListener callbacks = i.next(); IImsServiceFeatureCallback callbacks = i.next(); try { callbacks.imsStatusChanged(slot, feature, status); } catch (RemoteException e) { Loading @@ -465,8 +552,8 @@ public class ImsServiceController { ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.first, featurePair.second); mFeatureStatusCallbacks.add(c); mIImsServiceController.createImsFeature(featurePair.first, featurePair.second, c.getCallback()); IInterface f = createImsFeature(featurePair.first, featurePair.second, c.getCallback()); addImsFeatureBinder(featurePair.first, featurePair.second, f); // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController mCallbacks.imsServiceFeatureCreated(featurePair.first, featurePair.second, this); // Send callback to ImsServiceProxy to change supported ImsFeatures Loading @@ -489,6 +576,7 @@ public class ImsServiceController { } mIImsServiceController.removeImsFeature(featurePair.first, featurePair.second, (callbackToRemove != null ? callbackToRemove.getCallback() : null)); removeImsFeatureBinder(featurePair.first, featurePair.second); // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController mCallbacks.imsServiceFeatureRemoved(featurePair.first, featurePair.second, this); // Send callback to ImsServiceProxy to change supported ImsFeatures Loading @@ -498,6 +586,45 @@ public class ImsServiceController { sendImsFeatureRemovedCallback(featurePair.first, featurePair.second); } // This method should only be called when already synchronized on mLock; private IInterface createImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) throws RemoteException { switch (featureType) { case ImsFeature.EMERGENCY_MMTEL: { return mIImsServiceController.createEmergencyMMTelFeature(slotId, c); } case ImsFeature.MMTEL: { return mIImsServiceController.createMMTelFeature(slotId, c); } case ImsFeature.RCS: { return mIImsServiceController.createRcsFeature(slotId, c); } default: return null; } } // This method should only be called when synchronized on mLock private void addImsFeatureBinder(int slotId, int featureType, IInterface b) { mImsFeatureBinders.add(new ImsFeatureContainer(slotId, featureType, b)); } // This method should only be called when synchronized on mLock private void removeImsFeatureBinder(int slotId, int featureType) { ImsFeatureContainer container = mImsFeatureBinders.stream() .filter(f-> (f.slotId == slotId && f.featureType == featureType)) .findFirst().orElse(null); if (container != null) { mImsFeatureBinders.remove(container); } } private ImsFeatureContainer getImsFeatureContainer(int slotId, int featureType) { return mImsFeatureBinders.stream() .filter(f-> (f.slotId == slotId && f.featureType == featureType)) .findFirst().orElse(null); } private void notifyAllFeaturesRemoved() { if (mCallbacks == null) { Log.w(LOG_TAG, "notifyAllFeaturesRemoved called with invalid callbacks."); Loading
tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java +8 −31 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.telephony.ims; import android.os.IInterface; import android.support.test.runner.AndroidJUnit4; import android.telephony.ims.feature.ImsFeature; import android.test.suitebuilder.annotation.SmallTest; Loading @@ -30,9 +31,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @RunWith(AndroidJUnit4.class) Loading @@ -44,20 +43,21 @@ public class ImsFeatureTest { private IImsFeatureStatusCallback mTestStatusCallback; @Mock private IImsFeatureStatusCallback mTestStatusCallback2; @Mock private ImsFeature.INotifyFeatureRemoved mTestRemovedCallback; private class TestImsFeature extends ImsFeature { public boolean featureRemovedCalled = false; public void testSetFeatureState(int featureState) { setFeatureState(featureState); } @Override public void onFeatureRemoved() { featureRemovedCalled = true; } public void testSetFeatureState(int featureState) { setFeatureState(featureState); @Override public IInterface getBinder() { return null; } } Loading Loading @@ -94,27 +94,4 @@ public class ImsFeatureTest { verify(mTestStatusCallback2).notifyImsFeatureStatus(eq(ImsFeature.STATE_READY)); assertEquals(ImsFeature.STATE_READY, mTestImsService.getFeatureState()); } @Test @SmallTest public void testRegisterAndNotifyRemoveFeature() { mTestImsService.addFeatureRemovedListener(mTestRemovedCallback); mTestImsService.notifyFeatureRemoved(0); verify(mTestRemovedCallback).onFeatureRemoved(eq(0)); assertTrue(mTestImsService.featureRemovedCalled); } @Test @SmallTest public void testRegisterAndUnregisterNotify() { mTestImsService.addFeatureRemovedListener(mTestRemovedCallback); mTestImsService.removeFeatureRemovedListener(mTestRemovedCallback); mTestImsService.notifyFeatureRemoved(0); verify(mTestRemovedCallback, never()).onFeatureRemoved(eq(0)); assertTrue(mTestImsService.featureRemovedCalled); } }
tests/telephonytests/src/android/telephony/ims/ImsServiceTest.java +25 −79 Original line number Diff line number Diff line Loading @@ -16,44 +16,33 @@ package android.telephony.ims; import static android.Manifest.permission.MODIFY_PHONE_STATE; import static android.Manifest.permission.READ_PHONE_STATE; import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE; import static com.android.internal.telephony.ims.ImsResolver.SERVICE_INTERFACE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.nullable; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.RemoteException; import android.support.test.filters.FlakyTest; import android.support.test.runner.AndroidJUnit4; import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.feature.MMTelFeature; import android.test.suitebuilder.annotation.SmallTest; import android.util.SparseArray; import com.android.ims.ImsManager; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsServiceController; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; Loading Loading @@ -94,88 +83,45 @@ public class ImsServiceTest { @Test @SmallTest public void testCreateMMTelFeature() throws RemoteException { mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); when(mTestImsService.mSpyMMTelFeature.getFeatureState()).thenReturn( ImsFeature.STATE_READY); SparseArray<ImsFeature> features = mTestImsService.getImsFeatureMap(TEST_SLOT_0); assertEquals(mTestImsService.mSpyMMTelFeature, mTestImsService.getImsFeatureFromType(features, ImsFeature.MMTEL)); IImsMMTelFeature f = mTestImsServiceBinder.createMMTelFeature(TEST_SLOT_0, mTestCallback); mTestImsService.mTestMMTelFeature.sendSetFeatureState(ImsFeature.STATE_READY); SparseArray<ImsFeature> features = mTestImsService.getFeatures(TEST_SLOT_0); ImsFeature featureToVerify = features.get(ImsFeature.MMTEL); MMTelFeature testMMTelFeature = null; if (featureToVerify instanceof MMTelFeature) { testMMTelFeature = (MMTelFeature) featureToVerify; } else { fail(); } assertEquals(mTestImsService.mSpyMMTelFeature, testMMTelFeature); // Verify that upon creating a feature, we assign the callback and get the set feature state // when querying it. verify(mTestImsService.mSpyMMTelFeature).addImsFeatureStatusCallback(eq(mTestCallback)); assertEquals(ImsFeature.STATE_READY, mTestImsServiceBinder.getFeatureStatus(TEST_SLOT_0, ImsFeature.MMTEL)); assertEquals(ImsFeature.STATE_READY, f.getFeatureStatus()); } @Test @SmallTest public void testRemoveMMTelFeature() throws RemoteException { mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); mTestImsServiceBinder.createMMTelFeature(TEST_SLOT_0, mTestCallback); mTestImsServiceBinder.removeImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); verify(mTestImsService.mSpyMMTelFeature).notifyFeatureRemoved(eq(0)); verify(mTestImsService.mSpyMMTelFeature).onFeatureRemoved(); verify(mTestImsService.mSpyMMTelFeature).removeImsFeatureStatusCallback(mTestCallback); SparseArray<ImsFeature> features = mTestImsService.getImsFeatureMap(TEST_SLOT_0); assertNull(mTestImsService.getImsFeatureFromType(features, ImsFeature.MMTEL)); SparseArray<ImsFeature> features = mTestImsService.getFeatures(TEST_SLOT_0); assertNull(features.get(ImsFeature.MMTEL)); } @Test @SmallTest public void testCallMethodOnCreatedFeature() throws RemoteException { mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); mTestImsServiceBinder.isConnected(TEST_SLOT_0, ImsFeature.MMTEL, 0 /*callSessionType*/, 0 /*callType*/); verify(mTestImsService.mSpyMMTelFeature).isConnected(anyInt(), anyInt()); } @Test @SmallTest public void testCallMethodWithNoCreatedFeature() throws RemoteException { mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); mTestImsServiceBinder.isConnected(TEST_SLOT_1, ImsFeature.MMTEL, 0 /*callSessionType*/, 0 /*callType*/); verify(mTestImsService.mSpyMMTelFeature, never()).isConnected(anyInt(), anyInt()); } IImsMMTelFeature f = mTestImsServiceBinder.createMMTelFeature(TEST_SLOT_0, mTestCallback); @Test @SmallTest public void testCreateFeatureWithNoPermissions() throws RemoteException { doThrow(new SecurityException()).when(mMockContext).enforceCallingOrSelfPermission( eq(MODIFY_PHONE_STATE), anyString()); try { mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); fail(); } catch (SecurityException e) { // Expected } } @FlakyTest @Ignore @Test public void testMethodWithNoPermissions() throws RemoteException { when(mMockContext.checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE)).thenReturn( PackageManager.PERMISSION_DENIED); doThrow(new SecurityException()).when(mMockContext).enforceCallingOrSelfPermission( eq(READ_PHONE_STATE), nullable(String.class)); mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); try { mTestImsServiceBinder.isConnected(TEST_SLOT_1, ImsFeature.MMTEL, 0 /*callSessionType*/, 0 /*callType*/); fail(); } catch (SecurityException e) { // Expected } f.isConnected(0/*callSessionType*/, 0 /*callType*/); verify(mTestImsService.mSpyMMTelFeature, never()).isConnected(anyInt(), anyInt()); assertTrue(mTestImsService.mTestMMTelFeature.isConnectedCalled); } /** Loading @@ -185,7 +131,7 @@ public class ImsServiceTest { @Test @SmallTest public void testImsServiceUpSentCompat() throws RemoteException { mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); mTestImsServiceBinder.createMMTelFeature(TEST_SLOT_0, mTestCallback); mTestImsService.mSpyMMTelFeature.sendSetFeatureState(ImsFeature.STATE_READY); Loading @@ -207,7 +153,7 @@ public class ImsServiceTest { @Test @SmallTest public void testImsServiceDownSentCompatInitializing() throws RemoteException { mTestImsServiceBinder.createImsFeature(TEST_SLOT_0, ImsFeature.MMTEL, mTestCallback); mTestImsServiceBinder.createMMTelFeature(TEST_SLOT_0, mTestCallback); mTestImsService.mSpyMMTelFeature.sendSetFeatureState(ImsFeature.STATE_INITIALIZING); Loading
tests/telephonytests/src/android/telephony/ims/TestImsService.java +1 −2 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import android.telephony.ims.feature.MMTelFeature; import android.telephony.ims.feature.RcsFeature; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import static org.mockito.Mockito.spy; Loading @@ -32,7 +31,7 @@ import static org.mockito.Mockito.spy; public class TestImsService extends ImsService { public TestMMTelFeature mSpyMMTelFeature; private TestMMTelFeature mTestMMTelFeature; public TestMMTelFeature mTestMMTelFeature; public TestImsService(Context context) { attachBaseContext(context); Loading