chromium/media/audio/mac/audio_device_listener_mac_unittest.cc

// 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 <optional>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::Return;

namespace media {

class AudioDeviceListenerMacUnderTest final : public AudioDeviceListenerMac {
 public:
  AudioDeviceListenerMacUnderTest(base::RepeatingClosure listener_cb,
                                  bool monitor_output_sample_rate_changes,
                                  bool monitor_default_input,
                                  bool monitor_addition_removal,
                                  bool monitor_sources)
      : AudioDeviceListenerMac(std::move(listener_cb),
                               monitor_output_sample_rate_changes,
                               monitor_default_input,
                               monitor_addition_removal,
                               monitor_sources) {}

  ~AudioDeviceListenerMacUnderTest() final = default;

  MOCK_METHOD0(GetAllAudioDeviceIDs, std::vector<AudioObjectID>());
  MOCK_METHOD1(IsOutputDevice, bool(AudioObjectID));
  MOCK_METHOD2(GetDeviceSource, std::optional<uint32_t>(AudioObjectID, bool));

  OSStatus AddPropertyListener(AudioObjectID inObjectID,
                               const AudioObjectPropertyAddress* inAddress,
                               AudioObjectPropertyListenerProc inListener,
                               void* inClientData) final {
    return noErr;
  }
  OSStatus RemovePropertyListener(AudioObjectID inObjectID,
                                  const AudioObjectPropertyAddress* inAddress,
                                  AudioObjectPropertyListenerProc inListener,
                                  void* inClientData) final {
    return noErr;
  }
};

class AudioDeviceListenerMacTest : public testing::Test {
 public:
  AudioDeviceListenerMacTest() = default;

  AudioDeviceListenerMacTest(const AudioDeviceListenerMacTest&) = delete;
  AudioDeviceListenerMacTest& operator=(const AudioDeviceListenerMacTest&) =
      delete;

  ~AudioDeviceListenerMacTest() override = default;

  static bool SimulateEvent(const AudioObjectPropertyAddress& address,
                            std::vector<void*>& contexts) {
    // Include multiple addresses to ensure only a single device change event
    // occurs.
    const AudioObjectPropertyAddress addresses[] = {
        address,
        {kAudioHardwarePropertySleepingIsAllowed,
         kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain}};

    for (void* context : contexts) {
      OSStatus status = AudioDeviceListenerMac::SimulateEventForTesting(
          kAudioObjectSystemObject, 2, addresses, context);
      if (status != noErr)
        return false;
    }
    return true;
  }

  static bool SimulateDeviceEvent(AudioObjectID id,
                                  std::vector<void*>& contexts,
                                  const AudioObjectPropertyAddress& address) {
    const AudioObjectPropertyAddress addresses[] = {address};
    for (void* context : contexts) {
      OSStatus status = AudioDeviceListenerMac::SimulateEventForTesting(
          id, 1, addresses, context);
      if (status != noErr)
        return false;
    }
    return true;
  }

  static bool SimulateSampleRateChange(AudioObjectID id,
                                       std::vector<void*>& contexts) {
    return SimulateDeviceEvent(
        id, contexts, AudioDeviceListenerMac::kPropertyOutputSampleRateChanged);
  }

  static bool SimulateOutputSourceChange(AudioObjectID id,
                                         std::vector<void*>& contexts) {
    return SimulateDeviceEvent(
        id, contexts, AudioDeviceListenerMac::kPropertyOutputSourceChanged);
  }

  static bool SimluateInputSourceChange(AudioObjectID id,
                                        std::vector<void*>& contexts) {
    return SimulateDeviceEvent(
        id, contexts, AudioDeviceListenerMac::kPropertyInputSourceChanged);
  }

  static void CreatePropertyListeners(AudioDeviceListenerMac* device_listener) {
    return device_listener->CreatePropertyListeners();
  }

  static std::vector<void*> GetPropertyListeners(
      AudioDeviceListenerMac* device_listener) {
    return device_listener->GetPropertyListenersForTesting();
  }

  static bool SimulateDefaultOutputDeviceChange(std::vector<void*>& contexts) {
    return SimulateEvent(
        AudioDeviceListenerMac::kDefaultOutputDeviceChangePropertyAddress,
        contexts);
  }

  static bool SimulateDefaultInputDeviceChange(std::vector<void*>& contexts) {
    return SimulateEvent(
        AudioDeviceListenerMac::kDefaultInputDeviceChangePropertyAddress,
        contexts);
  }

  static bool SimulateDeviceAdditionRemoval(std::vector<void*>& contexts) {
    return SimulateEvent(AudioDeviceListenerMac::kDevicesPropertyAddress,
                         contexts);
  }

  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_DeviceMonitoring) {
  auto device_listener = AudioDeviceListenerMac::Create(
      base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
                          base::Unretained(this)),
      /*monitor_output_sample_rate_changes=*/false,
      /*monitor_default_input=*/true, /*monitor_addition_removal=*/true,
      /*monitor_sources*/ false);

  std::vector<void*> property_listeners =
      GetPropertyListeners(device_listener.get());
  // Default output, default input, addition-removal.
  EXPECT_EQ(property_listeners.size(), 3u);

  EXPECT_CALL(*this, OnDeviceChange()).Times(1);
  ASSERT_TRUE(SimulateDefaultOutputDeviceChange(property_listeners));
  base::RunLoop().RunUntilIdle();
  testing::Mock::VerifyAndClearExpectations(this);

  EXPECT_CALL(*this, OnDeviceChange()).Times(1);
  ASSERT_TRUE(SimulateDefaultInputDeviceChange(property_listeners));
  base::RunLoop().RunUntilIdle();
  testing::Mock::VerifyAndClearExpectations(this);

  EXPECT_CALL(*this, OnDeviceChange()).Times(1);
  ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
  base::RunLoop().RunUntilIdle();

  device_listener.reset();
  base::RunLoop().RunUntilIdle();
}

TEST_F(AudioDeviceListenerMacTest, Events_DefaultOutput) {
  auto device_listener = AudioDeviceListenerMac::Create(
      base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
                          base::Unretained(this)),
      /*monitor_output_sample_rate_changes=*/false,
      /*monitor_default_input=*/false, /*monitor_addition_removal=*/false,
      /*monitor_sources*/ false);

  std::vector<void*> property_listeners =
      GetPropertyListeners(device_listener.get());
  // Default output.
  EXPECT_EQ(property_listeners.size(), 1u);

  EXPECT_CALL(*this, OnDeviceChange()).Times(1);
  ASSERT_TRUE(SimulateDefaultOutputDeviceChange(property_listeners));
  base::RunLoop().RunUntilIdle();
  testing::Mock::VerifyAndClearExpectations(this);

  EXPECT_CALL(*this, OnDeviceChange()).Times(0);
  ASSERT_TRUE(SimulateDefaultInputDeviceChange(property_listeners));
  ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
  base::RunLoop().RunUntilIdle();
}

TEST_F(AudioDeviceListenerMacTest, EventsNotProcessedAfterLisneterDeletion) {
  auto device_listener = AudioDeviceListenerMac::Create(
      base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
                          base::Unretained(this)),
      /*monitor_output_sample_rate_changes=*/false,
      /*monitor_default_input=*/true, /*monitor_addition_removal=*/true,
      /*monitor_sources*/ false);

  std::vector<void*> property_listeners =
      GetPropertyListeners(device_listener.get());
  // Default output, default input, addition-removal.
  EXPECT_EQ(property_listeners.size(), 3u);

  // AudioDeviceListenerMac is destroyed, but property listener destructions are
  // delayed. Notifications on them should still work, but should not result
  // in OnDeviceChange call.
  device_listener.reset();

  EXPECT_CALL(*this, OnDeviceChange()).Times(0);
  ASSERT_TRUE(SimulateDefaultOutputDeviceChange(property_listeners));
  ASSERT_TRUE(SimulateDefaultInputDeviceChange(property_listeners));
  ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));

  base::RunLoop().RunUntilIdle();
  // Now all property listeners are destroyed.
}

TEST_F(AudioDeviceListenerMacTest, SampleRateChangeSubscription) {
  auto device_listener = std::make_unique<AudioDeviceListenerMacUnderTest>(
      base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
                          base::Unretained(this)),
      /*monitor_output_sample_rate_changes=*/true,
      /*monitor_default_input=*/false, /*monitor_addition_removal=*/false,
      /*monitor_sources*/ false);

  AudioDeviceListenerMacUnderTest& system_audio_mock = *device_listener.get();

  EXPECT_CALL(system_audio_mock, GetAllAudioDeviceIDs())
      .WillOnce(Return(std::vector<AudioObjectID>{1, 2, 3, 4}));

  EXPECT_CALL(system_audio_mock, IsOutputDevice(1)).WillOnce(Return(true));
  EXPECT_CALL(system_audio_mock, IsOutputDevice(2)).WillOnce(Return(false));
  EXPECT_CALL(system_audio_mock, IsOutputDevice(3)).WillOnce(Return(true));
  EXPECT_CALL(system_audio_mock, IsOutputDevice(4)).WillOnce(Return(false));

  CreatePropertyListeners(device_listener.get());

  std::vector<void*> property_listeners =
      GetPropertyListeners(device_listener.get());

  // Default output, addition-removal and two output devices
  EXPECT_EQ(property_listeners.size(), 4u);

  EXPECT_CALL(*this, OnDeviceChange()).Times(1);
  // Output.
  SimulateSampleRateChange(1, property_listeners);
  // Not output - no callback.
  SimulateSampleRateChange(2, property_listeners);

  device_listener.reset();

  base::RunLoop().RunUntilIdle();
}

TEST_F(AudioDeviceListenerMacTest,
       SampleRateChangeSubscriptionUpdatedWhenDevicesAddedRemoved) {
  auto device_listener = std::make_unique<AudioDeviceListenerMacUnderTest>(
      base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
                          base::Unretained(this)),
      /*monitor_output_sample_rate_changes=*/true,
      /*monitor_default_input=*/false, /*monitor_addition_removal=*/false,
      /*monitor_sources*/ false);

  AudioDeviceListenerMacUnderTest& system_audio_mock = *device_listener.get();

  EXPECT_CALL(system_audio_mock, GetAllAudioDeviceIDs())
      .WillOnce(Return(std::vector<AudioObjectID>{1}))
      .WillOnce(Return(std::vector<AudioObjectID>{}))
      .WillOnce(Return(std::vector<AudioObjectID>{1}))
      .WillOnce(Return(std::vector<AudioObjectID>{1, 2}));

  EXPECT_CALL(system_audio_mock, IsOutputDevice(1))
      .Times(3)
      .WillRepeatedly(Return(true));
  EXPECT_CALL(system_audio_mock, IsOutputDevice(2)).WillOnce(Return(true));

  // We only change the subscription, nothing notification-worthy.
  EXPECT_CALL(*this, OnDeviceChange()).Times(0);

  CreatePropertyListeners(device_listener.get());

  std::vector<void*> property_listeners =
      GetPropertyListeners(device_listener.get());

  // Default output, addition-removal and one output device
  EXPECT_EQ(property_listeners.size(), 3u);

  ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
  property_listeners = GetPropertyListeners(device_listener.get());
  // Default output, addition-removal and no output device
  EXPECT_EQ(property_listeners.size(), 2u);

  ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
  property_listeners = GetPropertyListeners(device_listener.get());
  // Default output, addition-removal and one output device
  EXPECT_EQ(property_listeners.size(), 3u);

  ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
  property_listeners = GetPropertyListeners(device_listener.get());
  // Default output, addition-removal and two output device
  EXPECT_EQ(property_listeners.size(), 4u);

  device_listener.reset();
  base::RunLoop().RunUntilIdle();
}

TEST_F(AudioDeviceListenerMacTest,
       SampleRateChangeNotificationsForAddedDevices) {
  auto device_listener = std::make_unique<AudioDeviceListenerMacUnderTest>(
      base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
                          base::Unretained(this)),
      /*monitor_output_sample_rate_changes=*/true,
      /*monitor_default_input=*/false, /*monitor_addition_removal=*/false,
      /*monitor_sources*/ false);

  AudioDeviceListenerMacUnderTest& system_audio_mock = *device_listener.get();

  EXPECT_CALL(system_audio_mock, GetAllAudioDeviceIDs())
      .WillOnce(Return(std::vector<AudioObjectID>{}))
      .WillOnce(Return(std::vector<AudioObjectID>{1}))
      .WillOnce(Return(std::vector<AudioObjectID>{1, 2}));

  EXPECT_CALL(system_audio_mock, IsOutputDevice(1))
      .Times(2)
      .WillRepeatedly(Return(true));
  EXPECT_CALL(system_audio_mock, IsOutputDevice(2)).WillOnce(Return(true));

  CreatePropertyListeners(device_listener.get());

  std::vector<void*> property_listeners =
      GetPropertyListeners(device_listener.get());
  // Default output, addition-removal and none output device
  EXPECT_EQ(property_listeners.size(), 2u);

  {
    EXPECT_CALL(*this, OnDeviceChange()).Times(0);
    SimulateSampleRateChange(1, property_listeners);
    SimulateSampleRateChange(2, property_listeners);
    SimulateSampleRateChange(3, property_listeners);
    testing::Mock::VerifyAndClearExpectations(this);
  }

  ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
  property_listeners = GetPropertyListeners(device_listener.get());
  // Default output, addition-removal and one output device
  EXPECT_EQ(property_listeners.size(), 3u);

  {
    EXPECT_CALL(*this, OnDeviceChange()).Times(1);
    SimulateSampleRateChange(1, property_listeners);
    SimulateSampleRateChange(2, property_listeners);
    SimulateSampleRateChange(3, property_listeners);
    testing::Mock::VerifyAndClearExpectations(this);
  }

  ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
  property_listeners = GetPropertyListeners(device_listener.get());
  // Default output, addition-removal and two output device
  EXPECT_EQ(property_listeners.size(), 4u);

  {
    EXPECT_CALL(*this, OnDeviceChange()).Times(2);
    SimulateSampleRateChange(1, property_listeners);
    SimulateSampleRateChange(2, property_listeners);
    SimulateSampleRateChange(3, property_listeners);
    testing::Mock::VerifyAndClearExpectations(this);
  }

  device_listener.reset();
  base::RunLoop().RunUntilIdle();
}

TEST_F(AudioDeviceListenerMacTest,
       SourceChangeSubscriptionUpdatedWhenDevicesAddedRemoved) {
  auto device_listener = std::make_unique<AudioDeviceListenerMacUnderTest>(
      base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
                          base::Unretained(this)),
      /*monitor_output_sample_rate_changes=*/false,
      /*monitor_default_input=*/false, /*monitor_addition_removal=*/true,
      /*monitor_sources*/ true);

  AudioDeviceListenerMacUnderTest& system_audio_mock = *device_listener.get();

  EXPECT_CALL(system_audio_mock, GetAllAudioDeviceIDs())
      .WillOnce(Return(std::vector<AudioObjectID>{1}))
      .WillOnce(Return(std::vector<AudioObjectID>{}))
      .WillOnce(Return(std::vector<AudioObjectID>{1, 2}))
      .WillOnce(Return(std::vector<AudioObjectID>{1, 3}));

  // Device 1 is an input device.
  EXPECT_CALL(system_audio_mock, GetDeviceSource(1, false))
      .Times(3)
      .WillRepeatedly(Return(std::optional<uint32_t>()));
  EXPECT_CALL(system_audio_mock, GetDeviceSource(1, true))
      .Times(3)
      .WillRepeatedly(Return(123));

  // Device 2 is an output device.
  EXPECT_CALL(system_audio_mock, GetDeviceSource(2, false))
      .WillOnce(Return(123));
  EXPECT_CALL(system_audio_mock, GetDeviceSource(2, true))
      .WillOnce(Return(std::optional<uint32_t>()));

  // Device 3 is both an input and output device.
  EXPECT_CALL(system_audio_mock, GetDeviceSource(3, false))
      .WillOnce(Return(123));
  EXPECT_CALL(system_audio_mock, GetDeviceSource(3, true))
      .WillOnce(Return(123));

  // We add or remove devices three times, expect a call for each of them.
  EXPECT_CALL(*this, OnDeviceChange()).Times(3);

  CreatePropertyListeners(device_listener.get());

  std::vector<void*> property_listeners =
      GetPropertyListeners(device_listener.get());

  // Default output, addition-removal and one device source
  EXPECT_EQ(property_listeners.size(), 3u);

  ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
  property_listeners = GetPropertyListeners(device_listener.get());
  // Default output, addition-removal and no device source
  EXPECT_EQ(property_listeners.size(), 2u);

  ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
  property_listeners = GetPropertyListeners(device_listener.get());
  // Default output, addition-removal and two device sources
  EXPECT_EQ(property_listeners.size(), 4u);

  ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
  property_listeners = GetPropertyListeners(device_listener.get());
  // Default output, addition-removal and three device sources
  EXPECT_EQ(property_listeners.size(), 5u);

  device_listener.reset();
  base::RunLoop().RunUntilIdle();
}

TEST_F(AudioDeviceListenerMacTest, SourceChangeNotifications) {
  auto device_listener = std::make_unique<AudioDeviceListenerMacUnderTest>(
      base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
                          base::Unretained(this)),
      /*monitor_output_sample_rate_changes=*/false,
      /*monitor_default_input=*/false, /*monitor_addition_removal=*/true,
      /*monitor_sources*/ true);

  AudioDeviceListenerMacUnderTest& system_audio_mock = *device_listener.get();

  EXPECT_CALL(system_audio_mock, GetAllAudioDeviceIDs())
      .WillOnce(Return(std::vector<AudioObjectID>{1}))
      .WillOnce(Return(std::vector<AudioObjectID>{}))
      .WillOnce(Return(std::vector<AudioObjectID>{1, 2}))
      .WillOnce(Return(std::vector<AudioObjectID>{1, 3}));

  // Device 1 is an input device.
  EXPECT_CALL(system_audio_mock, GetDeviceSource(1, false))
      .Times(3)
      .WillRepeatedly(Return(std::optional<uint32_t>()));
  EXPECT_CALL(system_audio_mock, GetDeviceSource(1, true))
      .Times(3)
      .WillRepeatedly(Return(123));

  // Device 2 is an output device.
  EXPECT_CALL(system_audio_mock, GetDeviceSource(2, false))
      .WillOnce(Return(123));
  EXPECT_CALL(system_audio_mock, GetDeviceSource(2, true))
      .WillOnce(Return(std::optional<uint32_t>()));

  // Device 3 is both an input and output device.
  EXPECT_CALL(system_audio_mock, GetDeviceSource(3, false))
      .WillOnce(Return(123));
  EXPECT_CALL(system_audio_mock, GetDeviceSource(3, true))
      .WillOnce(Return(123));

  CreatePropertyListeners(device_listener.get());

  std::vector<void*> property_listeners =
      GetPropertyListeners(device_listener.get());

  // Default output, addition-removal and one device source
  EXPECT_EQ(property_listeners.size(), 3u);
  {
    EXPECT_CALL(*this, OnDeviceChange()).Times(1);
    SimluateInputSourceChange(1, property_listeners);
    SimluateInputSourceChange(2, property_listeners);
    SimluateInputSourceChange(3, property_listeners);
    testing::Mock::VerifyAndClearExpectations(this);
    EXPECT_CALL(*this, OnDeviceChange()).Times(0);
    SimulateOutputSourceChange(1, property_listeners);
    SimulateOutputSourceChange(2, property_listeners);
    SimulateOutputSourceChange(3, property_listeners);
    testing::Mock::VerifyAndClearExpectations(this);
  }

  EXPECT_CALL(*this, OnDeviceChange());
  ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
  property_listeners = GetPropertyListeners(device_listener.get());
  // Default output, addition-removal and no device source
  EXPECT_EQ(property_listeners.size(), 2u);
  {
    EXPECT_CALL(*this, OnDeviceChange()).Times(0);
    SimluateInputSourceChange(1, property_listeners);
    SimluateInputSourceChange(2, property_listeners);
    SimluateInputSourceChange(3, property_listeners);
    testing::Mock::VerifyAndClearExpectations(this);
    EXPECT_CALL(*this, OnDeviceChange()).Times(0);
    SimulateOutputSourceChange(1, property_listeners);
    SimulateOutputSourceChange(2, property_listeners);
    SimulateOutputSourceChange(3, property_listeners);
    testing::Mock::VerifyAndClearExpectations(this);
  }

  EXPECT_CALL(*this, OnDeviceChange());
  ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
  property_listeners = GetPropertyListeners(device_listener.get());
  // Default output, addition-removal and two device sources
  EXPECT_EQ(property_listeners.size(), 4u);
  {
    EXPECT_CALL(*this, OnDeviceChange()).Times(1);
    SimluateInputSourceChange(1, property_listeners);
    SimluateInputSourceChange(2, property_listeners);
    SimluateInputSourceChange(3, property_listeners);
    testing::Mock::VerifyAndClearExpectations(this);
    EXPECT_CALL(*this, OnDeviceChange()).Times(1);
    SimulateOutputSourceChange(1, property_listeners);
    SimulateOutputSourceChange(2, property_listeners);
    SimulateOutputSourceChange(3, property_listeners);
    testing::Mock::VerifyAndClearExpectations(this);
  }

  EXPECT_CALL(*this, OnDeviceChange());
  ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
  property_listeners = GetPropertyListeners(device_listener.get());
  // Default output, addition-removal and three device sources
  EXPECT_EQ(property_listeners.size(), 5u);
  {
    EXPECT_CALL(*this, OnDeviceChange()).Times(2);
    SimluateInputSourceChange(1, property_listeners);
    SimluateInputSourceChange(2, property_listeners);
    SimluateInputSourceChange(3, property_listeners);
    testing::Mock::VerifyAndClearExpectations(this);
    EXPECT_CALL(*this, OnDeviceChange()).Times(1);
    SimulateOutputSourceChange(1, property_listeners);
    SimulateOutputSourceChange(2, property_listeners);
    SimulateOutputSourceChange(3, property_listeners);
    testing::Mock::VerifyAndClearExpectations(this);
  }

  device_listener.reset();
  base::RunLoop().RunUntilIdle();
}

}  // namespace media