blob: 71a0caa48158459787a785391ce66b0df5a1102c [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/audio/mac/audio_device_listener_mac.h"
#include <CoreAudio/AudioHardware.h>
#include <memory>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "media/base/bind_to_current_loop.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
class AudioDeviceListenerMacTest : public testing::Test {
public:
AudioDeviceListenerMacTest() {
// It's important to create the device listener from the message loop in
// order to ensure we don't end up with unbalanced TaskObserver calls.
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&AudioDeviceListenerMacTest::CreateDeviceListener,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
}
AudioDeviceListenerMacTest(const AudioDeviceListenerMacTest&) = delete;
AudioDeviceListenerMacTest& operator=(const AudioDeviceListenerMacTest&) =
delete;
~AudioDeviceListenerMacTest() override {
// It's important to destroy the device listener from the message loop in
// order to ensure we don't end up with unbalanced TaskObserver calls.
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&AudioDeviceListenerMacTest::DestroyDeviceListener,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
}
void CreateDeviceListener() {
// Force a post task using BindToCurrentLoop() to ensure device listener
// internals are working correctly.
device_listener_ = std::make_unique<AudioDeviceListenerMac>(
BindToCurrentLoop(
base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
base::Unretained(this))),
true /* monitor_default_input */, true /* monitor_addition_removal */);
}
void DestroyDeviceListener() { device_listener_.reset(); }
bool ListenerIsValid() { return !device_listener_->listener_cb_.is_null(); }
bool SimulateEvent(const AudioObjectPropertyAddress& address) {
// Include multiple addresses to ensure only a single device change event
// occurs.
const AudioObjectPropertyAddress addresses[] = {
address,
{kAudioHardwarePropertySleepingIsAllowed,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}};
OSStatus status = device_listener_->OnEvent(
kAudioObjectSystemObject, 2, addresses,
device_listener_->default_output_listener_.get());
if (status != noErr)
return false;
device_listener_->OnEvent(kAudioObjectSystemObject, 2, addresses,
device_listener_->default_input_listener_.get());
if (status != noErr)
return false;
device_listener_->OnEvent(
kAudioObjectSystemObject, 2, addresses,
device_listener_->addition_removal_listener_.get());
return status == noErr;
}
bool SimulateDefaultOutputDeviceChange() {
return SimulateEvent(
AudioDeviceListenerMac::kDefaultOutputDeviceChangePropertyAddress);
}
bool SimulateDefaultInputDeviceChange() {
return SimulateEvent(
AudioDeviceListenerMac::kDefaultInputDeviceChangePropertyAddress);
}
bool SimulateDeviceAdditionRemoval() {
return SimulateEvent(AudioDeviceListenerMac::kDevicesPropertyAddress);
}
MOCK_METHOD0(OnDeviceChange, void());
protected:
base::test::SingleThreadTaskEnvironment task_environment_;
std::unique_ptr<AudioDeviceListenerMac> device_listener_;
};
// Simulate a device change event and ensure we get the right callback.
TEST_F(AudioDeviceListenerMacTest, Events) {
ASSERT_TRUE(ListenerIsValid());
EXPECT_CALL(*this, OnDeviceChange()).Times(1);
ASSERT_TRUE(SimulateDefaultOutputDeviceChange());
base::RunLoop().RunUntilIdle();
EXPECT_CALL(*this, OnDeviceChange()).Times(1);
ASSERT_TRUE(SimulateDefaultInputDeviceChange());
base::RunLoop().RunUntilIdle();
EXPECT_CALL(*this, OnDeviceChange()).Times(1);
ASSERT_TRUE(SimulateDeviceAdditionRemoval());
base::RunLoop().RunUntilIdle();
}
} // namespace media