Merge "Adds Tests for new IMS APIs"
diff --git a/tests/telephonytests/src/android/telephony/ims/internal/ImsFeatureTest.java b/tests/telephonytests/src/android/telephony/ims/internal/ImsFeatureTest.java
new file mode 100644
index 0000000..888ab13
--- /dev/null
+++ b/tests/telephonytests/src/android/telephony/ims/internal/ImsFeatureTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.internal;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.ims.internal.feature.CapabilityChangeRequest;
+import android.telephony.ims.internal.feature.ImsFeature;
+import android.telephony.ims.internal.feature.MmTelFeature;
+import android.telephony.ims.internal.stub.ImsRegistrationImplBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.ims.internal.IImsFeatureStatusCallback;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class ImsFeatureTest {
+
+    private TestImsFeature mTestImsFeature;
+    private ImsFeature.CapabilityCallback mCapabilityCallback;
+
+    @Mock
+    private IImsFeatureStatusCallback mTestStatusCallback;
+    @Mock
+    private IImsFeatureStatusCallback mTestStatusCallback2;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTestImsFeature = new TestImsFeature();
+        mCapabilityCallback = Mockito.spy(new ImsFeature.CapabilityCallback());
+        mTestImsFeature.addCapabilityCallback(mCapabilityCallback);
+    }
+
+    @After
+    public void tearDown() {
+        mTestImsFeature = null;
+        mCapabilityCallback = null;
+    }
+
+    @Test
+    @SmallTest
+    public void testSetCallbackAndNotify() throws Exception {
+        mTestImsFeature.addImsFeatureStatusCallback(mTestStatusCallback);
+        mTestImsFeature.addImsFeatureStatusCallback(mTestStatusCallback2);
+
+        verify(mTestStatusCallback).notifyImsFeatureStatus(eq(ImsFeature.STATE_UNAVAILABLE));
+        verify(mTestStatusCallback2).notifyImsFeatureStatus(eq(ImsFeature.STATE_UNAVAILABLE));
+    }
+
+    @Test
+    @SmallTest
+    public void testSetFeatureAndCheckCallback() throws Exception {
+        mTestImsFeature.addImsFeatureStatusCallback(mTestStatusCallback);
+        mTestImsFeature.addImsFeatureStatusCallback(mTestStatusCallback2);
+
+        mTestImsFeature.testSetFeatureState(ImsFeature.STATE_READY);
+
+        verify(mTestStatusCallback).notifyImsFeatureStatus(eq(ImsFeature.STATE_READY));
+        verify(mTestStatusCallback2).notifyImsFeatureStatus(eq(ImsFeature.STATE_READY));
+        assertEquals(ImsFeature.STATE_READY, mTestImsFeature.getFeatureState());
+    }
+
+    @SmallTest
+    @Test
+    public void testCapabilityConfigAdd() throws Exception {
+        ImsFeature.Capabilities c = new ImsFeature.Capabilities();
+        c.addCapabilities(TestImsFeature.CAPABILITY_TEST_1);
+
+        assertTrue(c.isCapable(TestImsFeature.CAPABILITY_TEST_1));
+    }
+
+    @SmallTest
+    @Test
+    public void testCapabilityConfigAddMultiple() throws Exception {
+        ImsFeature.Capabilities c = new ImsFeature.Capabilities();
+        c.addCapabilities(TestImsFeature.CAPABILITY_TEST_1);
+        c.addCapabilities(TestImsFeature.CAPABILITY_TEST_2);
+
+        assertTrue(c.isCapable(TestImsFeature.CAPABILITY_TEST_2));
+    }
+
+    @SmallTest
+    @Test
+    public void testCapabilityConfigHasMultiple() throws Exception {
+        ImsFeature.Capabilities c = new ImsFeature.Capabilities();
+        c.addCapabilities(TestImsFeature.CAPABILITY_TEST_1);
+        c.addCapabilities(TestImsFeature.CAPABILITY_TEST_2);
+
+        assertTrue(c.isCapable(
+                TestImsFeature.CAPABILITY_TEST_1 | TestImsFeature.CAPABILITY_TEST_2));
+    }
+
+    @SmallTest
+    @Test
+    public void testCapabilityConfigRemove() throws Exception {
+        ImsFeature.Capabilities c = new ImsFeature.Capabilities();
+        c.addCapabilities(TestImsFeature.CAPABILITY_TEST_1);
+        c.addCapabilities(TestImsFeature.CAPABILITY_TEST_2);
+        c.removeCapabilities(TestImsFeature.CAPABILITY_TEST_1);
+
+        assertTrue(c.isCapable(TestImsFeature.CAPABILITY_TEST_2));
+    }
+
+    @SmallTest
+    @Test
+    public void testSetCapabilityConfig() throws Exception {
+        CapabilityChangeRequest request = new CapabilityChangeRequest();
+        request.addCapabilitiesToEnableForTech(TestImsFeature.CAPABILITY_TEST_1,
+                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+
+        mTestImsFeature.requestChangeEnabledCapabilities(request, null);
+
+        assertEquals(request, mTestImsFeature.lastRequest);
+    }
+
+
+    @SmallTest
+    @Test
+    public void testSetCapabilityConfigError() throws Exception {
+        CapabilityChangeRequest request = new CapabilityChangeRequest();
+        request.addCapabilitiesToEnableForTech(TestImsFeature.CAPABILITY_TEST_1,
+                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+
+        mTestImsFeature.setCapabilitiesResult = ImsFeature.CAPABILITY_ERROR_GENERIC;
+        mTestImsFeature.requestChangeEnabledCapabilities(request, mCapabilityCallback);
+
+        verify(mCapabilityCallback).onChangeCapabilityConfigurationError(
+                eq(TestImsFeature.CAPABILITY_TEST_1),
+                eq(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN),
+                eq(ImsFeature.CAPABILITY_ERROR_GENERIC));
+        assertEquals(request, mTestImsFeature.lastRequest);
+    }
+
+    @SmallTest
+    @Test
+    public void testNotifyCapabilityStatusChanged() throws Exception {
+        ImsFeature.Capabilities status =
+                new ImsFeature.Capabilities();
+        status.addCapabilities(TestImsFeature.CAPABILITY_TEST_1);
+        status.addCapabilities(TestImsFeature.CAPABILITY_TEST_2);
+
+        mTestImsFeature.capabilitiesStatusChanged(status);
+
+        assertEquals(status.getMask(), mTestImsFeature.queryCapabilityStatus().getMask());
+    }
+
+    @SmallTest
+    @Test
+    public void testNotifyCapabilityStatusChangedCallback() throws Exception {
+        ImsFeature.Capabilities status =
+                new ImsFeature.Capabilities();
+        status.addCapabilities(TestImsFeature.CAPABILITY_TEST_1);
+        status.addCapabilities(TestImsFeature.CAPABILITY_TEST_2);
+
+        mTestImsFeature.capabilitiesStatusChanged(status);
+
+        assertEquals(status.getMask(), mTestImsFeature.queryCapabilityStatus().getMask());
+        verify(mCapabilityCallback).onCapabilitiesStatusChanged(eq(status));
+    }
+
+    @SmallTest
+    @Test
+    public void testCapabilityChangeContainsFullSets() throws Exception {
+        CapabilityChangeRequest request = new CapabilityChangeRequest();
+        request.addCapabilitiesToEnableForTech(TestImsFeature.CAPABILITY_TEST_1
+                        | TestImsFeature.CAPABILITY_TEST_2,
+                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+        request.addCapabilitiesToEnableForTech(TestImsFeature.CAPABILITY_TEST_2,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+        request.addCapabilitiesToDisableForTech(TestImsFeature.CAPABILITY_TEST_1,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+
+        mTestImsFeature.changeEnabledCapabilities(request, /*Callback*/null);
+
+        assertTrue(request.getCapabilitiesToDisable().containsAll(
+                mTestImsFeature.lastRequest.getCapabilitiesToDisable()));
+        assertTrue(request.getCapabilitiesToEnable().containsAll(
+                mTestImsFeature.lastRequest.getCapabilitiesToEnable()));
+    }
+
+    @SmallTest
+    @Test
+    public void testCapabilityChangeRequestParcel() throws Exception {
+        CapabilityChangeRequest request = new CapabilityChangeRequest();
+        // add some capabilities
+        request.addCapabilitiesToEnableForTech(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+        request.addCapabilitiesToEnableForTech(
+                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO
+                        | MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+        request.addCapabilitiesToDisableForTech(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+        request.addCapabilitiesToDisableForTech(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT,
+                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+
+        Parcel p = Parcel.obtain();
+        request.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        CapabilityChangeRequest result =
+                CapabilityChangeRequest.CREATOR.createFromParcel(p);
+        p.recycle();
+
+        assertEquals(request, result);
+    }
+}
diff --git a/tests/telephonytests/src/android/telephony/ims/internal/ImsRegistrationTests.java b/tests/telephonytests/src/android/telephony/ims/internal/ImsRegistrationTests.java
new file mode 100644
index 0000000..4ae9205
--- /dev/null
+++ b/tests/telephonytests/src/android/telephony/ims/internal/ImsRegistrationTests.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.internal;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.ServiceState;
+import android.telephony.ims.internal.aidl.IImsRegistration;
+import android.telephony.ims.internal.feature.ImsFeature;
+import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
+import android.telephony.ims.internal.stub.ImsRegistrationImplBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.ims.ImsReasonInfo;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+@RunWith(AndroidJUnit4.class)
+public class ImsRegistrationTests {
+
+    @Spy private ImsRegistrationImplBase.Callback mCallback;
+    private TestImsRegistration mRegistration;
+    private IImsRegistration mRegBinder;
+
+    @Before
+    public void setup() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+        mRegistration = new TestImsRegistration();
+        mRegBinder = mRegistration.getBinder();
+        mRegBinder.addRegistrationCallback(mCallback);
+    }
+
+    @After
+    public void tearDown() {
+        mRegistration = null;
+        mRegBinder = null;
+    }
+
+    @SmallTest
+    @Test
+    public void testRegistrationConfigParcel() {
+        ImsFeatureConfiguration testConfig = new ImsFeatureConfiguration.Builder()
+                .addFeature(ImsFeature.FEATURE_MMTEL)
+                .addFeature(ImsFeature.FEATURE_RCS)
+                .build();
+        Parcel p = Parcel.obtain();
+        testConfig.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        ImsFeatureConfiguration result =
+                ImsFeatureConfiguration.CREATOR.createFromParcel(p);
+        p.recycle();
+
+        assertEquals(testConfig, result);
+    }
+
+    @SmallTest
+    @Test
+    public void testRegistrationConfigPermutationEqual() {
+        ImsFeatureConfiguration testConfig = new ImsFeatureConfiguration.Builder()
+                .addFeature(ImsFeature.FEATURE_MMTEL)
+                .addFeature(ImsFeature.FEATURE_RCS)
+                .build();
+
+        // Permute field insertion ordering to ensure order doesn't matter.
+        ImsFeatureConfiguration testConfig2 = new ImsFeatureConfiguration.Builder()
+                .addFeature(ImsFeature.FEATURE_RCS)
+                .addFeature(ImsFeature.FEATURE_MMTEL)
+                .build();
+
+        assertEquals(testConfig, testConfig2);
+    }
+
+    @SmallTest
+    @Test
+    public void testRegistrationConfigConstructorsEqual() {
+        ImsFeatureConfiguration testConfig = new ImsFeatureConfiguration(
+                new int[] {ImsFeature.FEATURE_MMTEL, ImsFeature.FEATURE_RCS});
+
+        // Permute field insertion ordering to ensure order doesn't matter.
+        ImsFeatureConfiguration testConfig2 = new ImsFeatureConfiguration.Builder()
+                .addFeature(ImsFeature.FEATURE_RCS)
+                .addFeature(ImsFeature.FEATURE_MMTEL)
+                .build();
+
+        assertEquals(testConfig, testConfig2);
+    }
+
+    @SmallTest
+    @Test
+    public void testRegistrationCallbackOnRegistered() throws RemoteException {
+        mRegistration.onRegistered(ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+
+        verify(mCallback).onRegistered(ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+    }
+
+    @SmallTest
+    @Test
+    public void testRegistrationCallbackOnRegistering() throws RemoteException {
+        mRegistration.onRegistering(ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+
+        verify(mCallback).onRegistering(ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+    }
+
+    @SmallTest
+    @Test
+    public void testRegistrationCallbackOnDeregistered() throws RemoteException {
+        ImsReasonInfo info = new ImsReasonInfo();
+        mRegistration.onDeregistered(info);
+
+        verify(mCallback).onDeregistered(eq(info));
+    }
+
+    @SmallTest
+    @Test
+    public void testRegistrationCallbackOnTechChangeFailed() throws RemoteException {
+        ImsReasonInfo info = new ImsReasonInfo();
+        mRegistration.onTechnologyChangeFailed(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+                info);
+
+        verify(mCallback).onTechnologyChangeFailed(
+                eq(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN), eq(info));
+    }
+
+    @SmallTest
+    @Test
+    public void testRegistrationCallbackAfterUnregistered() throws RemoteException {
+        mRegBinder.removeRegistrationCallback(mCallback);
+
+        mRegistration.onRegistered(ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+
+        verify(mCallback, never()).onRegistered(ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+    }
+
+    @SmallTest
+    @Test
+    public void testRegistrationCallbackSendCurrentState() throws RemoteException {
+        ImsRegistrationImplBase.Callback mCallback2 = spy(new ImsRegistrationImplBase.Callback());
+        mRegistration.onRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+
+        mRegBinder.addRegistrationCallback(mCallback2);
+
+        verify(mCallback2).onRegistered(eq(ImsRegistrationImplBase.REGISTRATION_TECH_LTE));
+    }
+
+    @SmallTest
+    @Test
+    public void testRegistrationCallbackGetRegistrationTech() throws RemoteException {
+        mRegistration.onRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+
+        assertEquals(ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
+                mRegBinder.getRegistrationTechnology());
+    }
+
+    @SmallTest
+    @Test
+    public void testRegistrationCallbackSendCurrentStateDisconnected() throws RemoteException {
+        ImsRegistrationImplBase.Callback mCallback2 = spy(new ImsRegistrationImplBase.Callback());
+        ImsReasonInfo info = new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE, 0);
+        mRegistration.onDeregistered(info);
+
+        mRegBinder.addRegistrationCallback(mCallback2);
+
+        // The original callback that has been registered should get LTE tech in disconnected
+        // message
+        verify(mCallback).onDeregistered(eq(info));
+        // A callback that has just been registered should get NONE for tech in disconnected
+        // message
+        verify(mCallback2).onDeregistered(eq(info));
+    }
+
+    @SmallTest
+    @Test
+    public void testRegistrationCallbackGetRegistrationTechDisconnected() throws RemoteException {
+        ImsReasonInfo info = new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE, 0);
+
+        mRegistration.onDeregistered(info);
+
+        verify(mCallback).onDeregistered(eq(info));
+        assertEquals(ImsRegistrationImplBase.REGISTRATION_TECH_NONE,
+                mRegBinder.getRegistrationTechnology());
+    }
+}
diff --git a/tests/telephonytests/src/android/telephony/ims/internal/ImsServiceTest.java b/tests/telephonytests/src/android/telephony/ims/internal/ImsServiceTest.java
new file mode 100644
index 0000000..f4c1149
--- /dev/null
+++ b/tests/telephonytests/src/android/telephony/ims/internal/ImsServiceTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.internal;
+
+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.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.ims.internal.aidl.IImsMmTelFeature;
+import android.telephony.ims.internal.aidl.IImsServiceController;
+import android.telephony.ims.internal.feature.ImsFeature;
+import android.telephony.ims.internal.feature.MmTelFeature;
+import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.SparseArray;
+
+import com.android.ims.ImsManager;
+import com.android.ims.internal.IImsFeatureStatusCallback;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Unit tests for ImsService
+ */
+@RunWith(AndroidJUnit4.class)
+public class ImsServiceTest {
+
+    private static final int TEST_SLOT_0 = 0;
+    private static final int TEST_SLOT_1 = 1;
+
+    private TestImsService mTestImsService;
+    private IImsServiceController mTestImsServiceBinder;
+
+    private Context mMockContext;
+    private IImsFeatureStatusCallback mTestCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockContext = mock(Context.class);
+        mTestCallback = mock(IImsFeatureStatusCallback.class);
+        mTestImsService = new TestImsService(mMockContext);
+        mTestImsServiceBinder = (IImsServiceController) mTestImsService.onBind(
+                new Intent(SERVICE_INTERFACE));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mMockContext = null;
+        mTestCallback = null;
+        mTestImsService = null;
+        mTestImsServiceBinder = null;
+    }
+
+    @Test
+    @SmallTest
+    public void testCreateMMTelFeature() throws RemoteException {
+        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.FEATURE_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, f.getFeatureState());
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoveMMTelFeature() throws RemoteException {
+        mTestImsServiceBinder.createMmTelFeature(TEST_SLOT_0, mTestCallback);
+
+        mTestImsServiceBinder.removeImsFeature(TEST_SLOT_0, ImsFeature.FEATURE_MMTEL,
+                mTestCallback);
+
+        verify(mTestImsService.mSpyMmTelFeature).onFeatureRemoved();
+        verify(mTestImsService.mSpyMmTelFeature).removeImsFeatureStatusCallback(mTestCallback);
+        SparseArray<ImsFeature> features = mTestImsService.getFeatures(TEST_SLOT_0);
+        assertNull(features.get(ImsFeature.FEATURE_MMTEL));
+    }
+
+    @Test
+    @SmallTest
+    public void testCallMethodOnCreatedFeature() throws RemoteException {
+        IImsMmTelFeature f = mTestImsServiceBinder.createMmTelFeature(TEST_SLOT_0, mTestCallback);
+
+        f.getUtInterface();
+
+        assertTrue(mTestImsService.mTestMmTelFeature.isUtInterfaceCalled);
+    }
+
+    /**
+     * Tests that the new ImsService still sends the IMS_SERVICE_UP broadcast when the feature is
+     * set to ready.
+     */
+    @Test
+    @SmallTest
+    public void testImsServiceUpSentCompat() throws RemoteException {
+        mTestImsServiceBinder.createMmTelFeature(TEST_SLOT_0, mTestCallback);
+
+        mTestImsService.mSpyMmTelFeature.sendSetFeatureState(ImsFeature.STATE_READY);
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext).sendBroadcast(intentCaptor.capture());
+        try {
+            // Verify IMS_SERVICE_UP is sent
+            assertNotNull(intentCaptor.getValue());
+            verifyServiceUpSent(intentCaptor.getValue());
+        } catch (IndexOutOfBoundsException e) {
+            fail("Did not receive all intents");
+        }
+    }
+
+    /**
+     * Tests that the new ImsService still sends the IMS_SERVICE_DOWN broadcast when the feature is
+     * set to initializing.
+     */
+    @Test
+    @SmallTest
+    public void testImsServiceDownSentCompatInitializing() throws RemoteException {
+        mTestImsServiceBinder.createMmTelFeature(TEST_SLOT_0, mTestCallback);
+
+        mTestImsService.mSpyMmTelFeature.sendSetFeatureState(ImsFeature.STATE_INITIALIZING);
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext).sendBroadcast(intentCaptor.capture());
+        try {
+            // IMS_SERVICE_DOWN is sent when the service is STATE_INITIALIZING.
+            assertNotNull(intentCaptor.getValue());
+            verifyServiceDownSent(intentCaptor.getValue());
+        } catch (IndexOutOfBoundsException e) {
+            fail("Did not receive all intents");
+        }
+    }
+
+    /**
+     * Tests that the ImsService will return the correct ImsFeatureConfiguration when queried.
+     */
+    @Test
+    @SmallTest
+    public void testQuerySupportedImsFeatures() throws RemoteException {
+        ImsFeatureConfiguration config = new ImsFeatureConfiguration.Builder()
+                .addFeature(ImsFeature.FEATURE_MMTEL)
+                .addFeature(ImsFeature.FEATURE_RCS)
+                .build();
+        mTestImsService.testFeatureConfig = config;
+
+        ImsFeatureConfiguration result = mTestImsServiceBinder.querySupportedImsFeatures();
+
+        assertEquals(config, result);
+    }
+
+    private void verifyServiceDownSent(Intent testIntent) {
+        assertEquals(ImsManager.ACTION_IMS_SERVICE_DOWN, testIntent.getAction());
+        assertEquals(TEST_SLOT_0, testIntent.getIntExtra(ImsManager.EXTRA_PHONE_ID, -1));
+    }
+
+    private void verifyServiceUpSent(Intent testIntent) {
+        assertEquals(ImsManager.ACTION_IMS_SERVICE_UP, testIntent.getAction());
+        assertEquals(TEST_SLOT_0, testIntent.getIntExtra(ImsManager.EXTRA_PHONE_ID, -1));
+    }
+}
diff --git a/tests/telephonytests/src/android/telephony/ims/internal/MmTelFeatureTests.java b/tests/telephonytests/src/android/telephony/ims/internal/MmTelFeatureTests.java
new file mode 100644
index 0000000..c2cdd75
--- /dev/null
+++ b/tests/telephonytests/src/android/telephony/ims/internal/MmTelFeatureTests.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.internal;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.os.RemoteException;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.ims.internal.aidl.IImsMmTelFeature;
+import android.telephony.ims.internal.feature.ImsFeature;
+import android.telephony.ims.internal.feature.MmTelFeature;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.ImsCallSession;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+@RunWith(AndroidJUnit4.class)
+public class MmTelFeatureTests {
+
+    private static final int TEST_CAPABILITY = 1;
+    private static final int TEST_RADIO_TECH = 0;
+
+    private TestMmTelFeature mFeature;
+    private IImsMmTelFeature mFeatureBinder;
+    private ImsFeature.CapabilityCallback mCapabilityCallback;
+    private MmTelFeature.Listener mListener;
+
+    @Before
+    public void setup() throws RemoteException {
+        mFeature = new TestMmTelFeature();
+        mFeatureBinder = mFeature.getBinder();
+        mCapabilityCallback = spy(new ImsFeature.CapabilityCallback());
+        mListener = spy(new MmTelFeature.Listener());
+        mFeatureBinder.setListener(mListener);
+    }
+
+    @After
+    public void tearDown() {
+        mFeature = null;
+        mFeatureBinder = null;
+    }
+
+    @SmallTest
+    @Test
+    public void testQueryCapabilityConfiguration() throws Exception {
+        mFeature.queryConfigurationResult = true;
+
+        mFeatureBinder.queryCapabilityConfiguration(TEST_CAPABILITY, TEST_RADIO_TECH,
+                mCapabilityCallback);
+
+        verify(mCapabilityCallback).onQueryCapabilityConfiguration(eq(TEST_CAPABILITY),
+                eq(TEST_RADIO_TECH), eq(true));
+    }
+
+    @SmallTest
+    @Test
+    public void testNewIncomingCall() throws Exception {
+        IImsCallSession sessionBinder = Mockito.mock(IImsCallSession.class);
+        ImsCallSession session = new ImsCallSession(sessionBinder);
+
+        mFeature.incomingCall(session);
+        ArgumentCaptor<ImsCallSession> captor = ArgumentCaptor.forClass(ImsCallSession.class);
+        verify(mListener).onIncomingCall(captor.capture());
+
+        assertEquals(sessionBinder, captor.getValue().getSession());
+    }
+}
diff --git a/tests/telephonytests/src/android/telephony/ims/internal/TestImsFeature.java b/tests/telephonytests/src/android/telephony/ims/internal/TestImsFeature.java
new file mode 100644
index 0000000..efbf95e
--- /dev/null
+++ b/tests/telephonytests/src/android/telephony/ims/internal/TestImsFeature.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.internal;
+
+import android.os.IInterface;
+import android.telephony.ims.internal.feature.CapabilityChangeRequest;
+import android.telephony.ims.internal.feature.ImsFeature;
+
+public class TestImsFeature extends ImsFeature {
+
+
+    public static final int CAPABILITY_TEST_1 = 1 << 0;
+    public static final int CAPABILITY_TEST_2 = 1 << 1;
+
+    public int setCapabilitiesResult = ImsFeature.CAPABILITY_SUCCESS;
+    public CapabilityChangeRequest lastRequest;
+    public int onFeatureRemovedCount = 0;
+    public int onFeatureReadyCount = 0;
+
+    public void testSetFeatureState(int featureState) {
+        setFeatureState(featureState);
+    }
+
+    public void capabilitiesStatusChanged(Capabilities c) {
+        notifyCapabilitiesStatusChanged(c);
+    }
+
+    @Override
+    public void changeEnabledCapabilities(CapabilityChangeRequest request,
+            CapabilityCallbackProxy c) {
+        lastRequest = request;
+        if (setCapabilitiesResult != ImsFeature.CAPABILITY_SUCCESS) {
+            // Take the first value to enable and return it as an error.
+            CapabilityChangeRequest.CapabilityPair capPair = request.getCapabilitiesToEnable()
+                    .get(0);
+            c.onChangeCapabilityConfigurationError(capPair.getCapability(), capPair.getRadioTech(),
+                    ImsFeature.CAPABILITY_ERROR_GENERIC);
+        }
+    }
+
+    @Override
+    public void onFeatureRemoved() {
+        onFeatureRemovedCount++;
+    }
+
+    @Override
+    public void onFeatureReady() {
+        onFeatureReadyCount++;
+    }
+
+    @Override
+    public IInterface getBinder() {
+        return null;
+    }
+}
diff --git a/tests/telephonytests/src/android/telephony/ims/internal/TestImsRegistration.java b/tests/telephonytests/src/android/telephony/ims/internal/TestImsRegistration.java
new file mode 100644
index 0000000..6b94efb
--- /dev/null
+++ b/tests/telephonytests/src/android/telephony/ims/internal/TestImsRegistration.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.internal;
+
+import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
+import android.telephony.ims.internal.stub.ImsRegistrationImplBase;
+
+public class TestImsRegistration  extends ImsRegistrationImplBase {
+
+    private ImsFeatureConfiguration mTestConfig;
+    private ImsRegistrationImplBase.Callback mCallback = new ImsRegistrationImplBase.Callback() {
+
+    };
+
+    public void setConfig(ImsFeatureConfiguration c) {
+        mTestConfig = c;
+    }
+
+}
diff --git a/tests/telephonytests/src/android/telephony/ims/internal/TestImsService.java b/tests/telephonytests/src/android/telephony/ims/internal/TestImsService.java
new file mode 100644
index 0000000..97dfff7
--- /dev/null
+++ b/tests/telephonytests/src/android/telephony/ims/internal/TestImsService.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.internal;
+
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.telephony.ims.internal.feature.MmTelFeature;
+import android.telephony.ims.internal.feature.RcsFeature;
+import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
+
+/**
+ * Test ImsService used by mockito to verify functionality.
+ */
+
+public class TestImsService extends ImsService {
+
+    public TestMmTelFeature mSpyMmTelFeature;
+    public TestMmTelFeature mTestMmTelFeature;
+
+    public ImsFeatureConfiguration testFeatureConfig;
+
+    public TestImsService(Context context) {
+        attachBaseContext(context);
+        // Must create real MMTelFeature to initialize ImsFeature objects.
+        mTestMmTelFeature = new TestMmTelFeature();
+        mSpyMmTelFeature = spy(mTestMmTelFeature);
+    }
+
+    @Override
+    public ImsFeatureConfiguration querySupportedImsFeatures() {
+        return testFeatureConfig;
+    }
+
+    @Override
+    public MmTelFeature createMmTelFeature(int slotId) {
+        return mSpyMmTelFeature;
+    }
+
+    @Override
+    public RcsFeature createRcsFeature(int slotId) {
+        return null;
+    }
+}
diff --git a/tests/telephonytests/src/android/telephony/ims/internal/TestMmTelFeature.java b/tests/telephonytests/src/android/telephony/ims/internal/TestMmTelFeature.java
new file mode 100644
index 0000000..1f5b23e
--- /dev/null
+++ b/tests/telephonytests/src/android/telephony/ims/internal/TestMmTelFeature.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.internal;
+
+import android.os.RemoteException;
+import android.telephony.ims.internal.feature.CapabilityChangeRequest;
+import android.telephony.ims.internal.feature.ImsFeature;
+import android.telephony.ims.internal.feature.MmTelFeature;
+import android.telephony.ims.internal.stub.ImsRegistrationImplBase;
+import android.telephony.ims.stub.ImsEcbmImplBase;
+import android.telephony.ims.stub.ImsMultiEndpointImplBase;
+import android.telephony.ims.stub.ImsUtImplBase;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.internal.ImsCallSession;
+
+public class TestMmTelFeature extends MmTelFeature {
+
+    public boolean queryConfigurationResult = false;
+    public int setCapabilitiesResult = ImsFeature.CAPABILITY_SUCCESS;
+    public CapabilityChangeRequest lastRequest;
+    public boolean isUtInterfaceCalled = false;
+
+    public void incomingCall(ImsCallSession c) throws RemoteException {
+        notifyIncomingCall(c);
+    }
+
+    @Override
+    public ImsCallProfile createCallProfile(int callSessionType, int callType) {
+        return super.createCallProfile(callSessionType, callType);
+    }
+
+    @Override
+    public ImsCallSession createCallSession(ImsCallProfile profile,
+            ImsCallSessionListener listener) {
+        return super.createCallSession(profile, listener);
+    }
+
+    @Override
+    public ImsUtImplBase getUt() {
+        isUtInterfaceCalled = true;
+        return super.getUt();
+    }
+
+    @Override
+    public ImsEcbmImplBase getEcbm() {
+        return super.getEcbm();
+    }
+
+    @Override
+    public ImsMultiEndpointImplBase getMultiEndpoint() {
+        return super.getMultiEndpoint();
+    }
+
+    @Override
+    public boolean queryCapabilityConfiguration(@MmTelCapabilities.MmTelCapability int capability,
+            @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
+        // Base implementation - Override to provide functionality
+        return queryConfigurationResult;
+    }
+
+    @Override
+    public void changeEnabledCapabilities(CapabilityChangeRequest request,
+            CapabilityCallbackProxy c) {
+        lastRequest = request;
+        if (setCapabilitiesResult != ImsFeature.CAPABILITY_SUCCESS) {
+            // Take the first value to enable and return it as an error.
+            CapabilityChangeRequest.CapabilityPair capPair = request.getCapabilitiesToEnable()
+                    .get(0);
+            c.onChangeCapabilityConfigurationError(capPair.getCapability(), capPair.getRadioTech(),
+                    ImsFeature.CAPABILITY_ERROR_GENERIC);
+        }
+    }
+
+    public void sendSetFeatureState(int state) {
+        setFeatureState(state);
+    }
+
+    @Override
+    public void onFeatureRemoved() {
+    }
+
+    @Override
+    public void onFeatureReady() {
+    }
+}