1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.internal.telephony.imsphone;
17
18import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
19
20import static org.junit.Assert.assertEquals;
21import static org.junit.Assert.assertFalse;
22import static org.junit.Assert.assertTrue;
23import static org.mockito.ArgumentMatchers.nullable;
24import static org.mockito.Mockito.any;
25import static org.mockito.Mockito.anyBoolean;
26import static org.mockito.Mockito.anyInt;
27import static org.mockito.Mockito.doAnswer;
28import static org.mockito.Mockito.doNothing;
29import static org.mockito.Mockito.doReturn;
30import static org.mockito.Mockito.doThrow;
31import static org.mockito.Mockito.eq;
32import static org.mockito.Mockito.isNull;
33import static org.mockito.Mockito.mock;
34import static org.mockito.Mockito.never;
35import static org.mockito.Mockito.spy;
36import static org.mockito.Mockito.times;
37import static org.mockito.Mockito.verify;
38
39import android.content.Context;
40import android.content.SharedPreferences;
41import android.os.Bundle;
42import android.os.Handler;
43import android.os.HandlerThread;
44import android.os.Message;
45import android.os.PersistableBundle;
46import android.support.test.filters.FlakyTest;
47import android.telecom.VideoProfile;
48import android.telephony.CarrierConfigManager;
49import android.telephony.DisconnectCause;
50import android.telephony.PhoneNumberUtils;
51import android.telephony.ServiceState;
52import android.telephony.TelephonyManager;
53import android.telephony.ims.ImsCallProfile;
54import android.telephony.ims.ImsCallSession;
55import android.telephony.ims.ImsReasonInfo;
56import android.telephony.ims.ImsStreamMediaProfile;
57import android.telephony.ims.feature.ImsFeature;
58import android.telephony.ims.feature.MmTelFeature;
59import android.telephony.ims.stub.ImsRegistrationImplBase;
60import android.test.suitebuilder.annotation.SmallTest;
61
62import com.android.ims.ImsCall;
63import com.android.ims.ImsConfig;
64import com.android.ims.ImsException;
65import com.android.ims.internal.IImsCallSession;
66import com.android.internal.telephony.Call;
67import com.android.internal.telephony.CallStateException;
68import com.android.internal.telephony.CommandsInterface;
69import com.android.internal.telephony.Connection;
70import com.android.internal.telephony.PhoneConstants;
71import com.android.internal.telephony.TelephonyTest;
72
73import org.junit.After;
74import org.junit.Assert;
75import org.junit.Before;
76import org.junit.Ignore;
77import org.junit.Test;
78import org.mockito.ArgumentCaptor;
79import org.mockito.Mock;
80import org.mockito.invocation.InvocationOnMock;
81import org.mockito.stubbing.Answer;
82
83public class ImsPhoneCallTrackerTest extends TelephonyTest {
84    private ImsPhoneCallTracker mCTUT;
85    private ImsCTHandlerThread mImsCTHandlerThread;
86    private MmTelFeature.Listener mMmTelListener;
87    private ImsRegistrationImplBase.Callback mRegistrationCallback;
88    private ImsFeature.CapabilityCallback mCapabilityCallback;
89    private ImsCall.Listener mImsCallListener;
90    private ImsCall mImsCall;
91    private ImsCall mSecondImsCall;
92    private Bundle mBundle = new Bundle();
93    private int mServiceId;
94    @Mock
95    private ImsCallSession mImsCallSession;
96    @Mock
97    private SharedPreferences mSharedPreferences;
98    @Mock
99    private ImsPhoneConnection.Listener mImsPhoneConnectionListener;
100    @Mock
101    private ImsConfig mImsConfig;
102    private Handler mCTHander;
103
104    private class ImsCTHandlerThread extends HandlerThread {
105
106        private ImsCTHandlerThread(String name) {
107            super(name);
108        }
109        @Override
110        public void onLooperPrepared() {
111            mCTUT = new ImsPhoneCallTracker(mImsPhone);
112            mCTUT.addReasonCodeRemapping(null, "Wifi signal lost.", ImsReasonInfo.CODE_WIFI_LOST);
113            mCTUT.addReasonCodeRemapping(501, "Call answered elsewhere.",
114                    ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
115            mCTUT.addReasonCodeRemapping(510, "Call answered elsewhere.",
116                    ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
117            mCTUT.setDataEnabled(true);
118            mCTHander = new Handler(mCTUT.getLooper());
119            setReady(true);
120        }
121    }
122
123    private void imsCallMocking(final ImsCall mImsCall) throws Exception {
124
125        doAnswer(new Answer<Void>() {
126            @Override
127            public Void answer(InvocationOnMock invocation) throws Throwable {
128                // trigger the listener on accept call
129                if (mImsCallListener != null) {
130                    mImsCallListener.onCallStarted(mImsCall);
131                }
132                return null;
133            }
134        }).when(mImsCall).accept(anyInt());
135
136        doAnswer(new Answer<Void>() {
137            @Override
138            public Void answer(InvocationOnMock invocation) throws Throwable {
139                // trigger the listener on reject call
140                int reasonCode = (int) invocation.getArguments()[0];
141                if (mImsCallListener != null) {
142                    mImsCallListener.onCallStartFailed(mImsCall, new ImsReasonInfo(reasonCode, -1));
143                    mImsCallListener.onCallTerminated(mImsCall, new ImsReasonInfo(reasonCode, -1));
144                }
145                return null;
146            }
147        }).when(mImsCall).reject(anyInt());
148
149        doAnswer(new Answer<Void>() {
150            @Override
151            public Void answer(InvocationOnMock invocation) throws Throwable {
152                // trigger the listener on reject call
153                int reasonCode = (int) invocation.getArguments()[0];
154                if (mImsCallListener != null) {
155                    mImsCallListener.onCallTerminated(mImsCall, new ImsReasonInfo(reasonCode, -1));
156                }
157                return null;
158            }
159        }).when(mImsCall).terminate(anyInt());
160
161        doAnswer(new Answer<Void>() {
162            @Override
163            public Void answer(InvocationOnMock invocation) throws Throwable {
164                if (mImsCallListener != null) {
165                    mImsCallListener.onCallHeld(mImsCall);
166                }
167                return null;
168            }
169        }).when(mImsCall).hold();
170
171        mImsCall.attachSession(mImsCallSession);
172    }
173
174    @Before
175    public void setUp() throws Exception {
176        super.setUp(this.getClass().getSimpleName());
177        mServiceId = 0;
178        mImsCallProfile.mCallExtras = mBundle;
179        mImsManagerInstances.put(mImsPhone.getPhoneId(), mImsManager);
180        mImsCall = spy(new ImsCall(mContext, mImsCallProfile));
181        mSecondImsCall = spy(new ImsCall(mContext, mImsCallProfile));
182        mImsPhoneConnectionListener = mock(ImsPhoneConnection.Listener.class);
183        imsCallMocking(mImsCall);
184        imsCallMocking(mSecondImsCall);
185        doReturn(ImsFeature.STATE_READY).when(mImsManager).getImsServiceState();
186        doReturn(mImsCallProfile).when(mImsManager).createCallProfile(anyInt(), anyInt());
187
188        doAnswer(invocation -> {
189            mMmTelListener = (MmTelFeature.Listener) invocation.getArguments()[0];
190            return null;
191        }).when(mImsManager).open(any(MmTelFeature.Listener.class));
192
193
194        doAnswer(new Answer<ImsCall>() {
195            @Override
196            public ImsCall answer(InvocationOnMock invocation) throws Throwable {
197                mImsCallListener =
198                        (ImsCall.Listener) invocation.getArguments()[2];
199                mImsCall.setListener(mImsCallListener);
200                return mImsCall;
201            }
202        }).when(mImsManager).takeCall(any(), any(), any());
203
204        doAnswer(new Answer<ImsCall>() {
205            @Override
206            public ImsCall answer(InvocationOnMock invocation) throws Throwable {
207                mImsCallListener =
208                        (ImsCall.Listener) invocation.getArguments()[2];
209                mSecondImsCall.setListener(mImsCallListener);
210                return mSecondImsCall;
211            }
212        }).when(mImsManager).makeCall(eq(mImsCallProfile), (String []) any(),
213                (ImsCall.Listener) any());
214
215        doAnswer(invocation -> {
216            mRegistrationCallback = invocation.getArgument(0);
217            return mRegistrationCallback;
218        }).when(mImsManager).addRegistrationCallback(any(ImsRegistrationImplBase.Callback.class));
219
220        doAnswer(invocation -> {
221            mCapabilityCallback = (ImsFeature.CapabilityCallback) invocation.getArguments()[0];
222            return mCapabilityCallback;
223
224        }).when(mImsManager).addCapabilitiesCallback(any(ImsFeature.CapabilityCallback.class));
225
226        doReturn(mImsConfig).when(mImsManager).getConfigInterface();
227
228        doNothing().when(mImsManager).addNotifyStatusChangedCallbackIfAvailable(any());
229
230        mImsCTHandlerThread = new ImsCTHandlerThread(this.getClass().getSimpleName());
231        mImsCTHandlerThread.start();
232
233        waitUntilReady();
234        logd("ImsPhoneCallTracker initiated");
235        /* Make sure getImsService is triggered on handler */
236        waitForHandlerAction(mCTHander, 100);
237    }
238
239    @After
240    public void tearDown() throws Exception {
241        mCTUT = null;
242        mImsCTHandlerThread.quit();
243        super.tearDown();
244    }
245
246    @Test
247    @SmallTest
248    public void testImsRegistered() {
249        // when IMS is registered
250        mRegistrationCallback.onRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
251        // then service state should be IN_SERVICE and ImsPhone state set to registered
252        verify(mImsPhone).setServiceState(eq(ServiceState.STATE_IN_SERVICE));
253        verify(mImsPhone).setImsRegistered(eq(true));
254    }
255
256    @Test
257    @SmallTest
258    public void testImsRegistering() {
259        // when IMS is registering
260        mRegistrationCallback.onRegistering(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
261        // then service state should be OUT_OF_SERVICE and ImsPhone state set to not registered
262        verify(mImsPhone).setServiceState(eq(ServiceState.STATE_OUT_OF_SERVICE));
263        verify(mImsPhone).setImsRegistered(eq(false));
264    }
265
266    @Test
267    @SmallTest
268    public void testImsDeregistered() {
269        // when IMS is deregistered
270        mRegistrationCallback.onDeregistered(new ImsReasonInfo());
271        // then service state should be OUT_OF_SERVICE and ImsPhone state set to not registered
272        verify(mImsPhone).setServiceState(eq(ServiceState.STATE_OUT_OF_SERVICE));
273        verify(mImsPhone).setImsRegistered(eq(false));
274    }
275
276    @Test
277    @SmallTest
278    public void testVowifiDisabledOnLte() {
279        // LTE is registered.
280        doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_LTE).when(
281                mImsManager).getRegistrationTech();
282        assertFalse(mCTUT.isVowifiEnabled());
283
284        // enable Voice over LTE
285        ImsFeature.Capabilities caps = new ImsFeature.Capabilities();
286        caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
287        mCapabilityCallback.onCapabilitiesStatusChanged(caps);
288        waitForHandlerAction(mCTHander, 1000);
289
290        // Voice over IWLAN is still disabled
291        assertFalse(mCTUT.isVowifiEnabled());
292    }
293
294    @Test
295    @SmallTest
296    public void testVowifiDisabledOnIwlan() {
297        // LTE is registered.
298        doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN).when(
299                mImsManager).getRegistrationTech();
300        assertFalse(mCTUT.isVowifiEnabled());
301
302        // enable Voice over IWLAN
303        ImsFeature.Capabilities caps = new ImsFeature.Capabilities();
304        caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
305        mCapabilityCallback.onCapabilitiesStatusChanged(caps);
306        waitForHandlerAction(mCTHander, 1000);
307
308        // Voice over IWLAN is enabled
309        assertTrue(mCTUT.isVowifiEnabled());
310    }
311
312    @Test
313    @SmallTest
314    public void testImsFeatureCapabilityChange() {
315        doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_LTE).when(
316                mImsManager).getRegistrationTech();
317        assertFalse(mCTUT.isVolteEnabled());
318        assertFalse(mCTUT.isVideoCallEnabled());
319
320        // enable only Voice
321        ImsFeature.Capabilities caps = new ImsFeature.Capabilities();
322        caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
323        mCapabilityCallback.onCapabilitiesStatusChanged(caps);
324        waitForHandlerAction(mCTHander, 1000);
325
326        assertTrue(mCTUT.isVolteEnabled());
327        assertFalse(mCTUT.isVideoCallEnabled());
328        // video call not enabled
329        verify(mImsPhone, times(0)).notifyForVideoCapabilityChanged(anyBoolean());
330        verify(mImsPhone, times(1)).onFeatureCapabilityChanged();
331
332        // enable video call
333        ImsFeature.Capabilities capsVideo = new ImsFeature.Capabilities();
334        capsVideo.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
335        capsVideo.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
336        mCapabilityCallback.onCapabilitiesStatusChanged(capsVideo);
337        waitForHandlerAction(mCTHander, 1000);
338        assertTrue(mCTUT.isVideoCallEnabled());
339        verify(mImsPhone, times(1)).notifyForVideoCapabilityChanged(eq(true));
340    }
341
342    @Test
343    @SmallTest
344    public void testImsMTCall() {
345        assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
346        assertFalse(mCTUT.mRingingCall.isRinging());
347        // mock a MT call
348        mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
349        verify(mImsPhone, times(1)).notifyNewRingingConnection((Connection) any());
350        verify(mImsPhone, times(1)).notifyIncomingRing();
351        assertEquals(PhoneConstants.State.RINGING, mCTUT.getState());
352        assertTrue(mCTUT.mRingingCall.isRinging());
353        assertEquals(1, mCTUT.mRingingCall.getConnections().size());
354        ImsPhoneConnection connection =
355                (ImsPhoneConnection) mCTUT.mRingingCall.getConnections().get(0);
356        connection.addListener(mImsPhoneConnectionListener);
357    }
358
359    @Test
360    @SmallTest
361    public void testImsMTCallAccept() {
362        testImsMTCall();
363        assertTrue(mCTUT.mRingingCall.isRinging());
364        try {
365            mCTUT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
366            verify(mImsCall, times(1)).accept(eq(ImsCallProfile
367                    .getCallTypeFromVideoState(ImsCallProfile.CALL_TYPE_VOICE)));
368        } catch (Exception ex) {
369            ex.printStackTrace();
370            Assert.fail("unexpected exception thrown" + ex.getMessage());
371        }
372        assertFalse(mCTUT.mRingingCall.isRinging());
373        assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
374        assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
375        assertEquals(1, mCTUT.mForegroundCall.getConnections().size());
376    }
377
378    @Test
379    @SmallTest
380    public void testImsMTCallReject() {
381        testImsMTCall();
382        assertTrue(mCTUT.mRingingCall.isRinging());
383        try {
384            mCTUT.rejectCall();
385            verify(mImsCall, times(1)).reject(eq(ImsReasonInfo.CODE_USER_DECLINE));
386        } catch (Exception ex) {
387            ex.printStackTrace();
388            Assert.fail("unexpected exception thrown" + ex.getMessage());
389        }
390        assertFalse(mCTUT.mRingingCall.isRinging());
391        assertEquals(0, mCTUT.mRingingCall.getConnections().size());
392        assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
393    }
394
395    @Test
396    @SmallTest
397    public void testImsMTCallAcceptHangUp() {
398        testImsMTCallAccept();
399        assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
400        assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
401        try {
402            mCTUT.hangup(mCTUT.mForegroundCall);
403        } catch (Exception ex) {
404            ex.printStackTrace();
405            Assert.fail("unexpected exception thrown" + ex.getMessage());
406        }
407        assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
408        assertEquals(Call.State.IDLE, mCTUT.mForegroundCall.getState());
409    }
410
411    @Test
412    @SmallTest
413    public void testImsMTCallAcceptHold() {
414        testImsMTCallAccept();
415
416        assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
417        assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
418        // mock a new MT
419        try {
420            doReturn(mSecondImsCall).when(mImsManager).takeCall(any(IImsCallSession.class),
421                    any(Bundle.class), any(ImsCall.Listener.class));
422        } catch (Exception ex) {
423            ex.printStackTrace();
424            Assert.fail("unexpected exception thrown" + ex.getMessage());
425        }
426        mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
427
428        verify(mImsPhone, times(2)).notifyNewRingingConnection((Connection) any());
429        verify(mImsPhone, times(2)).notifyIncomingRing();
430        assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
431        assertEquals(ImsPhoneCall.State.WAITING, mCTUT.mRingingCall.getState());
432        assertEquals(PhoneConstants.State.RINGING, mCTUT.getState());
433
434        //hold the foreground active call, accept the new ringing call
435        try {
436            mCTUT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
437            verify(mImsCall, times(1)).hold();
438        } catch (Exception ex) {
439            ex.printStackTrace();
440            Assert.fail("unexpected exception thrown" + ex.getMessage());
441        }
442
443        waitForMs(100);
444        assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
445        assertFalse(mCTUT.mRingingCall.isRinging());
446        assertEquals(Call.State.HOLDING, mCTUT.mBackgroundCall.getState());
447    }
448
449    /**
450     * Ensures that the dial method will perform a shared preferences lookup using the correct
451     * shared preference key to determine the CLIR mode.
452     */
453    @Test
454    @SmallTest
455    public void testDialClirMode() {
456        mCTUT.setSharedPreferenceProxy((Context context) -> {
457            return mSharedPreferences;
458        });
459        ArgumentCaptor<String> mStringCaptor = ArgumentCaptor.forClass(String.class);
460        doReturn(CommandsInterface.CLIR_INVOCATION).when(mSharedPreferences).getInt(
461                mStringCaptor.capture(), anyInt());
462
463        try {
464            mCTUT.dial("+17005554141", VideoProfile.STATE_AUDIO_ONLY, null);
465        } catch (CallStateException cse) {
466            cse.printStackTrace();
467            Assert.fail("unexpected exception thrown" + cse.getMessage());
468        }
469
470        // Ensure that the correct key was queried from the shared prefs.
471        assertEquals("clir_key0", mStringCaptor.getValue());
472    }
473
474    /**
475     * Ensures for an emergency call that the dial method will default the CLIR to
476     * {@link CommandsInterface#CLIR_SUPPRESSION}, ensuring the caller's ID is shown.
477     */
478    @Test
479    @SmallTest
480    public void testEmergencyDialSuppressClir() {
481        mCTUT.setSharedPreferenceProxy((Context context) -> {
482            return mSharedPreferences;
483        });
484        // Mock implementation of phone number utils treats everything as an emergency.
485        mCTUT.setPhoneNumberUtilsProxy((String string) -> {
486            return true;
487        });
488        // Set preference to hide caller ID.
489        ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class);
490        doReturn(CommandsInterface.CLIR_INVOCATION).when(mSharedPreferences).getInt(
491                stringCaptor.capture(), anyInt());
492
493        try {
494            mCTUT.dial("+17005554141", VideoProfile.STATE_AUDIO_ONLY, null);
495
496            ArgumentCaptor<ImsCallProfile> profileCaptor = ArgumentCaptor.forClass(
497                    ImsCallProfile.class);
498            verify(mImsManager, times(1)).makeCall(eq(mImsCallProfile),
499                    eq(new String[]{"+17005554141"}), any());
500
501            // Because this is an emergency call, we expect caller id to be visible now.
502            assertEquals(mImsCallProfile.getCallExtraInt(ImsCallProfile.EXTRA_OIR),
503                    CommandsInterface.CLIR_SUPPRESSION);
504        } catch (CallStateException cse) {
505            cse.printStackTrace();
506            Assert.fail("unexpected exception thrown" + cse.getMessage());
507        } catch (ImsException ie) {
508            ie.printStackTrace();
509            Assert.fail("unexpected exception thrown" + ie.getMessage());
510        }
511    }
512
513    @FlakyTest
514    @Ignore
515    @Test
516    @SmallTest
517    public void testImsMOCallDial() {
518        assertEquals(Call.State.IDLE, mCTUT.mForegroundCall.getState());
519        assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
520        try {
521            mCTUT.dial("+17005554141", ImsCallProfile.CALL_TYPE_VOICE, null);
522            verify(mImsManager, times(1)).makeCall(eq(mImsCallProfile),
523                    eq(new String[]{"+17005554141"}), (ImsCall.Listener) any());
524        } catch (Exception ex) {
525            ex.printStackTrace();
526            Assert.fail("unexpected exception thrown" + ex.getMessage());
527        }
528        assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
529        assertEquals(Call.State.DIALING, mCTUT.mForegroundCall.getState());
530        //call established
531        mImsCallListener.onCallProgressing(mSecondImsCall);
532        assertEquals(Call.State.ALERTING, mCTUT.mForegroundCall.getState());
533    }
534
535    @FlakyTest
536    @Ignore
537    @Test
538    @SmallTest
539    public void testImsMTActiveMODial() {
540        assertEquals(Call.State.IDLE, mCTUT.mForegroundCall.getState());
541        assertEquals(Call.State.IDLE, mCTUT.mBackgroundCall.getState());
542
543        testImsMTCallAccept();
544
545        assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
546        assertEquals(Call.State.IDLE, mCTUT.mBackgroundCall.getState());
547        try {
548            mCTUT.dial("+17005554141", ImsCallProfile.CALL_TYPE_VOICE, null);
549            verify(mImsManager, times(1)).makeCall(eq(mImsCallProfile),
550                    eq(new String[]{"+17005554141"}), (ImsCall.Listener) any());
551        } catch (Exception ex) {
552            ex.printStackTrace();
553            Assert.fail("unexpected exception thrown" + ex.getMessage());
554        }
555        waitForMs(100);
556        assertEquals(Call.State.DIALING, mCTUT.mForegroundCall.getState());
557        assertEquals(Call.State.HOLDING, mCTUT.mBackgroundCall.getState());
558    }
559
560    @Test
561    @SmallTest
562    public void testImsMOCallHangup() {
563        testImsMOCallDial();
564        //hangup before call go to active
565        try {
566            mCTUT.hangup(mCTUT.mForegroundCall);
567        } catch (Exception ex) {
568            ex.printStackTrace();
569            Assert.fail("unexpected exception thrown" + ex.getMessage());
570        }
571        assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
572        assertEquals(Call.State.IDLE, mCTUT.mForegroundCall.getState());
573    }
574
575    @Test
576    @SmallTest
577    public void testImsSendDtmf() {
578        //establish a MT call
579        testImsMTCallAccept();
580        mCTUT.sendDtmf(PhoneNumberUtils.PAUSE, null);
581        //verify trigger sendDtmf to mImsCall
582        verify(mImsCall, times(1)).sendDtmf(eq(PhoneNumberUtils.PAUSE), (Message) isNull());
583        // mock a new MT
584        try {
585            doReturn(mSecondImsCall).when(mImsManager).takeCall(any(IImsCallSession.class),
586                    any(Bundle.class), any(ImsCall.Listener.class));
587            mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
588            mCTUT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
589        } catch (Exception ex) {
590            ex.printStackTrace();
591            Assert.fail("unexpected exception thrown" + ex.getMessage());
592        }
593
594        waitForMs(100);
595
596        mCTUT.sendDtmf(PhoneNumberUtils.WAIT, null);
597        //verify trigger sendDtmf to mImsSecondCall
598        verify(mSecondImsCall, times(1)).sendDtmf(eq(PhoneNumberUtils.WAIT), (Message) isNull());
599    }
600
601    @Test
602    @SmallTest
603    public void testReasonCodeRemap() {
604        assertEquals(ImsReasonInfo.CODE_WIFI_LOST, mCTUT.maybeRemapReasonCode(
605                new ImsReasonInfo(1, 1, "Wifi signal lost.")));
606        assertEquals(ImsReasonInfo.CODE_WIFI_LOST, mCTUT.maybeRemapReasonCode(
607                new ImsReasonInfo(200, 1, "Wifi signal lost.")));
608        assertEquals(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE,
609                mCTUT.maybeRemapReasonCode(new ImsReasonInfo(501, 1, "Call answered elsewhere.")));
610        assertEquals(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE,
611                mCTUT.maybeRemapReasonCode(new ImsReasonInfo(510, 1, "Call answered elsewhere.")));
612        assertEquals(90210, mCTUT.maybeRemapReasonCode(new ImsReasonInfo(90210, 1,
613                "Call answered elsewhere.")));
614    }
615
616
617    @Test
618    @SmallTest
619    public void testDialImsServiceUnavailable() throws ImsException {
620        doThrow(new ImsException("Test Exception", ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN)).when(
621                mImsManager).createCallProfile(anyInt(), anyInt());
622        mCTUT.setRetryTimeout(() -> 0);
623        assertEquals(Call.State.IDLE, mCTUT.mForegroundCall.getState());
624        assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
625
626        try {
627            mCTUT.dial("+17005554141", ImsCallProfile.CALL_TYPE_VOICE, null);
628        } catch (Exception e) {
629            Assert.fail();
630        }
631
632        // wait for handler to process ImsService connection retry
633        waitForHandlerAction(mCTHander, 1000); // 1 second timeout
634        verify(mImsManager, never()).makeCall(nullable(ImsCallProfile.class),
635                eq(new String[]{"+17005554141"}), nullable(ImsCall.Listener.class));
636        // Make sure that open is called in ImsPhoneCallTracker when it was first connected and
637        // again after retry.
638        verify(mImsManager, times(2)).open(
639                nullable(MmTelFeature.Listener.class));
640    }
641
642    @FlakyTest
643    @Ignore
644    @Test
645    @SmallTest
646    public void testTTYImsServiceUnavailable() throws ImsException {
647        doThrow(new ImsException("Test Exception", ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN)).when(
648                mImsManager).setUiTTYMode(nullable(Context.class), anyInt(),
649                nullable(Message.class));
650        // Remove retry timeout delay
651        mCTUT.setRetryTimeout(() -> 0); //ms
652
653        mCTUT.setUiTTYMode(0, new Message());
654
655        // wait for handler to process ImsService connection retry
656        waitForHandlerAction(mCTHander, 100);
657        // Make sure that open is called in ImsPhoneCallTracker to re-establish connection to
658        // ImsService
659        verify(mImsManager, times(2)).open(
660                nullable(MmTelFeature.Listener.class));
661    }
662
663    /**
664     * Test notification of handover from LTE to WIFI and WIFI to LTE and ensure that the expected
665     * connection events are sent.
666     */
667    @Test
668    @SmallTest
669    public void testNotifyHandovers() {
670        setupCarrierConfig();
671
672        //establish a MT call
673        testImsMTCallAccept();
674        ImsPhoneConnection connection =
675                (ImsPhoneConnection) mCTUT.mForegroundCall.getConnections().get(0);
676        ImsCall call = connection.getImsCall();
677        // Needs to be a video call to see this signalling.
678        mImsCallProfile.mCallType = ImsCallProfile.CALL_TYPE_VT;
679
680        // First handover from LTE to WIFI; this takes us into a mid-call state.
681        call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
682                ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
683                new ImsReasonInfo());
684        // Handover back to LTE.
685        call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
686                ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, ServiceState.RIL_RADIO_TECHNOLOGY_LTE,
687                new ImsReasonInfo());
688        verify(mImsPhoneConnectionListener).onConnectionEvent(eq(
689                TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE), isNull());
690
691        // Finally hand back to WIFI
692        call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
693                ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
694                new ImsReasonInfo());
695        verify(mImsPhoneConnectionListener).onConnectionEvent(eq(
696                TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI), isNull());
697    }
698
699    /**
700     * Configure carrier config options relevant to the unit test.
701     */
702    public void setupCarrierConfig() {
703        PersistableBundle bundle = new PersistableBundle();
704        bundle.putBoolean(CarrierConfigManager.KEY_NOTIFY_HANDOVER_VIDEO_FROM_LTE_TO_WIFI_BOOL,
705                true);
706        bundle.putBoolean(CarrierConfigManager.KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL,
707                true);
708        bundle.putBoolean(CarrierConfigManager.KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL, true);
709        mCTUT.updateCarrierConfigCache(bundle);
710    }
711
712    @Test
713    @SmallTest
714    public void testLowBatteryDisconnectMidCall() {
715        assertEquals(DisconnectCause.LOW_BATTERY, mCTUT.getDisconnectCauseFromReasonInfo(
716                new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_LOW_BATTERY, 0), Call.State.ACTIVE));
717        assertEquals(DisconnectCause.LOW_BATTERY, mCTUT.getDisconnectCauseFromReasonInfo(
718                new ImsReasonInfo(ImsReasonInfo.CODE_LOW_BATTERY, 0), Call.State.ACTIVE));
719    }
720
721    @Test
722    @SmallTest
723    public void testImsAlternateEmergencyDisconnect() {
724        assertEquals(DisconnectCause.IMS_SIP_ALTERNATE_EMERGENCY_CALL,
725                mCTUT.getDisconnectCauseFromReasonInfo(
726                        new ImsReasonInfo(ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL, 0),
727                        Call.State.ACTIVE));
728    }
729
730    @Test
731    @SmallTest
732    public void testLowBatteryDisconnectDialing() {
733        assertEquals(DisconnectCause.DIAL_LOW_BATTERY, mCTUT.getDisconnectCauseFromReasonInfo(
734                new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_LOW_BATTERY, 0), Call.State.DIALING));
735        assertEquals(DisconnectCause.DIAL_LOW_BATTERY, mCTUT.getDisconnectCauseFromReasonInfo(
736                new ImsReasonInfo(ImsReasonInfo.CODE_LOW_BATTERY, 0), Call.State.DIALING));
737    }
738
739    /**
740     * Tests that no hold tone is played if the call is remotely held and the media direction is
741     * send/receive (i.e. there is an audio stream present).
742     */
743    @Test
744    @SmallTest
745    public void testNoRemoteHoldtone() {
746        //establish a MT call
747        testImsMTCallAccept();
748        ImsPhoneConnection connection = mCTUT.mForegroundCall.getFirstConnection();
749        ImsCall call = connection.getImsCall();
750
751        // Set the media direction to send/receive.
752        ImsCallProfile callProfile = new ImsCallProfile();
753        callProfile.mMediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
754        call.setCallProfile(callProfile);
755
756        try {
757            mCTUT.onCallHoldReceived(call);
758        } catch (Exception ex) {
759            ex.printStackTrace();
760            Assert.fail("unexpected exception thrown" + ex.getMessage());
761        }
762        verify(mImsPhone, never()).startOnHoldTone(nullable(Connection.class));
763    }
764
765    /**
766     * Verifies that a remote hold tone is played when the call is remotely held and the media
767     * direction is inactive (i.e. the audio stream is not playing, so we should play the tone).
768     */
769    @Test
770    @SmallTest
771    public void testRemoteToneInactive() {
772        //establish a MT call
773        testImsMTCallAccept();
774        ImsPhoneConnection connection = mCTUT.mForegroundCall.getFirstConnection();
775        ImsCall call = connection.getImsCall();
776
777        // Set the media direction to inactive to trigger a hold tone.
778        ImsCallProfile callProfile = new ImsCallProfile();
779        callProfile.mMediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_INACTIVE;
780        call.setCallProfile(callProfile);
781
782        try {
783            mCTUT.onCallHoldReceived(call);
784        } catch (Exception ex) {
785            ex.printStackTrace();
786            Assert.fail("unexpected exception thrown" + ex.getMessage());
787        }
788        verify(mImsPhone, times(1)).startOnHoldTone(nullable(Connection.class));
789    }
790
791    @Test
792    @SmallTest
793    public void testRemoteHoldtone() {
794        // Set carrier config to always play remote hold tone.
795        mCTUT.setAlwaysPlayRemoteHoldTone(true);
796        //establish a MT call
797        testImsMTCallAccept();
798        ImsPhoneConnection connection = mCTUT.mForegroundCall.getFirstConnection();
799        ImsCall call = connection.getImsCall();
800
801        // Set the media direction to send/receive; normally we don't play a hold tone but the
802        // carrier config option is set to ensure we will do it in this case.
803        ImsCallProfile callProfile = new ImsCallProfile();
804        callProfile.mMediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
805        call.setCallProfile(callProfile);
806
807        try {
808            mCTUT.onCallHoldReceived(call);
809        } catch (Exception ex) {
810            ex.printStackTrace();
811            Assert.fail("unexpected exception thrown" + ex.getMessage());
812        }
813        verify(mImsPhone, times(1)).startOnHoldTone(nullable(Connection.class));
814    }
815}
816