blob: 9c4775a2be7a8622dc65e6c0fc0bda7d4723dad9 [file] [log] [blame]
tkchin0ce3bf92016-03-12 16:52:04 -08001/*
2 * Copyright 2016 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#import <Foundation/Foundation.h>
jttehf84c1d62017-04-21 13:56:39 -070012#import <OCMock/OCMock.h>
tkchin0ce3bf92016-03-12 16:52:04 -080013
Joe Chen0b3a6e32019-12-26 23:01:42 -080014#include <vector>
15
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020016#include "rtc_base/gunit.h"
tkchin0ce3bf92016-03-12 16:52:04 -080017
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020018#import "components/audio/RTCAudioSession+Private.h"
denicija59ee91b2017-06-05 05:48:47 -070019
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020020#import "components/audio/RTCAudioSession.h"
21#import "components/audio/RTCAudioSessionConfiguration.h"
tkchine54467f2016-03-15 16:54:03 -070022
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020023@interface RTC_OBJC_TYPE (RTCAudioSession)
24(UnitTesting)
Peter Hanspers47217362017-10-05 11:39:15 +020025
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020026 @property(nonatomic,
27 readonly) std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> > delegates;
Joe Chen0b3a6e32019-12-26 23:01:42 -080028
Peter Hanspers47217362017-10-05 11:39:15 +020029- (instancetype)initWithAudioSession:(id)audioSession;
30
31@end
32
33@interface MockAVAudioSession : NSObject
34
35@property (nonatomic, readwrite, assign) float outputVolume;
36
37@end
38
39@implementation MockAVAudioSession
40@synthesize outputVolume = _outputVolume;
41@end
42
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020043@interface RTCAudioSessionTestDelegate : NSObject <RTC_OBJC_TYPE (RTCAudioSessionDelegate)>
jtteh13ae11a2017-05-25 17:52:20 -070044
45@property (nonatomic, readonly) float outputVolume;
46
tkchine54467f2016-03-15 16:54:03 -070047@end
48
49@implementation RTCAudioSessionTestDelegate
50
jtteh13ae11a2017-05-25 17:52:20 -070051@synthesize outputVolume = _outputVolume;
52
53- (instancetype)init {
54 if (self = [super init]) {
55 _outputVolume = -1;
56 }
57 return self;
58}
59
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020060- (void)audioSessionDidBeginInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070061}
62
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020063- (void)audioSessionDidEndInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session
tkchine54467f2016-03-15 16:54:03 -070064 shouldResumeSession:(BOOL)shouldResumeSession {
65}
66
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020067- (void)audioSessionDidChangeRoute:(RTC_OBJC_TYPE(RTCAudioSession) *)session
68 reason:(AVAudioSessionRouteChangeReason)reason
69 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -070070}
71
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020072- (void)audioSessionMediaServerTerminated:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070073}
74
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020075- (void)audioSessionMediaServerReset:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070076}
77
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020078- (void)audioSessionShouldConfigure:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070079}
80
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020081- (void)audioSessionShouldUnconfigure:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
tkchine54467f2016-03-15 16:54:03 -070082}
83
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020084- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession
jtteh13ae11a2017-05-25 17:52:20 -070085 didChangeOutputVolume:(float)outputVolume {
86 _outputVolume = outputVolume;
87}
88
tkchine54467f2016-03-15 16:54:03 -070089@end
90
tkchinefdd9302016-04-11 12:00:59 -070091// A delegate that adds itself to the audio session on init and removes itself
92// in its dealloc.
93@interface RTCTestRemoveOnDeallocDelegate : RTCAudioSessionTestDelegate
94@end
95
96@implementation RTCTestRemoveOnDeallocDelegate
97
98- (instancetype)init {
99 if (self = [super init]) {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200100 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchinefdd9302016-04-11 12:00:59 -0700101 [session addDelegate:self];
102 }
103 return self;
104}
105
106- (void)dealloc {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200107 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchinefdd9302016-04-11 12:00:59 -0700108 [session removeDelegate:self];
109}
110
111@end
112
tkchin0ce3bf92016-03-12 16:52:04 -0800113
114@interface RTCAudioSessionTest : NSObject
115
116- (void)testLockForConfiguration;
117
118@end
119
120@implementation RTCAudioSessionTest
121
122- (void)testLockForConfiguration {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200123 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchin0ce3bf92016-03-12 16:52:04 -0800124
125 for (size_t i = 0; i < 2; i++) {
126 [session lockForConfiguration];
127 EXPECT_TRUE(session.isLocked);
128 }
129 for (size_t i = 0; i < 2; i++) {
130 EXPECT_TRUE(session.isLocked);
131 [session unlockForConfiguration];
132 }
133 EXPECT_FALSE(session.isLocked);
134}
135
tkchine54467f2016-03-15 16:54:03 -0700136- (void)testAddAndRemoveDelegates {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200137 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchine54467f2016-03-15 16:54:03 -0700138 NSMutableArray *delegates = [NSMutableArray array];
139 const size_t count = 5;
140 for (size_t i = 0; i < count; ++i) {
141 RTCAudioSessionTestDelegate *delegate =
142 [[RTCAudioSessionTestDelegate alloc] init];
143 [session addDelegate:delegate];
144 [delegates addObject:delegate];
145 EXPECT_EQ(i + 1, session.delegates.size());
146 }
147 [delegates enumerateObjectsUsingBlock:^(RTCAudioSessionTestDelegate *obj,
148 NSUInteger idx,
149 BOOL *stop) {
150 [session removeDelegate:obj];
151 }];
152 EXPECT_EQ(0u, session.delegates.size());
153}
154
155- (void)testPushDelegate {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200156 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchine54467f2016-03-15 16:54:03 -0700157 NSMutableArray *delegates = [NSMutableArray array];
158 const size_t count = 2;
159 for (size_t i = 0; i < count; ++i) {
160 RTCAudioSessionTestDelegate *delegate =
161 [[RTCAudioSessionTestDelegate alloc] init];
162 [session addDelegate:delegate];
163 [delegates addObject:delegate];
164 }
165 // Test that it gets added to the front of the list.
166 RTCAudioSessionTestDelegate *pushedDelegate =
167 [[RTCAudioSessionTestDelegate alloc] init];
168 [session pushDelegate:pushedDelegate];
169 EXPECT_TRUE(pushedDelegate == session.delegates[0]);
170
171 // Test that it stays at the front of the list.
172 for (size_t i = 0; i < count; ++i) {
173 RTCAudioSessionTestDelegate *delegate =
174 [[RTCAudioSessionTestDelegate alloc] init];
175 [session addDelegate:delegate];
176 [delegates addObject:delegate];
177 }
178 EXPECT_TRUE(pushedDelegate == session.delegates[0]);
179
180 // Test that the next one goes to the front too.
181 pushedDelegate = [[RTCAudioSessionTestDelegate alloc] init];
182 [session pushDelegate:pushedDelegate];
183 EXPECT_TRUE(pushedDelegate == session.delegates[0]);
184}
185
186// Tests that delegates added to the audio session properly zero out. This is
187// checking an implementation detail (that vectors of __weak work as expected).
188- (void)testZeroingWeakDelegate {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200189 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchine54467f2016-03-15 16:54:03 -0700190 @autoreleasepool {
191 // Add a delegate to the session. There should be one delegate at this
192 // point.
193 RTCAudioSessionTestDelegate *delegate =
194 [[RTCAudioSessionTestDelegate alloc] init];
195 [session addDelegate:delegate];
196 EXPECT_EQ(1u, session.delegates.size());
197 EXPECT_TRUE(session.delegates[0]);
198 }
199 // The previously created delegate should've de-alloced, leaving a nil ptr.
200 EXPECT_FALSE(session.delegates[0]);
201 RTCAudioSessionTestDelegate *delegate =
202 [[RTCAudioSessionTestDelegate alloc] init];
203 [session addDelegate:delegate];
204 // On adding a new delegate, nil ptrs should've been cleared.
205 EXPECT_EQ(1u, session.delegates.size());
206 EXPECT_TRUE(session.delegates[0]);
207}
208
tkchinefdd9302016-04-11 12:00:59 -0700209// Tests that we don't crash when removing delegates in dealloc.
210// Added as a regression test.
211- (void)testRemoveDelegateOnDealloc {
212 @autoreleasepool {
213 RTCTestRemoveOnDeallocDelegate *delegate =
214 [[RTCTestRemoveOnDeallocDelegate alloc] init];
215 EXPECT_TRUE(delegate);
216 }
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200217 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
tkchinefdd9302016-04-11 12:00:59 -0700218 EXPECT_EQ(0u, session.delegates.size());
219}
220
jtteh3c9a6c02017-04-18 09:09:35 -0700221- (void)testAudioSessionActivation {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200222 RTC_OBJC_TYPE(RTCAudioSession) *audioSession = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
jtteh3c9a6c02017-04-18 09:09:35 -0700223 EXPECT_EQ(0, audioSession.activationCount);
224 [audioSession audioSessionDidActivate:[AVAudioSession sharedInstance]];
225 EXPECT_EQ(1, audioSession.activationCount);
226 [audioSession audioSessionDidDeactivate:[AVAudioSession sharedInstance]];
227 EXPECT_EQ(0, audioSession.activationCount);
228}
229
jttehf84c1d62017-04-21 13:56:39 -0700230// Hack - fixes OCMVerify link error
231// Link error is: Undefined symbols for architecture i386:
232// "OCMMakeLocation(objc_object*, char const*, int)", referenced from:
233// -[RTCAudioSessionTest testConfigureWebRTCSession] in RTCAudioSessionTest.o
234// ld: symbol(s) not found for architecture i386
235// REASON: https://github.com/erikdoe/ocmock/issues/238
236OCMLocation *OCMMakeLocation(id testCase, const char *fileCString, int line){
237 return [OCMLocation locationWithTestCase:testCase
238 file:[NSString stringWithUTF8String:fileCString]
239 line:line];
240}
241
242- (void)testConfigureWebRTCSession {
243 NSError *error = nil;
244
245 void (^setActiveBlock)(NSInvocation *invocation) = ^(NSInvocation *invocation) {
246 __autoreleasing NSError **retError;
247 [invocation getArgument:&retError atIndex:4];
248 *retError = [NSError errorWithDomain:@"AVAudioSession"
249 code:AVAudioSessionErrorInsufficientPriority
250 userInfo:nil];
251 BOOL failure = NO;
252 [invocation setReturnValue:&failure];
253 };
254
255 id mockAVAudioSession = OCMPartialMock([AVAudioSession sharedInstance]);
Mirko Bonadeiad8a00d2021-02-10 09:23:03 +0100256 OCMStub([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES
257 withOptions:0
258 error:([OCMArg anyObjectRef])])
259 .andDo(setActiveBlock);
jttehf84c1d62017-04-21 13:56:39 -0700260
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200261 id mockAudioSession = OCMPartialMock([RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]);
jttehf84c1d62017-04-21 13:56:39 -0700262 OCMStub([mockAudioSession session]).andReturn(mockAVAudioSession);
263
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200264 RTC_OBJC_TYPE(RTCAudioSession) *audioSession = mockAudioSession;
jttehf84c1d62017-04-21 13:56:39 -0700265 EXPECT_EQ(0, audioSession.activationCount);
266 [audioSession lockForConfiguration];
267 EXPECT_TRUE([audioSession checkLock:nil]);
268 // configureWebRTCSession is forced to fail in the above mock interface,
269 // so activationCount should remain 0
Mirko Bonadeiad8a00d2021-02-10 09:23:03 +0100270 OCMExpect([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES
271 withOptions:0
272 error:([OCMArg anyObjectRef])])
273 .andDo(setActiveBlock);
jttehf84c1d62017-04-21 13:56:39 -0700274 OCMExpect([mockAudioSession session]).andReturn(mockAVAudioSession);
275 EXPECT_FALSE([audioSession configureWebRTCSession:&error]);
276 EXPECT_EQ(0, audioSession.activationCount);
277
278 id session = audioSession.session;
279 EXPECT_EQ(session, mockAVAudioSession);
280 EXPECT_EQ(NO, [mockAVAudioSession setActive:YES withOptions:0 error:&error]);
281 [audioSession unlockForConfiguration];
282
283 OCMVerify([mockAudioSession session]);
284 OCMVerify([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES withOptions:0 error:&error]);
285 OCMVerify([[mockAVAudioSession ignoringNonObjectArgs] setActive:NO withOptions:0 error:&error]);
286
287 [mockAVAudioSession stopMocking];
288 [mockAudioSession stopMocking];
289}
290
jtteh13ae11a2017-05-25 17:52:20 -0700291- (void)testAudioVolumeDidNotify {
Peter Hanspers47217362017-10-05 11:39:15 +0200292 MockAVAudioSession *mockAVAudioSession = [[MockAVAudioSession alloc] init];
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200293 RTC_OBJC_TYPE(RTCAudioSession) *session =
294 [[RTC_OBJC_TYPE(RTCAudioSession) alloc] initWithAudioSession:mockAVAudioSession];
jtteh13ae11a2017-05-25 17:52:20 -0700295 RTCAudioSessionTestDelegate *delegate =
296 [[RTCAudioSessionTestDelegate alloc] init];
297 [session addDelegate:delegate];
298
Peter Hanspers47217362017-10-05 11:39:15 +0200299 float expectedVolume = 0.75;
300 mockAVAudioSession.outputVolume = expectedVolume;
jtteh13ae11a2017-05-25 17:52:20 -0700301
Peter Hanspers47217362017-10-05 11:39:15 +0200302 EXPECT_EQ(expectedVolume, delegate.outputVolume);
jtteh13ae11a2017-05-25 17:52:20 -0700303}
304
tkchin0ce3bf92016-03-12 16:52:04 -0800305@end
306
tkchine54467f2016-03-15 16:54:03 -0700307namespace webrtc {
308
309class AudioSessionTest : public ::testing::Test {
310 protected:
Mirko Bonadei17aff352018-07-26 12:20:40 +0200311 void TearDown() override {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200312 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
313 for (id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> delegate : session.delegates) {
tkchine54467f2016-03-15 16:54:03 -0700314 [session removeDelegate:delegate];
315 }
316 }
317};
318
319TEST_F(AudioSessionTest, LockForConfiguration) {
tkchin0ce3bf92016-03-12 16:52:04 -0800320 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
321 [test testLockForConfiguration];
322}
tkchine54467f2016-03-15 16:54:03 -0700323
324TEST_F(AudioSessionTest, AddAndRemoveDelegates) {
325 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
326 [test testAddAndRemoveDelegates];
327}
328
329TEST_F(AudioSessionTest, PushDelegate) {
330 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
331 [test testPushDelegate];
332}
333
334TEST_F(AudioSessionTest, ZeroingWeakDelegate) {
335 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
336 [test testZeroingWeakDelegate];
337}
338
tkchinefdd9302016-04-11 12:00:59 -0700339TEST_F(AudioSessionTest, RemoveDelegateOnDealloc) {
340 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
341 [test testRemoveDelegateOnDealloc];
342}
343
jtteh3c9a6c02017-04-18 09:09:35 -0700344TEST_F(AudioSessionTest, AudioSessionActivation) {
345 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
346 [test testAudioSessionActivation];
347}
348
jttehf84c1d62017-04-21 13:56:39 -0700349TEST_F(AudioSessionTest, ConfigureWebRTCSession) {
350 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
351 [test testConfigureWebRTCSession];
352}
jtteh3c9a6c02017-04-18 09:09:35 -0700353
jtteh13ae11a2017-05-25 17:52:20 -0700354TEST_F(AudioSessionTest, AudioVolumeDidNotify) {
355 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
356 [test testAudioVolumeDidNotify];
357}
358
tkchine54467f2016-03-15 16:54:03 -0700359} // namespace webrtc