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 com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.fail;
22 import static org.mockito.Mockito.eq;
23 import static org.mockito.Mockito.times;
24 import static org.mockito.Mockito.verify;
25 
26 import android.os.Looper;
27 import android.os.Parcel;
28 import android.os.ParcelFileDescriptor;
29 import android.os.RemoteException;
30 import android.telephony.CallQuality;
31 import android.telephony.ims.RtpHeaderExtension;
32 import android.telephony.imsmedia.AudioConfig;
33 import android.telephony.imsmedia.IImsAudioSessionCallback;
34 import android.telephony.imsmedia.ImsMediaSession;
35 import android.telephony.imsmedia.MediaQualityStatus;
36 import android.telephony.imsmedia.MediaQualityThreshold;
37 import android.telephony.imsmedia.RtpReceptionStats;
38 import android.testing.AndroidTestingRunner;
39 import android.testing.TestableLooper;
40 
41 import com.android.telephony.imsmedia.AudioService;
42 import com.android.telephony.imsmedia.AudioSession;
43 import com.android.telephony.imsmedia.Utils;
44 import com.android.telephony.imsmedia.Utils.OpenSessionParams;
45 import com.android.telephony.imsmedia.tests.RtpReceptionStatsTest;
46 
47 import org.junit.After;
48 import org.junit.Before;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 import org.mockito.Mock;
52 import org.mockito.MockitoAnnotations;
53 
54 import java.net.DatagramSocket;
55 import java.net.SocketException;
56 import java.util.ArrayList;
57 
58 @RunWith(AndroidTestingRunner.class)
59 @TestableLooper.RunWithLooper
60 public class AudioSessionTest extends ImsMediaTest {
61     private static final int SESSION_ID = 1;
62     private static final int DTMF_DURATION = 140;
63     private static final int UNUSED = -1;
64     private static final int SUCCESS = ImsMediaSession.RESULT_SUCCESS;
65     private static final int NO_RESOURCES = ImsMediaSession.RESULT_NO_RESOURCES;
66     private static final int PACKET_LOSS = 15;
67     private static final int JITTER = 200;
68     private static final char DTMF_DIGIT = '7';
69     private static final int RECEPTION_DURATION = 10000;
70     private static final int DELAY_ADJUSTMENT = 100;
71     private AudioSession audioSession;
72     private AudioSession.AudioSessionHandler handler;
73     private WakeLockManager mWakeLockManager;
74     @Mock
75     private AudioService audioService;
76     private AudioListener audioListener;
77     @Mock
78     private AudioLocalSession audioLocalSession;
79     @Mock
80     private IImsAudioSessionCallback callback;
81 
82     @Before
setUp()83     public void setUp() {
84         MockitoAnnotations.initMocks(this);
85         audioSession = new AudioSession(SESSION_ID, callback,
86                 audioService, audioLocalSession, null, Looper.myLooper());
87         audioListener = audioSession.getAudioListener();
88         handler = audioSession.getAudioSessionHandler();
89         mTestClass = AudioSessionTest.this;
90         mWakeLockManager = WakeLockManager.getInstance();
91         super.setUp();
92     }
93 
94     @After
tearDown()95     public void tearDown() throws Exception {
96         super.tearDown();
97         mWakeLockManager.cleanup();
98     }
99 
createParcel(int message, int result, AudioConfig config)100     private Parcel createParcel(int message, int result, AudioConfig config) {
101         Parcel parcel = Parcel.obtain();
102         parcel.writeInt(message);
103         parcel.writeInt(result);
104         if (config != null) {
105             config.writeToParcel(parcel, 0);
106         }
107         parcel.setDataPosition(0);
108         return parcel;
109     }
110 
111     @Test
testOpenSession()112     public void testOpenSession() {
113         DatagramSocket rtpSocket = null;
114         DatagramSocket rtcpSocket = null;
115 
116         try {
117             rtpSocket = new DatagramSocket();
118             rtcpSocket = new DatagramSocket();
119         } catch (SocketException e) {
120             fail("SocketException:" + e);
121         }
122 
123         OpenSessionParams params = new OpenSessionParams(
124                 ParcelFileDescriptor.fromDatagramSocket(rtpSocket),
125                 ParcelFileDescriptor.fromDatagramSocket(rtcpSocket),
126                 null, null);
127 
128         audioSession.openSession(params);
129         processAllMessages();
130         verify(audioService, times(1)).openSession(eq(SESSION_ID), eq(params));
131         assertThat(mWakeLockManager.mWakeLock.isHeld()).isEqualTo(false);
132     }
133 
134     @Test
testCloseSession()135     public void testCloseSession() {
136         audioSession.closeSession();
137         processAllMessages();
138         verify(audioService, times(1)).closeSession(eq(SESSION_ID));
139         assertThat(mWakeLockManager.mWakeLock.isHeld()).isEqualTo(false);
140     }
141 
142     @Test
testModifySession()143     public void testModifySession() {
144         // Modify Session Request
145         AudioConfig config = AudioConfigTest.createAudioConfig();
146         audioSession.modifySession(config);
147         processAllMessages();
148         verify(audioLocalSession, times(1)).modifySession(eq(config));
149         assertThat(mWakeLockManager.mWakeLock.isHeld()).isEqualTo(true);
150 
151         // Modify Session Response - Success
152         audioListener.onMessage(
153             createParcel(AudioSession.EVENT_MODIFY_SESSION_RESPONSE, SUCCESS, config));
154         processAllMessages();
155         try {
156             verify(callback, times(1)).onModifySessionResponse(eq(config), eq(SUCCESS));
157         }  catch(RemoteException e) {
158             fail("Failed to notify modifySessionResponse: " + e);
159         }
160 
161         // Modify Session Response - Failure (NO_RESOURCES)
162         audioListener.onMessage(
163             createParcel(AudioSession.EVENT_MODIFY_SESSION_RESPONSE, NO_RESOURCES, config));
164         processAllMessages();
165         try {
166             verify(callback, times(1)).onModifySessionResponse(eq(config), eq(NO_RESOURCES));
167         }  catch(RemoteException e) {
168             fail("Failed to notify modifySessionResponse: " + e);
169         }
170     }
171 
172     @Test
testAddConfig()173     public void testAddConfig() {
174         // Add Config Request
175         AudioConfig config = AudioConfigTest.createAudioConfig();
176         audioSession.addConfig(config);
177         processAllMessages();
178         verify(audioLocalSession, times(1)).addConfig(eq(config));
179 
180         // Add Config Response - Success
181         audioListener.onMessage(
182             createParcel(AudioSession.EVENT_ADD_CONFIG_RESPONSE, SUCCESS, config));
183         processAllMessages();
184         try {
185             verify(callback, times(1)).onAddConfigResponse(eq(config), eq(SUCCESS));
186         }  catch(RemoteException e) {
187             fail("Failed to notify addConfigResponse: " + e);
188         }
189 
190         // Add Config Response - Failure (NO_RESOURCES)
191         audioListener.onMessage(
192             createParcel(AudioSession.EVENT_ADD_CONFIG_RESPONSE, NO_RESOURCES, config));
193         processAllMessages();
194         try {
195             verify(callback, times(1)).onAddConfigResponse(eq(config), eq(NO_RESOURCES));
196         }  catch(RemoteException e) {
197             fail("Failed to notify addConfigResponse: " + e);
198         }
199     }
200 
201     @Test
testDeleteConfig()202     public void testDeleteConfig() {
203         // Delete Config Request
204         AudioConfig config = AudioConfigTest.createAudioConfig();
205         audioSession.deleteConfig(config);
206         processAllMessages();
207         verify(audioLocalSession, times(1)).deleteConfig(eq(config));
208     }
209 
210     @Test
testConfirmConfig()211     public void testConfirmConfig() {
212         // Confirm Config Request
213         AudioConfig config = AudioConfigTest.createAudioConfig();
214         audioSession.confirmConfig(config);
215         processAllMessages();
216         verify(audioLocalSession, times(1)).confirmConfig(eq(config));
217 
218         // Confirm Config Response - Success
219         audioListener.onMessage(
220             createParcel(AudioSession.EVENT_CONFIRM_CONFIG_RESPONSE, SUCCESS, config));
221         processAllMessages();
222         try {
223             verify(callback, times(1)).onConfirmConfigResponse(eq(config), eq(SUCCESS));
224         }  catch(RemoteException e) {
225             fail("Failed to notify confirmConfigResponse: " + e);
226         }
227 
228         // Confirm Config Response - Failure (NO_RESOURCES)
229         audioListener.onMessage(
230             createParcel(AudioSession.EVENT_CONFIRM_CONFIG_RESPONSE, NO_RESOURCES, config));
231         processAllMessages();
232         try {
233             verify(callback, times(1)).onConfirmConfigResponse(eq(config), eq(NO_RESOURCES));
234         }  catch(RemoteException e) {
235             fail("Failed to notify confirmConfigResponse: " + e);
236         }
237     }
238 
239     @Test
testSendDtmf()240     public void testSendDtmf() {
241         audioSession.sendDtmf(DTMF_DIGIT, DTMF_DURATION);
242         processAllMessages();
243         verify(audioLocalSession, times(1)).sendDtmf(eq(DTMF_DIGIT), eq(DTMF_DURATION));
244     }
245 
246     @Test
testStartDtmf()247     public void testStartDtmf() {
248         audioSession.startDtmf(DTMF_DIGIT);
249         processAllMessages();
250         verify(audioLocalSession, times(1)).sendDtmf(eq(DTMF_DIGIT), eq(DTMF_DURATION));
251     }
252 
253     @Test
testSetMediaQualityThreshold()254     public void testSetMediaQualityThreshold() {
255         // Set Media Quality Threshold
256         MediaQualityThreshold threshold = MediaQualityThresholdTest.createMediaQualityThreshold();
257         audioSession.setMediaQualityThreshold(threshold);
258         processAllMessages();
259         verify(audioLocalSession, times(1)).setMediaQualityThreshold(eq(threshold));
260     }
261 
262     @Test
testRequestRtpReceptionStats()263     public void testRequestRtpReceptionStats() {
264         // Query rtp reception stats
265         audioSession.requestRtpReceptionStats(RECEPTION_DURATION);
266         processAllMessages();
267         verify(audioLocalSession, times(1)).requestRtpReceptionStats(eq(RECEPTION_DURATION));
268     }
269 
270     @Test
testAdjustDelay()271     public void testAdjustDelay() {
272         // Apply delay
273         audioSession.adjustDelay(DELAY_ADJUSTMENT);
274         processAllMessages();
275         verify(audioLocalSession, times(1)).adjustDelay(eq(DELAY_ADJUSTMENT));
276     }
277 
278     @Test
testFirstMediaPacketReceivedInd()279     public void testFirstMediaPacketReceivedInd() {
280         // Receive First MediaPacket Received Indication
281         AudioConfig config = AudioConfigTest.createAudioConfig();
282         Utils.sendMessage(handler, AudioSession.EVENT_FIRST_MEDIA_PACKET_IND, config);
283         processAllMessages();
284         try {
285             verify(callback, times(1)).onFirstMediaPacketReceived(eq(config));
286         }  catch(RemoteException e) {
287             fail("Failed to notify onFirstMediaPacketReceived: " + e);
288         }
289     }
290 
291     @Test
testHeaderExtension()292     public void testHeaderExtension() {
293         // Send RtpHeaderExtension
294         ArrayList extensions = new ArrayList<RtpHeaderExtension>();
295         audioSession.sendHeaderExtension(extensions);
296         processAllMessages();
297         verify(audioLocalSession, times(1)).sendHeaderExtension(eq(extensions));
298 
299         // Receive RtpHeaderExtension
300         Utils.sendMessage(handler, AudioSession.EVENT_RTP_HEADER_EXTENSION_IND, extensions);
301         processAllMessages();
302         try {
303             verify(callback, times(1)).onHeaderExtensionReceived(eq(extensions));
304         }  catch(RemoteException e) {
305             fail("Failed to notify onHeaderExtensionReceived: " + e);
306         }
307     }
308 
309     @Test
testNotifyMediaQualityStatus()310     public void testNotifyMediaQualityStatus() {
311         // Receive MediaQualityStatus
312         MediaQualityStatus status = MediaQualityStatusTest.createMediaQualityStatus();
313         Utils.sendMessage(handler, AudioSession.EVENT_MEDIA_QUALITY_STATUS_IND, status);
314         processAllMessages();
315         try {
316             verify(callback, times(1)).notifyMediaQualityStatus(eq(status));
317         } catch (RemoteException e) {
318             fail("Failed to notify notifyMediaInactivity: " + e);
319         }
320     }
321 
322     @Test
testTriggerAnbrQuery()323     public void testTriggerAnbrQuery() {
324         // Receive triggerAnbrQuery for ANBR
325         AudioConfig config = AudioConfigTest.createAudioConfig();
326         Utils.sendMessage(handler, AudioSession.EVENT_TRIGGER_ANBR_QUERY_IND, config);
327         processAllMessages();
328         try {
329             verify(callback, times(1)).triggerAnbrQuery(eq(config));
330         }  catch (RemoteException e) {
331             fail("Failed to notify triggerAnbrQuery: " + e);
332         }
333     }
334 
335     @Test
testDtmfReceived()336     public void testDtmfReceived() {
337         // Receive onDtmfReceived
338         Utils.sendMessage(handler, AudioSession.EVENT_DTMF_RECEIVED_IND, DTMF_DIGIT, DTMF_DURATION);
339         processAllMessages();
340         try {
341             verify(callback, times(1)).onDtmfReceived(eq(DTMF_DIGIT), eq(DTMF_DURATION));
342         }  catch (RemoteException e) {
343             fail("Failed to notify onDtmfReceived: " + e);
344         }
345     }
346 
347     @Test
testCallQualityChangedInd()348     public void testCallQualityChangedInd() {
349         // Receive Call Quality Changed Indication
350         CallQuality callQuality = CallQualityTest.createCallQuality();
351         Utils.sendMessage(handler, AudioSession.EVENT_CALL_QUALITY_CHANGE_IND, callQuality);
352         processAllMessages();
353         try {
354             verify(callback, times(1)).onCallQualityChanged(eq(callQuality));
355         } catch (RemoteException e) {
356             fail("Failed to notify onCallQualityChanged: " + e);
357         }
358     }
359 
360     @Test
testNotifyRtpReceptionStats()361     public void testNotifyRtpReceptionStats() {
362         // Receive Rtp reception statistics notification
363         RtpReceptionStats stats = RtpReceptionStatsTest.createRtpReceptionStats();
364         Utils.sendMessage(handler, AudioSession.EVENT_NOTIFY_RECEPTION_STATS, stats);
365         processAllMessages();
366         try {
367             verify(callback, times(1)).notifyRtpReceptionStats(eq(stats));
368         } catch (RemoteException e) {
369             fail("Failed to notify RtpReceptionStats: " + e);
370         }
371     }
372 
373     @Test
testOpenSessionSuccess()374     public void testOpenSessionSuccess() {
375         audioSession.onOpenSessionSuccess(audioLocalSession);
376         processAllMessages();
377         try {
378             verify(callback, times(1)).onOpenSessionSuccess(audioSession);
379             assertThat(mWakeLockManager.mWakeLock.isHeld()).isEqualTo(false);
380         } catch (RemoteException e) {
381             fail("Failed to notify onOpenSessionSuccess: " + e);
382         }
383     }
384 
385     @Test
testOpenSessionFailure()386     public void testOpenSessionFailure() {
387         audioSession.onOpenSessionFailure(ImsMediaSession.RESULT_INVALID_PARAM);
388         processAllMessages();
389         try {
390             verify(callback, times(1)).onOpenSessionFailure(ImsMediaSession.RESULT_INVALID_PARAM);
391             assertThat(mWakeLockManager.mWakeLock.isHeld()).isEqualTo(false);
392         } catch (RemoteException e) {
393             fail("Failed to notify onOpenSessionFailure: " + e);
394         }
395     }
396 
397     @Test
testSessionClosed()398     public void testSessionClosed() {
399         audioSession.onSessionClosed();
400         processAllMessages();
401         try {
402             verify(callback, times(1)).onSessionClosed();
403             assertThat(mWakeLockManager.mWakeLock.isHeld()).isEqualTo(false);
404         } catch (RemoteException e) {
405             fail("Failed to notify onSessionClosed: " + e);
406         }
407     }
408 }
409