blob: 4e309ca2fab9308fe41eaa993a71c3d281815c56 [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]);
256 OCMStub([[mockAVAudioSession ignoringNonObjectArgs]
257 setActive:YES withOptions:0 error:((NSError __autoreleasing **)[OCMArg anyPointer])]).
258 andDo(setActiveBlock);
259
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200260 id mockAudioSession = OCMPartialMock([RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]);
jttehf84c1d62017-04-21 13:56:39 -0700261 OCMStub([mockAudioSession session]).andReturn(mockAVAudioSession);
262
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200263 RTC_OBJC_TYPE(RTCAudioSession) *audioSession = mockAudioSession;
jttehf84c1d62017-04-21 13:56:39 -0700264 EXPECT_EQ(0, audioSession.activationCount);
265 [audioSession lockForConfiguration];
266 EXPECT_TRUE([audioSession checkLock:nil]);
267 // configureWebRTCSession is forced to fail in the above mock interface,
268 // so activationCount should remain 0
269 OCMExpect([[mockAVAudioSession ignoringNonObjectArgs]
270 setActive:YES withOptions:0 error:((NSError __autoreleasing **)[OCMArg anyPointer])]).
271 andDo(setActiveBlock);
272 OCMExpect([mockAudioSession session]).andReturn(mockAVAudioSession);
273 EXPECT_FALSE([audioSession configureWebRTCSession:&error]);
274 EXPECT_EQ(0, audioSession.activationCount);
275
276 id session = audioSession.session;
277 EXPECT_EQ(session, mockAVAudioSession);
278 EXPECT_EQ(NO, [mockAVAudioSession setActive:YES withOptions:0 error:&error]);
279 [audioSession unlockForConfiguration];
280
281 OCMVerify([mockAudioSession session]);
282 OCMVerify([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES withOptions:0 error:&error]);
283 OCMVerify([[mockAVAudioSession ignoringNonObjectArgs] setActive:NO withOptions:0 error:&error]);
284
285 [mockAVAudioSession stopMocking];
286 [mockAudioSession stopMocking];
287}
288
jtteh13ae11a2017-05-25 17:52:20 -0700289- (void)testAudioVolumeDidNotify {
Peter Hanspers47217362017-10-05 11:39:15 +0200290 MockAVAudioSession *mockAVAudioSession = [[MockAVAudioSession alloc] init];
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200291 RTC_OBJC_TYPE(RTCAudioSession) *session =
292 [[RTC_OBJC_TYPE(RTCAudioSession) alloc] initWithAudioSession:mockAVAudioSession];
jtteh13ae11a2017-05-25 17:52:20 -0700293 RTCAudioSessionTestDelegate *delegate =
294 [[RTCAudioSessionTestDelegate alloc] init];
295 [session addDelegate:delegate];
296
Peter Hanspers47217362017-10-05 11:39:15 +0200297 float expectedVolume = 0.75;
298 mockAVAudioSession.outputVolume = expectedVolume;
jtteh13ae11a2017-05-25 17:52:20 -0700299
Peter Hanspers47217362017-10-05 11:39:15 +0200300 EXPECT_EQ(expectedVolume, delegate.outputVolume);
jtteh13ae11a2017-05-25 17:52:20 -0700301}
302
tkchin0ce3bf92016-03-12 16:52:04 -0800303@end
304
tkchine54467f2016-03-15 16:54:03 -0700305namespace webrtc {
306
307class AudioSessionTest : public ::testing::Test {
308 protected:
Mirko Bonadei17aff352018-07-26 12:20:40 +0200309 void TearDown() override {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200310 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
311 for (id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> delegate : session.delegates) {
tkchine54467f2016-03-15 16:54:03 -0700312 [session removeDelegate:delegate];
313 }
314 }
315};
316
317TEST_F(AudioSessionTest, LockForConfiguration) {
tkchin0ce3bf92016-03-12 16:52:04 -0800318 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
319 [test testLockForConfiguration];
320}
tkchine54467f2016-03-15 16:54:03 -0700321
322TEST_F(AudioSessionTest, AddAndRemoveDelegates) {
323 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
324 [test testAddAndRemoveDelegates];
325}
326
327TEST_F(AudioSessionTest, PushDelegate) {
328 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
329 [test testPushDelegate];
330}
331
332TEST_F(AudioSessionTest, ZeroingWeakDelegate) {
333 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
334 [test testZeroingWeakDelegate];
335}
336
tkchinefdd9302016-04-11 12:00:59 -0700337TEST_F(AudioSessionTest, RemoveDelegateOnDealloc) {
338 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
339 [test testRemoveDelegateOnDealloc];
340}
341
jtteh3c9a6c02017-04-18 09:09:35 -0700342TEST_F(AudioSessionTest, AudioSessionActivation) {
343 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
344 [test testAudioSessionActivation];
345}
346
jttehf84c1d62017-04-21 13:56:39 -0700347TEST_F(AudioSessionTest, ConfigureWebRTCSession) {
348 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
349 [test testConfigureWebRTCSession];
350}
jtteh3c9a6c02017-04-18 09:09:35 -0700351
jtteh13ae11a2017-05-25 17:52:20 -0700352TEST_F(AudioSessionTest, AudioVolumeDidNotify) {
353 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init];
354 [test testAudioVolumeDidNotify];
355}
356
tkchine54467f2016-03-15 16:54:03 -0700357} // namespace webrtc