Loading src/java/com/android/internal/telephony/Connection.java +38 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.internal.telephony; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.net.Uri; import android.os.Build; Loading @@ -25,6 +26,7 @@ import android.telephony.DisconnectCause; import android.telephony.ServiceState; import android.telephony.ServiceState.RilRadioTechnology; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.RtpHeaderExtension; import android.util.Log; import com.android.ims.internal.ConferenceParticipant; Loading Loading @@ -119,6 +121,18 @@ public abstract class Connection { public void onRttTerminated(); public void onOriginalConnectionReplaced(Connection newConnection); public void onIsNetworkEmergencyCallChanged(boolean isEmergencyCall); /** * Indicates a DTMF digit has been received from the network. * @param digit The DTMF digit. */ public void onReceivedDtmfDigit(char digit); /** * Indicates data from an RTP header extension has been received from the network. * @param extensionData The extension data. */ public void onReceivedRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> extensionData); } /** Loading Loading @@ -168,6 +182,10 @@ public abstract class Connection { public void onOriginalConnectionReplaced(Connection newConnection) {} @Override public void onIsNetworkEmergencyCallChanged(boolean isEmergencyCall) {} @Override public void onReceivedDtmfDigit(char digit) {} @Override public void onReceivedRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> extensionData) {} } public static final int AUDIO_QUALITY_STANDARD = 1; Loading Loading @@ -1427,4 +1445,24 @@ public abstract class Connection { @android.telecom.Connection.VerificationStatus int verificationStatus) { mNumberVerificationStatus = verificationStatus; } /** * Called to report a DTMF digit received from the network. * @param digit the received digit. */ public void receivedDtmfDigit(char digit) { for (Listener l : mListeners) { l.onReceivedDtmfDigit(digit); } } /** * Called to report RTP header extensions received from the network. * @param extensionData the received extension data. */ public void receivedRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> extensionData) { for (Listener l : mListeners) { l.onReceivedRtpHeaderExtensions(extensionData); } } } src/java/com/android/internal/telephony/TelephonyTester.java +43 −21 Original line number Diff line number Diff line Loading @@ -124,6 +124,14 @@ public class TelephonyTester { private static final String ACTION_TEST_IMS_E_CALL = "com.android.internal.telephony.TestImsECall"; /** * Test-only intent used to trigger signalling that an IMS call received a DTMF tone. */ private static final String ACTION_TEST_RECEIVE_DTMF = "com.android.internal.telephony.TestReceiveDtmf"; private static final String EXTRA_DIGIT = "digit"; /** * Test-only intent used to trigger a change to the current call's phone number. * Use the {@link #EXTRA_NUMBER} extra to specify the new phone number. Loading Loading @@ -197,6 +205,9 @@ public class TelephonyTester { } else if (action.equals(ACTION_TEST_IMS_E_CALL)) { log("handle test IMS ecall intent"); testImsECall(); } else if (action.equals(ACTION_TEST_RECEIVE_DTMF)) { log("handle test DTMF intent"); testImsReceiveDtmf(intent); } else if (action.equals(ACTION_TEST_CHANGE_NUMBER)) { log("handle test change number intent"); testChangeNumber(intent); Loading Loading @@ -229,6 +240,7 @@ public class TelephonyTester { filter.addAction(ACTION_TEST_HANDOVER_FAIL); filter.addAction(ACTION_TEST_SUPP_SRVC_NOTIFICATION); filter.addAction(ACTION_TEST_IMS_E_CALL); filter.addAction(ACTION_TEST_RECEIVE_DTMF); mImsExternalCallStates = new ArrayList<ImsExternalCallState>(); } Loading Loading @@ -262,17 +274,7 @@ public class TelephonyTester { private void handleHandoverFailedIntent() { // Attempt to get the active IMS call ImsPhone imsPhone = (ImsPhone) mPhone; if (imsPhone == null) { return; } ImsPhoneCall imsPhoneCall = imsPhone.getForegroundCall(); if (imsPhoneCall == null) { return; } ImsCall imsCall = imsPhoneCall.getImsCall(); ImsCall imsCall = getImsCall(); if (imsCall == null) { return; } Loading Loading @@ -491,30 +493,50 @@ public class TelephonyTester { void testImsECall() { // Attempt to get the active IMS call before parsing the test XML file. ImsCall imsCall = getImsCall(); if (imsCall == null) return; ImsCallProfile callProfile = imsCall.getCallProfile(); Bundle extras = callProfile.getCallExtras(); if (extras == null) { extras = new Bundle(); } extras.putBoolean(ImsCallProfile.EXTRA_EMERGENCY_CALL, true); callProfile.mCallExtras = extras; imsCall.getImsCallSessionListenerProxy().callSessionUpdated(imsCall.getSession(), callProfile); } private ImsCall getImsCall() { ImsPhone imsPhone = (ImsPhone) mPhone; if (imsPhone == null) { return; return null; } ImsPhoneCall imsPhoneCall = imsPhone.getForegroundCall(); if (imsPhoneCall == null) { return; return null; } ImsCall imsCall = imsPhoneCall.getImsCall(); if (imsCall == null) { return null; } return imsCall; } void testImsReceiveDtmf(Intent intent) { if (!intent.hasExtra(EXTRA_DIGIT)) { return; } char digit = intent.getStringExtra(EXTRA_DIGIT).charAt(0); ImsCallProfile callProfile = imsCall.getCallProfile(); Bundle extras = callProfile.getCallExtras(); if (extras == null) { extras = new Bundle(); ImsCall imsCall = getImsCall(); if (imsCall == null) { return; } extras.putBoolean(ImsCallProfile.EXTRA_EMERGENCY_CALL, true); callProfile.mCallExtras = extras; imsCall.getImsCallSessionListenerProxy().callSessionUpdated(imsCall.getSession(), callProfile); imsCall.getImsCallSessionListenerProxy().callSessionDtmfReceived(digit); } void testChangeNumber(Intent intent) { Loading src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java +27 −0 Original line number Diff line number Diff line Loading @@ -67,6 +67,7 @@ import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ImsStreamMediaProfile; import android.telephony.ims.ImsSuppServiceNotification; import android.telephony.ims.ProvisioningManager; import android.telephony.ims.RtpHeaderExtension; import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.stub.ImsRegistrationImplBase; Loading Loading @@ -124,6 +125,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; Loading Loading @@ -3576,6 +3578,15 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { mPhone.notifySuppServiceFailed(Phone.SuppService.TRANSFER); } @Override public void onCallSessionDtmfReceived(ImsCall imsCall, char digit) { log("onCallSessionDtmfReceived digit=" + digit); ImsPhoneConnection conn = findConnection(imsCall); if (conn != null) { conn.receivedDtmfDigit(digit); } } /** * Handles a change to the multiparty state for an {@code ImsCall}. Notifies the associated * {@link ImsPhoneConnection} of the change. Loading Loading @@ -3610,6 +3621,22 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { cqm.saveCallQuality(callQuality); mCallQualityMetrics.put(callId, cqm); } /** * Handles reception of RTP header extension data from the network. * @param imsCall The ImsCall the data was received on. * @param rtpHeaderExtensionData The RTP extension data received. */ @Override public void onCallSessionRtpHeaderExtensionsReceived(ImsCall imsCall, @NonNull Set<RtpHeaderExtension> rtpHeaderExtensionData) { log("onCallSessionRtpHeaderExtensionsReceived numExtensions=" + rtpHeaderExtensionData.size()); ImsPhoneConnection conn = findConnection(imsCall); if (conn != null) { conn.receivedRtpHeaderExtensions(rtpHeaderExtensionData); } } }; /** Loading tests/telephonytests/src/android/telephony/ims/ImsCallSessionListenerTests.java +56 −0 Original line number Diff line number Diff line Loading @@ -16,21 +16,29 @@ package android.telephony.ims; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.telephony.ims.aidl.IImsCallSessionListener; import android.util.ArraySet; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.List; @RunWith(AndroidJUnit4.class) public class ImsCallSessionListenerTests { Loading Loading @@ -75,4 +83,52 @@ public class ImsCallSessionListenerTests { eq(TelephonyManager.NETWORK_TYPE_IWLAN), eq(imsReasonInfo)); } @Test public void testCallSessionDtmfReceived() throws Exception { ImsCallSessionListener mTestListener = new ImsCallSessionListener(mMockListener); mTestListener.callSessionDtmfReceived('A'); mTestListener.callSessionDtmfReceived('a'); verify(mMockListener, times(2)).callSessionDtmfReceived(eq('A')); mTestListener.callSessionDtmfReceived('B'); mTestListener.callSessionDtmfReceived('b'); verify(mMockListener, times(2)).callSessionDtmfReceived(eq('B')); mTestListener.callSessionDtmfReceived('0'); verify(mMockListener, times(1)).callSessionDtmfReceived(eq('0')); mTestListener.callSessionDtmfReceived('*'); verify(mMockListener, times(1)).callSessionDtmfReceived(eq('*')); mTestListener.callSessionDtmfReceived('#'); verify(mMockListener, times(1)).callSessionDtmfReceived(eq('#')); try { mTestListener.callSessionDtmfReceived('P'); fail("expected exception"); } catch (IllegalArgumentException illegalArgumentException) { // expected } } @Test public void testCallSessionRtpExtensionHeadersReceived() throws Exception { ImsCallSessionListener mTestListener = new ImsCallSessionListener(mMockListener); ArraySet<RtpHeaderExtension> headers = new ArraySet<RtpHeaderExtension>(); RtpHeaderExtension extension = new RtpHeaderExtension(1, new byte[1]); headers.add(extension); mTestListener.callSessionRtpHeaderExtensionsReceived(headers); final ArgumentCaptor<List<RtpHeaderExtension>> listCaptor = ArgumentCaptor.forClass((Class) List.class); verify(mMockListener).callSessionRtpHeaderExtensionsReceived( listCaptor.capture()); assertEquals(1, listCaptor.getValue().size()); assertEquals(extension.getLocalIdentifier(), listCaptor.getValue().get(0).getLocalIdentifier()); try { mTestListener.callSessionRtpHeaderExtensionsReceived(null); fail("expected exception"); } catch (NullPointerException npe) { // expected } } } Loading
src/java/com/android/internal/telephony/Connection.java +38 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.internal.telephony; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.net.Uri; import android.os.Build; Loading @@ -25,6 +26,7 @@ import android.telephony.DisconnectCause; import android.telephony.ServiceState; import android.telephony.ServiceState.RilRadioTechnology; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.RtpHeaderExtension; import android.util.Log; import com.android.ims.internal.ConferenceParticipant; Loading Loading @@ -119,6 +121,18 @@ public abstract class Connection { public void onRttTerminated(); public void onOriginalConnectionReplaced(Connection newConnection); public void onIsNetworkEmergencyCallChanged(boolean isEmergencyCall); /** * Indicates a DTMF digit has been received from the network. * @param digit The DTMF digit. */ public void onReceivedDtmfDigit(char digit); /** * Indicates data from an RTP header extension has been received from the network. * @param extensionData The extension data. */ public void onReceivedRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> extensionData); } /** Loading Loading @@ -168,6 +182,10 @@ public abstract class Connection { public void onOriginalConnectionReplaced(Connection newConnection) {} @Override public void onIsNetworkEmergencyCallChanged(boolean isEmergencyCall) {} @Override public void onReceivedDtmfDigit(char digit) {} @Override public void onReceivedRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> extensionData) {} } public static final int AUDIO_QUALITY_STANDARD = 1; Loading Loading @@ -1427,4 +1445,24 @@ public abstract class Connection { @android.telecom.Connection.VerificationStatus int verificationStatus) { mNumberVerificationStatus = verificationStatus; } /** * Called to report a DTMF digit received from the network. * @param digit the received digit. */ public void receivedDtmfDigit(char digit) { for (Listener l : mListeners) { l.onReceivedDtmfDigit(digit); } } /** * Called to report RTP header extensions received from the network. * @param extensionData the received extension data. */ public void receivedRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> extensionData) { for (Listener l : mListeners) { l.onReceivedRtpHeaderExtensions(extensionData); } } }
src/java/com/android/internal/telephony/TelephonyTester.java +43 −21 Original line number Diff line number Diff line Loading @@ -124,6 +124,14 @@ public class TelephonyTester { private static final String ACTION_TEST_IMS_E_CALL = "com.android.internal.telephony.TestImsECall"; /** * Test-only intent used to trigger signalling that an IMS call received a DTMF tone. */ private static final String ACTION_TEST_RECEIVE_DTMF = "com.android.internal.telephony.TestReceiveDtmf"; private static final String EXTRA_DIGIT = "digit"; /** * Test-only intent used to trigger a change to the current call's phone number. * Use the {@link #EXTRA_NUMBER} extra to specify the new phone number. Loading Loading @@ -197,6 +205,9 @@ public class TelephonyTester { } else if (action.equals(ACTION_TEST_IMS_E_CALL)) { log("handle test IMS ecall intent"); testImsECall(); } else if (action.equals(ACTION_TEST_RECEIVE_DTMF)) { log("handle test DTMF intent"); testImsReceiveDtmf(intent); } else if (action.equals(ACTION_TEST_CHANGE_NUMBER)) { log("handle test change number intent"); testChangeNumber(intent); Loading Loading @@ -229,6 +240,7 @@ public class TelephonyTester { filter.addAction(ACTION_TEST_HANDOVER_FAIL); filter.addAction(ACTION_TEST_SUPP_SRVC_NOTIFICATION); filter.addAction(ACTION_TEST_IMS_E_CALL); filter.addAction(ACTION_TEST_RECEIVE_DTMF); mImsExternalCallStates = new ArrayList<ImsExternalCallState>(); } Loading Loading @@ -262,17 +274,7 @@ public class TelephonyTester { private void handleHandoverFailedIntent() { // Attempt to get the active IMS call ImsPhone imsPhone = (ImsPhone) mPhone; if (imsPhone == null) { return; } ImsPhoneCall imsPhoneCall = imsPhone.getForegroundCall(); if (imsPhoneCall == null) { return; } ImsCall imsCall = imsPhoneCall.getImsCall(); ImsCall imsCall = getImsCall(); if (imsCall == null) { return; } Loading Loading @@ -491,30 +493,50 @@ public class TelephonyTester { void testImsECall() { // Attempt to get the active IMS call before parsing the test XML file. ImsCall imsCall = getImsCall(); if (imsCall == null) return; ImsCallProfile callProfile = imsCall.getCallProfile(); Bundle extras = callProfile.getCallExtras(); if (extras == null) { extras = new Bundle(); } extras.putBoolean(ImsCallProfile.EXTRA_EMERGENCY_CALL, true); callProfile.mCallExtras = extras; imsCall.getImsCallSessionListenerProxy().callSessionUpdated(imsCall.getSession(), callProfile); } private ImsCall getImsCall() { ImsPhone imsPhone = (ImsPhone) mPhone; if (imsPhone == null) { return; return null; } ImsPhoneCall imsPhoneCall = imsPhone.getForegroundCall(); if (imsPhoneCall == null) { return; return null; } ImsCall imsCall = imsPhoneCall.getImsCall(); if (imsCall == null) { return null; } return imsCall; } void testImsReceiveDtmf(Intent intent) { if (!intent.hasExtra(EXTRA_DIGIT)) { return; } char digit = intent.getStringExtra(EXTRA_DIGIT).charAt(0); ImsCallProfile callProfile = imsCall.getCallProfile(); Bundle extras = callProfile.getCallExtras(); if (extras == null) { extras = new Bundle(); ImsCall imsCall = getImsCall(); if (imsCall == null) { return; } extras.putBoolean(ImsCallProfile.EXTRA_EMERGENCY_CALL, true); callProfile.mCallExtras = extras; imsCall.getImsCallSessionListenerProxy().callSessionUpdated(imsCall.getSession(), callProfile); imsCall.getImsCallSessionListenerProxy().callSessionDtmfReceived(digit); } void testChangeNumber(Intent intent) { Loading
src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java +27 −0 Original line number Diff line number Diff line Loading @@ -67,6 +67,7 @@ import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ImsStreamMediaProfile; import android.telephony.ims.ImsSuppServiceNotification; import android.telephony.ims.ProvisioningManager; import android.telephony.ims.RtpHeaderExtension; import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.stub.ImsRegistrationImplBase; Loading Loading @@ -124,6 +125,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; Loading Loading @@ -3576,6 +3578,15 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { mPhone.notifySuppServiceFailed(Phone.SuppService.TRANSFER); } @Override public void onCallSessionDtmfReceived(ImsCall imsCall, char digit) { log("onCallSessionDtmfReceived digit=" + digit); ImsPhoneConnection conn = findConnection(imsCall); if (conn != null) { conn.receivedDtmfDigit(digit); } } /** * Handles a change to the multiparty state for an {@code ImsCall}. Notifies the associated * {@link ImsPhoneConnection} of the change. Loading Loading @@ -3610,6 +3621,22 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { cqm.saveCallQuality(callQuality); mCallQualityMetrics.put(callId, cqm); } /** * Handles reception of RTP header extension data from the network. * @param imsCall The ImsCall the data was received on. * @param rtpHeaderExtensionData The RTP extension data received. */ @Override public void onCallSessionRtpHeaderExtensionsReceived(ImsCall imsCall, @NonNull Set<RtpHeaderExtension> rtpHeaderExtensionData) { log("onCallSessionRtpHeaderExtensionsReceived numExtensions=" + rtpHeaderExtensionData.size()); ImsPhoneConnection conn = findConnection(imsCall); if (conn != null) { conn.receivedRtpHeaderExtensions(rtpHeaderExtensionData); } } }; /** Loading
tests/telephonytests/src/android/telephony/ims/ImsCallSessionListenerTests.java +56 −0 Original line number Diff line number Diff line Loading @@ -16,21 +16,29 @@ package android.telephony.ims; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.telephony.ims.aidl.IImsCallSessionListener; import android.util.ArraySet; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.List; @RunWith(AndroidJUnit4.class) public class ImsCallSessionListenerTests { Loading Loading @@ -75,4 +83,52 @@ public class ImsCallSessionListenerTests { eq(TelephonyManager.NETWORK_TYPE_IWLAN), eq(imsReasonInfo)); } @Test public void testCallSessionDtmfReceived() throws Exception { ImsCallSessionListener mTestListener = new ImsCallSessionListener(mMockListener); mTestListener.callSessionDtmfReceived('A'); mTestListener.callSessionDtmfReceived('a'); verify(mMockListener, times(2)).callSessionDtmfReceived(eq('A')); mTestListener.callSessionDtmfReceived('B'); mTestListener.callSessionDtmfReceived('b'); verify(mMockListener, times(2)).callSessionDtmfReceived(eq('B')); mTestListener.callSessionDtmfReceived('0'); verify(mMockListener, times(1)).callSessionDtmfReceived(eq('0')); mTestListener.callSessionDtmfReceived('*'); verify(mMockListener, times(1)).callSessionDtmfReceived(eq('*')); mTestListener.callSessionDtmfReceived('#'); verify(mMockListener, times(1)).callSessionDtmfReceived(eq('#')); try { mTestListener.callSessionDtmfReceived('P'); fail("expected exception"); } catch (IllegalArgumentException illegalArgumentException) { // expected } } @Test public void testCallSessionRtpExtensionHeadersReceived() throws Exception { ImsCallSessionListener mTestListener = new ImsCallSessionListener(mMockListener); ArraySet<RtpHeaderExtension> headers = new ArraySet<RtpHeaderExtension>(); RtpHeaderExtension extension = new RtpHeaderExtension(1, new byte[1]); headers.add(extension); mTestListener.callSessionRtpHeaderExtensionsReceived(headers); final ArgumentCaptor<List<RtpHeaderExtension>> listCaptor = ArgumentCaptor.forClass((Class) List.class); verify(mMockListener).callSessionRtpHeaderExtensionsReceived( listCaptor.capture()); assertEquals(1, listCaptor.getValue().size()); assertEquals(extension.getLocalIdentifier(), listCaptor.getValue().get(0).getLocalIdentifier()); try { mTestListener.callSessionRtpHeaderExtensionsReceived(null); fail("expected exception"); } catch (NullPointerException npe) { // expected } } }