1 /** 2 * Copyright (C) 2022 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 */ 16 17 package com.android.telephony.imsmedia; 18 19 import static junit.framework.Assert.assertEquals; 20 21 import static org.junit.Assert.fail; 22 import static org.mockito.ArgumentMatchers.any; 23 import static org.mockito.Mockito.doReturn; 24 import static org.mockito.Mockito.eq; 25 import static org.mockito.Mockito.spy; 26 import static org.mockito.Mockito.times; 27 import static org.mockito.Mockito.verify; 28 29 import android.hardware.radio.ims.media.IImsMedia; 30 import android.hardware.radio.ims.media.IImsMediaSession; 31 import android.hardware.radio.ims.media.RtpConfig; 32 import android.hardware.radio.ims.media.RtpError; 33 import android.os.Looper; 34 import android.os.ParcelFileDescriptor; 35 import android.os.RemoteException; 36 import android.telephony.CallQuality; 37 import android.telephony.ims.RtpHeaderExtension; 38 import android.telephony.imsmedia.AudioConfig; 39 import android.telephony.imsmedia.IImsAudioSessionCallback; 40 import android.telephony.imsmedia.ImsMediaSession; 41 import android.telephony.imsmedia.MediaQualityStatus; 42 import android.telephony.imsmedia.MediaQualityThreshold; 43 import android.testing.AndroidTestingRunner; 44 import android.testing.TestableLooper; 45 46 import com.android.telephony.imsmedia.AudioSession; 47 import com.android.telephony.imsmedia.Utils; 48 import com.android.telephony.imsmedia.Utils.OpenSessionParams; 49 50 import org.junit.After; 51 import org.junit.Before; 52 import org.junit.Test; 53 import org.junit.runner.RunWith; 54 import org.mockito.ArgumentCaptor; 55 import org.mockito.Mock; 56 import org.mockito.MockitoAnnotations; 57 58 import java.net.DatagramSocket; 59 import java.net.SocketException; 60 import java.util.ArrayList; 61 import java.util.List; 62 import java.util.stream.Collectors; 63 64 @RunWith(AndroidTestingRunner.class) 65 @TestableLooper.RunWithLooper 66 public class AudioOffloadTest extends ImsMediaTest { 67 private static final int SESSION_ID = 1; 68 private static final int DTMF_DURATION = 120; 69 private static final int NO_RESOURCES = ImsMediaSession.RESULT_NO_RESOURCES; 70 private static final int NO_MEMORY = ImsMediaSession.RESULT_NO_MEMORY; 71 private static final int SUCCESS = ImsMediaSession.RESULT_SUCCESS; 72 private static final int PACKET_LOSS = 15; 73 private static final int JITTER = 200; 74 private static final char DTMF_DIGIT = '7'; 75 private AudioSession audioSession; 76 private AudioOffloadListener offloadListener; 77 private AudioSession.AudioSessionHandler handler; 78 @Mock 79 private IImsAudioSessionCallback callback; 80 @Mock 81 private IImsMedia imsMedia; 82 @Mock 83 private IImsMediaSession imsMediaSession; 84 @Mock 85 private AudioOffloadService offloadService; 86 87 @Before setUp()88 public void setUp() { 89 MockitoAnnotations.initMocks(this); 90 offloadService = spy(AudioOffloadService.getInstance()); 91 doReturn(imsMedia).when(offloadService).getIImsMedia(); 92 audioSession = new AudioSession(SESSION_ID, callback, null, null, offloadService, 93 Looper.myLooper()); 94 handler = audioSession.getAudioSessionHandler(); 95 audioSession.setAudioOffload(true); 96 offloadListener = audioSession.getOffloadListener(); 97 audioSession.onOpenSessionSuccess(imsMediaSession); 98 mTestClass = AudioOffloadTest.this; 99 super.setUp(); 100 } 101 102 @After tearDown()103 public void tearDown() throws Exception { 104 super.tearDown(); 105 } 106 107 @Test testOpenSession()108 public void testOpenSession() { 109 DatagramSocket rtpSocket = null; 110 DatagramSocket rtcpSocket = null; 111 112 try { 113 rtpSocket = new DatagramSocket(); 114 rtcpSocket = new DatagramSocket(); 115 } catch (SocketException e) { 116 fail("SocketException:" + e); 117 } 118 119 OpenSessionParams params = new OpenSessionParams( 120 ParcelFileDescriptor.fromDatagramSocket(rtpSocket), 121 ParcelFileDescriptor.fromDatagramSocket(rtcpSocket), 122 null, null); 123 audioSession.openSession(params); 124 processAllMessages(); 125 126 verify(offloadService, times(1)).openSession(eq(SESSION_ID), eq(params)); 127 try { 128 verify(imsMedia, times(1)).openSession(eq(SESSION_ID), any(), eq(null)); 129 } catch (RemoteException e) { 130 fail("Failed to invoke openSession:" + e); 131 } 132 133 rtpSocket.close(); 134 rtcpSocket.close(); 135 } 136 137 @Test testCloseSession()138 public void testCloseSession() { 139 audioSession.closeSession(); 140 processAllMessages(); 141 verify(offloadService, times(1)).closeSession(eq(SESSION_ID)); 142 } 143 144 @Test testModifySession()145 public void testModifySession() { 146 final AudioConfig inputAudioConfig = AudioConfigTest.createAudioConfig(); 147 RtpConfig outputRtpConfig = null; 148 149 // Modify Session Request 150 audioSession.modifySession(inputAudioConfig); 151 processAllMessages(); 152 try { 153 ArgumentCaptor<RtpConfig> argumentCaptor = ArgumentCaptor.forClass(RtpConfig.class); 154 verify(imsMediaSession, times(1)).modifySession(argumentCaptor.capture()); 155 // Get the HAL RtpConfig 156 outputRtpConfig = argumentCaptor.getValue(); 157 // Covert it back to AudioConfig 158 final AudioConfig outputAudioConfig = Utils.convertToAudioConfig(outputRtpConfig); 159 // Ensure both are same 160 assertEquals(inputAudioConfig, outputAudioConfig); 161 } catch (RemoteException e) { 162 fail("Failed to invoke modifySession: " + e); 163 } 164 165 // Modify Session Response - SUCCESS 166 offloadListener.onModifySessionResponse(outputRtpConfig, RtpError.NONE); 167 processAllMessages(); 168 try { 169 verify(callback, times(1)).onModifySessionResponse(eq(inputAudioConfig), eq(SUCCESS)); 170 } catch (RemoteException e) { 171 fail("Failed to notify modifySessionResponse: " + e); 172 } 173 174 // Modify Session Response - FAILURE 175 offloadListener.onModifySessionResponse(outputRtpConfig, RtpError.NO_RESOURCES); 176 processAllMessages(); 177 try { 178 verify(callback, times(1)).onModifySessionResponse( 179 eq(inputAudioConfig), eq(NO_RESOURCES)); 180 } catch (RemoteException e) { 181 fail("Failed to notify modifySessionResponse: " + e); 182 } 183 } 184 185 @Test testAddConfig()186 public void testAddConfig() { 187 final AudioConfig inputAudioConfig = AudioConfigTest.createAudioConfig(); 188 RtpConfig outputRtpConfig = null; 189 190 // Add Config Request 191 audioSession.addConfig(inputAudioConfig); 192 processAllMessages(); 193 try { 194 ArgumentCaptor<RtpConfig> argumentCaptor = ArgumentCaptor.forClass(RtpConfig.class); 195 verify(imsMediaSession, times(1)).modifySession(argumentCaptor.capture()); 196 // Get the HAL RtpConfig 197 outputRtpConfig = argumentCaptor.getValue(); 198 // Covert it back to AudioConfig 199 final AudioConfig outputAudioConfig = Utils.convertToAudioConfig(outputRtpConfig); 200 // Ensure both are same 201 assertEquals(inputAudioConfig, outputAudioConfig); 202 } catch (RemoteException e) { 203 fail("Failed to invoke addConfig: " + e); 204 } 205 206 // Add Config Response - SUCCESS 207 offloadListener.onModifySessionResponse(outputRtpConfig, RtpError.NONE); 208 processAllMessages(); 209 try { 210 verify(callback, times(1)).onModifySessionResponse(eq(inputAudioConfig), eq(SUCCESS)); 211 } catch (RemoteException e) { 212 fail("Failed to notify addConfigResponse: " + e); 213 } 214 215 // Add Config Response - FAILURE 216 offloadListener.onModifySessionResponse(outputRtpConfig, RtpError.NO_MEMORY); 217 processAllMessages(); 218 try { 219 verify(callback, times(1)).onModifySessionResponse(eq(inputAudioConfig), eq(NO_MEMORY)); 220 } catch (RemoteException e) { 221 fail("Failed to notify addConfigResponse: " + e); 222 } 223 } 224 225 @Test testsendDtmf()226 public void testsendDtmf() { 227 audioSession.sendDtmf(DTMF_DIGIT, DTMF_DURATION); 228 processAllMessages(); 229 try { 230 verify(imsMediaSession, times(1)).sendDtmf(eq(DTMF_DIGIT), eq(DTMF_DURATION)); 231 } catch (RemoteException e) { 232 fail("Failed to invoke sendDtmf: " + e); 233 } 234 } 235 236 @Test testStartDtmf()237 public void testStartDtmf() { 238 audioSession.startDtmf(DTMF_DIGIT); 239 processAllMessages(); 240 try { 241 verify(imsMediaSession, times(1)).startDtmf(eq(DTMF_DIGIT)); 242 } catch (RemoteException e) { 243 fail("Failed to invoke startDtmf: " + e); 244 } 245 } 246 247 @Test testStopDtmf()248 public void testStopDtmf() { 249 audioSession.stopDtmf(); 250 processAllMessages(); 251 try { 252 verify(imsMediaSession, times(1)).stopDtmf(); 253 } catch (RemoteException e) { 254 fail("Failed to invoke stopDtmf: " + e); 255 } 256 257 } 258 259 @Test testSetMediaQualityThreshold()260 public void testSetMediaQualityThreshold() { 261 // Set Media Quality Threshold 262 MediaQualityThreshold threshold = 263 MediaQualityThresholdTest.createMediaQualityThresholdForHal(); 264 audioSession.setMediaQualityThreshold(threshold); 265 processAllMessages(); 266 try { 267 ArgumentCaptor<android.hardware.radio.ims.media.MediaQualityThreshold> argumentCaptor = 268 ArgumentCaptor.forClass( 269 android.hardware.radio.ims.media.MediaQualityThreshold.class); 270 verify(imsMediaSession, times(1)).setMediaQualityThreshold(argumentCaptor.capture()); 271 // Get the HAL MediaQualityThreshold 272 final android.hardware.radio.ims.media.MediaQualityThreshold 273 halThreshold = argumentCaptor.getValue(); 274 // Covert it back to {@link MediaQualityThreshold} 275 final MediaQualityThreshold expectedThreshold = 276 Utils.convertMediaQualityThreshold(halThreshold); 277 // Ensure both are same 278 assertEquals(threshold, expectedThreshold); 279 } catch (RemoteException e) { 280 fail("Failed to invoke setMediaQualityThreshold: " + e); 281 } 282 283 } 284 285 @Test testMediaQualityStatusInd()286 public void testMediaQualityStatusInd() { 287 // Receive MediaQualityStatus 288 final MediaQualityStatus outputStatus = 289 MediaQualityStatusTest.createMediaQualityStatus(); 290 final android.hardware.radio.ims.media.MediaQualityStatus inputStatus = 291 Utils.convertToHalMediaQualityStatus(outputStatus); 292 offloadListener.notifyMediaQualityStatus(inputStatus); 293 processAllMessages(); 294 try { 295 verify(callback, times(1)).notifyMediaQualityStatus(eq(outputStatus)); 296 } catch (RemoteException e) { 297 fail("Failed to notify media quality status: " + e); 298 } 299 } 300 301 @Test testFirstMediaPacketReceivedInd()302 public void testFirstMediaPacketReceivedInd() { 303 final AudioConfig outputAudioConfig = AudioConfigTest.createAudioConfig(); 304 final RtpConfig inputRtpConfig = Utils.convertToRtpConfig(outputAudioConfig); 305 306 // Receive First MediaPacket Received Indication 307 offloadListener.onFirstMediaPacketReceived(inputRtpConfig); 308 processAllMessages(); 309 try { 310 verify(callback, times(1)).onFirstMediaPacketReceived(eq(outputAudioConfig)); 311 } catch (RemoteException e) { 312 fail("Failed to notify onFirstMediaPacketReceived: " + e); 313 } 314 } 315 316 @Test testHeaderExtension()317 public void testHeaderExtension() { 318 final byte[] arr1 = {1, 2, 3, 4}; 319 final byte[] arr2 = {4, 2, 3, 4, 6}; 320 final ArrayList inputExtensions = new ArrayList<RtpHeaderExtension>(); 321 inputExtensions.add(new RtpHeaderExtension(7, arr1)); 322 inputExtensions.add(new RtpHeaderExtension(8, arr2)); 323 324 List<android.hardware.radio.ims.media.RtpHeaderExtension> halExtensions = null; 325 326 // Send RtpHeaderExtension 327 audioSession.sendHeaderExtension(inputExtensions); 328 processAllMessages(); 329 try { 330 ArgumentCaptor<List<android.hardware.radio.ims.media.RtpHeaderExtension>> 331 argumentCaptor = ArgumentCaptor.forClass(List.class); 332 verify(imsMediaSession, times(1)).sendHeaderExtension(argumentCaptor.capture()); 333 // Get the HAL RtpHeaderExtension list 334 halExtensions = argumentCaptor.getValue(); 335 // Covert it back to {@link RtpHeaderExtension} list 336 final List<RtpHeaderExtension> outputExtensions = 337 halExtensions.stream().map(Utils::convertRtpHeaderExtension) 338 .collect(Collectors.toList()); 339 // Ensure both are same 340 assertEquals(inputExtensions, outputExtensions); 341 } catch (RemoteException e) { 342 fail("Failed to invoke sendHeaderExtension: " + e); 343 } 344 345 // Receive HAL RtpHeaderExtension 346 offloadListener.onHeaderExtensionReceived(halExtensions); 347 processAllMessages(); 348 try { 349 verify(callback, times(1)).onHeaderExtensionReceived(eq(inputExtensions)); 350 } catch (RemoteException e) { 351 fail("Failed to notify onHeaderExtensionReceived: " + e); 352 } 353 } 354 355 @Test testTriggerAnbrQuery()356 public void testTriggerAnbrQuery() { 357 final AudioConfig outputAudioConfig = AudioConfigTest.createAudioConfig(); 358 final RtpConfig inputRtpConfig = Utils.convertToRtpConfig(outputAudioConfig); 359 360 // Receive triggerAnbrQuery for ANBR 361 offloadListener.triggerAnbrQuery(inputRtpConfig); 362 processAllMessages(); 363 try { 364 verify(callback, times(1)).triggerAnbrQuery(eq(outputAudioConfig)); 365 } catch (RemoteException e) { 366 fail("Failed to notify triggerAnbrQuery: " + e); 367 } 368 } 369 370 @Test testDtmfReceived()371 public void testDtmfReceived() { 372 // Receive DTMF Received 373 offloadListener.onDtmfReceived(DTMF_DIGIT, DTMF_DURATION); 374 processAllMessages(); 375 try { 376 verify(callback, times(1)).onDtmfReceived(eq(DTMF_DIGIT), eq(DTMF_DURATION)); 377 } catch (RemoteException e) { 378 fail("Failed to notify onDtmfReceived: " + e); 379 } 380 } 381 382 @Test testCallQualityChangedInd()383 public void testCallQualityChangedInd() { 384 final android.hardware.radio.ims.media.CallQuality inputCallQuality = 385 CallQualityTest.createHalCallQuality(); 386 final CallQuality outputCallQuality = Utils.convertCallQuality(inputCallQuality); 387 388 // Receive Call Quality Changed Indication 389 offloadListener.onCallQualityChanged(inputCallQuality); 390 processAllMessages(); 391 try { 392 verify(callback, times(1)).onCallQualityChanged(eq(outputCallQuality)); 393 } catch (RemoteException e) { 394 fail("Failed to notify onCallQualityChanged: " + e); 395 } 396 } 397 } 398