chromium/chromeos/ash/components/dbus/audio/cras_audio_client_unittest.cc

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chromeos/ash/components/dbus/audio/cras_audio_client.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "dbus/message.h"
#include "dbus/mock_bus.h"
#include "dbus/mock_object_proxy.h"
#include "dbus/object_path.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/audio/dbus-constants.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;

namespace ash {

namespace {

// Audio nodes for GetNodes unit test.
const uint64_t kInternalSpeakerId = 10001;
const uint64_t kInternalMicId = 20001;
const uint64_t kNbsMicId = 30001;

const uint32_t kInputMaxSupportedChannels = 1;
const uint32_t kOutputMaxSupportedChannels = 2;

const uint32_t kInputAudioEffect = cras::EFFECT_TYPE_NOISE_CANCELLATION;
const uint32_t kNbsMicAudioEffect = cras::EFFECT_TYPE_HFP_MIC_SR;
const uint32_t kOutputAudioEffect = 0;

const int32_t kInputNumberOfVolumeSteps = 0;
const int32_t kOutputNumberOfVolumeSteps = 25;

const AudioNode kInternalSpeaker(false,
                                 kInternalSpeakerId,
                                 false /* has_v2_stable_device_id */,
                                 kInternalSpeakerId /* stable_device_id_v1 */,
                                 0 /* stable_device_id_v2 */,
                                 "Fake Speaker",
                                 "INTERNAL_SPEAKER",
                                 "Speaker",
                                 false,
                                 0,
                                 kOutputMaxSupportedChannels,
                                 kOutputAudioEffect,
                                 kOutputNumberOfVolumeSteps);

const AudioNode kInternalMic(true,
                             kInternalMicId,
                             false /* has_v2_stable_device_id */,
                             kInternalMicId /* stable_device_id_v1*/,
                             0 /* stable_device_id_v2 */,
                             "Fake Mic",
                             "INTERNAL_MIC",
                             "Internal Mic",
                             false,
                             0,
                             kInputMaxSupportedChannels,
                             kInputAudioEffect,
                             kInputNumberOfVolumeSteps);

const AudioNode kInternalSpeakerV2(
    false,
    kInternalSpeakerId,
    true /* has_v2_stable_device_id */,
    kInternalSpeakerId /* stable_device_id_v1 */,
    // stable_device_id_v2: XOR to make sure the
    // ID is different from |stable_device_id_v1|.
    kInternalSpeakerId ^ 0xFF,
    "Fake Speaker",
    "INTERNAL_SPEAKER",
    "Speaker",
    false,
    0,
    kOutputMaxSupportedChannels,
    kOutputAudioEffect,
    kOutputNumberOfVolumeSteps);

const AudioNode kInternalMicV2(true,
                               kInternalMicId,
                               true /* has_v2_stable_device_id */,
                               kInternalMicId /* stable_device_id_v1 */,
                               // XOR to make sure the ID is different from
                               // |stable_device_id_v1|.
                               kInternalMicId ^ 0xFF /* stable_device_id_v2 */,
                               "Fake Mic",
                               "INTERNAL_MIC",
                               "Internal Mic",
                               false,
                               0,
                               kInputMaxSupportedChannels,
                               kInputAudioEffect,
                               kInputNumberOfVolumeSteps);

const AudioNode kNbsMic(true,
                        kNbsMicId,
                        false /* has_v2_stable_device_id */,
                        kNbsMicId /* stable_device_id_v1*/,
                        0 /* stable_device_id_v2 */,
                        "Fake Nbs Mic",
                        "BLUETOOTH_NB_MIC",
                        "Bluetooth Nb Mic",
                        false,
                        0,
                        kInputMaxSupportedChannels,
                        kNbsMicAudioEffect,
                        kInputNumberOfVolumeSteps);

// A mock CrasAudioClient Observer.
class MockObserver : public CrasAudioClient::Observer {
 public:
  MockObserver() = default;
  ~MockObserver() override = default;
  MOCK_METHOD1(OutputMuteChanged, void(bool mute_on));
  MOCK_METHOD1(InputMuteChanged, void(bool mute_on));
  MOCK_METHOD0(NodesChanged, void());
  MOCK_METHOD1(ActiveOutputNodeChanged, void(uint64_t node_id));
  MOCK_METHOD1(ActiveInputNodeChanged, void(uint64_t node_id));
  MOCK_METHOD2(OutputNodeVolumeChanged, void(uint64_t node_id, int volume));
  MOCK_METHOD2(InputNodeGainChanged, void(uint64_t node_id, int gain));
  MOCK_METHOD2(HotwordTriggered, void(uint64_t tv_sec, uint64_t tv_nsec));
  MOCK_METHOD0(NumberOfActiveStreamsChanged, void());
  MOCK_METHOD1(
      NumberOfInputStreamsWithPermissionChanged,
      void(const base::flat_map<std::string, uint32_t>& num_input_streams));
  MOCK_METHOD2(BluetoothBatteryChanged,
               void(const std::string& address, uint32_t level));
  MOCK_METHOD1(SurveyTriggered,
               void(const base::flat_map<std::string, std::string>&
                        survey_specific_data));
  MOCK_METHOD0(SpeakOnMuteDetected, void());
  MOCK_METHOD1(EwmaPowerReported, void(double power));
  MOCK_METHOD0(NumberOfNonChromeOutputStreamsChanged, void());
  MOCK_METHOD1(NumStreamIgnoreUiGains, void(int32_t num));
  MOCK_METHOD0(NumberOfArcStreamsChanged, void());
  MOCK_METHOD1(SidetoneSupportedChanged, void(bool supported));
};

// Expect the reader to be empty.
void ExpectNoArgument(dbus::MessageReader* reader) {
  EXPECT_FALSE(reader->HasMoreData());
}

// Expect the reader to have an int32_t and an array of doubles.
void ExpectInt32AndArrayOfDoublesArguments(
    int32_t expected_value,
    const std::vector<double>& expected_doubles,
    dbus::MessageReader* reader) {
  int32_t value;
  ASSERT_TRUE(reader->PopInt32(&value));
  EXPECT_EQ(expected_value, value);
  const double* doubles = nullptr;
  size_t size = 0;
  ASSERT_TRUE(reader->PopArrayOfDoubles(&doubles, &size));
  EXPECT_EQ(expected_doubles.size(), size);
  for (size_t i = 0; i < size; ++i) {
    EXPECT_EQ(expected_doubles[i], doubles[i]);
  }
  EXPECT_FALSE(reader->HasMoreData());
}

// Expect the reader to have an uint64_t and an int32_t.
void ExpectUint64AndInt32Arguments(uint64_t expected_uint64,
                                   int32_t expected_int32,
                                   dbus::MessageReader* reader) {
  uint64_t value1;
  ASSERT_TRUE(reader->PopUint64(&value1));
  EXPECT_EQ(expected_uint64, value1);
  int32_t value2;
  ASSERT_TRUE(reader->PopInt32(&value2));
  EXPECT_EQ(expected_int32, value2);
  EXPECT_FALSE(reader->HasMoreData());
}

// Expect the reader to have an uint64_t.
void ExpectUint64Argument(uint64_t expected_value,
                          dbus::MessageReader* reader) {
  uint64_t value;
  ASSERT_TRUE(reader->PopUint64(&value));
  EXPECT_EQ(expected_value, value);
  EXPECT_FALSE(reader->HasMoreData());
}

// Expect the reader to have a bool.
void ExpectBoolArgument(bool expected_bool, dbus::MessageReader* reader) {
  bool value;
  ASSERT_TRUE(reader->PopBool(&value));
  EXPECT_EQ(expected_bool, value);
  EXPECT_FALSE(reader->HasMoreData());
}

// Expect the reader to have an uint64_t and a bool.
void ExpectUint64AndBoolArguments(uint64_t expected_uint64,
                                  bool expected_bool,
                                  dbus::MessageReader* reader) {
  uint64_t value1;
  ASSERT_TRUE(reader->PopUint64(&value1));
  EXPECT_EQ(expected_uint64, value1);
  bool value2;
  ASSERT_TRUE(reader->PopBool(&value2));
  EXPECT_EQ(expected_bool, value2);
  EXPECT_FALSE(reader->HasMoreData());
}

// Expect the reader to have a string.
void ExpectStringArgument(std::string expected_string,
                          dbus::MessageReader* reader) {
  std::string value;
  ASSERT_TRUE(reader->PopString(&value));
  EXPECT_EQ(expected_string, value);
  EXPECT_FALSE(reader->HasMoreData());
}

// Expect the reader to have a int64_t.
void ExpectInt64Argument(int64_t expected_int64, dbus::MessageReader* reader) {
  int64_t value;
  ASSERT_TRUE(reader->PopInt64(&value));
  EXPECT_EQ(expected_int64, value);
  EXPECT_FALSE(reader->HasMoreData());
}

// Expect the reader to have an array of dictionary of string and variant
// string.
void ExpectArrayOfDictOfStringAndVariantStringArguments(
    const std::map<std::string, std::string>& expected_dict,
    dbus::MessageReader* reader) {
  dbus::MessageReader array_reader(nullptr);
  ASSERT_TRUE(reader->PopArray(&array_reader));

  dbus::MessageReader dict_entry_reader(nullptr);
  for (auto& entry : expected_dict) {
    EXPECT_TRUE(array_reader.HasMoreData());
    ASSERT_TRUE(array_reader.PopDictEntry(&dict_entry_reader));

    std::string key, value;
    ASSERT_TRUE(dict_entry_reader.PopString(&key));
    EXPECT_EQ(entry.first, key);
    ASSERT_TRUE(dict_entry_reader.PopVariantOfString(&value));
    EXPECT_EQ(entry.second, value);
  }
  EXPECT_FALSE(reader->HasMoreData());
}

// Expect the reader to have an array of dictionary of string and variant
// int64.
void ExpectArrayOfDictOfStringAndVariantInt64Arguments(
    const std::map<std::string, int64_t>& expected_dict,
    dbus::MessageReader* reader) {
  dbus::MessageReader array_reader(nullptr);
  ASSERT_TRUE(reader->PopArray(&array_reader));

  dbus::MessageReader dict_entry_reader(nullptr);
  for (auto& entry : expected_dict) {
    EXPECT_TRUE(array_reader.HasMoreData());
    ASSERT_TRUE(array_reader.PopDictEntry(&dict_entry_reader));

    std::string key;
    int64_t value;
    ASSERT_TRUE(dict_entry_reader.PopString(&key));
    EXPECT_EQ(entry.first, key);
    ASSERT_TRUE(dict_entry_reader.PopVariantOfInt64(&value));
    EXPECT_EQ(entry.second, value);
  }
  EXPECT_FALSE(reader->HasMoreData());
}

void WriteNodesToResponse(const AudioNodeList& node_list,
                          dbus::MessageWriter* writer) {
  dbus::MessageWriter sub_writer(nullptr);
  dbus::MessageWriter entry_writer(nullptr);
  for (size_t i = 0; i < node_list.size(); ++i) {
    writer->OpenArray("{sv}", &sub_writer);
    sub_writer.OpenDictEntry(&entry_writer);
    entry_writer.AppendString(cras::kIsInputProperty);
    entry_writer.AppendVariantOfBool(node_list[i].is_input);
    sub_writer.CloseContainer(&entry_writer);

    sub_writer.OpenDictEntry(&entry_writer);
    entry_writer.AppendString(cras::kIdProperty);
    entry_writer.AppendVariantOfUint64(node_list[i].id);
    sub_writer.CloseContainer(&entry_writer);

    sub_writer.OpenDictEntry(&entry_writer);
    entry_writer.AppendString(cras::kDeviceNameProperty);
    entry_writer.AppendVariantOfString(node_list[i].device_name);
    sub_writer.CloseContainer(&entry_writer);

    sub_writer.OpenDictEntry(&entry_writer);
    entry_writer.AppendString(cras::kTypeProperty);
    entry_writer.AppendVariantOfString(node_list[i].type);
    sub_writer.CloseContainer(&entry_writer);

    sub_writer.OpenDictEntry(&entry_writer);
    entry_writer.AppendString(cras::kNameProperty);
    entry_writer.AppendVariantOfString(node_list[i].name);
    sub_writer.CloseContainer(&entry_writer);

    sub_writer.OpenDictEntry(&entry_writer);
    entry_writer.AppendString(cras::kActiveProperty);
    entry_writer.AppendVariantOfBool(node_list[i].active);
    sub_writer.CloseContainer(&entry_writer);

    sub_writer.OpenDictEntry(&entry_writer);
    entry_writer.AppendString(cras::kPluggedTimeProperty);
    entry_writer.AppendVariantOfUint64(node_list[i].plugged_time);
    sub_writer.CloseContainer(&entry_writer);

    sub_writer.OpenDictEntry(&entry_writer);
    entry_writer.AppendString(cras::kStableDeviceIdProperty);
    entry_writer.AppendVariantOfUint64(node_list[i].stable_device_id_v1);
    sub_writer.CloseContainer(&entry_writer);

    if (node_list[i].has_v2_stable_device_id) {
      sub_writer.OpenDictEntry(&entry_writer);
      entry_writer.AppendString(cras::kStableDeviceIdNewProperty);
      entry_writer.AppendVariantOfUint64(node_list[i].stable_device_id_v2);
      sub_writer.CloseContainer(&entry_writer);
    }

    sub_writer.OpenDictEntry(&entry_writer);
    entry_writer.AppendString(cras::kAudioEffectProperty);
    entry_writer.AppendVariantOfUint32(node_list[i].audio_effect);
    sub_writer.CloseContainer(&entry_writer);

    sub_writer.OpenDictEntry(&entry_writer);
    entry_writer.AppendString(cras::kNumberOfVolumeStepsProperty);
    entry_writer.AppendVariantOfInt32(node_list[i].number_of_volume_steps);
    sub_writer.CloseContainer(&entry_writer);

    writer->CloseContainer(&sub_writer);
  }
}

// Expect the AudioNodeList result.
void ExpectAudioNodeListResult(bool* called,
                               const AudioNodeList& expected_node_list,
                               std::optional<AudioNodeList> result) {
  *called = true;
  ASSERT_TRUE(result.has_value());
  const AudioNodeList& node_list = result.value();
  ASSERT_EQ(expected_node_list.size(), node_list.size());
  for (size_t i = 0; i < node_list.size(); ++i) {
    EXPECT_EQ(expected_node_list[i].is_input, node_list[i].is_input);
    EXPECT_EQ(expected_node_list[i].id, node_list[i].id);
    EXPECT_EQ(expected_node_list[i].stable_device_id_v1,
              node_list[i].stable_device_id_v1);
    EXPECT_EQ(expected_node_list[i].stable_device_id_v2,
              node_list[i].stable_device_id_v2);
    EXPECT_EQ(expected_node_list[i].device_name, node_list[i].device_name);
    EXPECT_EQ(expected_node_list[i].type, node_list[i].type);
    EXPECT_EQ(expected_node_list[i].name, node_list[i].name);
    EXPECT_EQ(expected_node_list[i].active, node_list[i].active);
    EXPECT_EQ(expected_node_list[i].plugged_time, node_list[i].plugged_time);
    EXPECT_EQ(expected_node_list[i].StableDeviceIdVersion(),
              node_list[i].StableDeviceIdVersion());
    EXPECT_EQ(expected_node_list[i].audio_effect, node_list[i].audio_effect);
    EXPECT_EQ(expected_node_list[i].number_of_volume_steps,
              node_list[i].number_of_volume_steps);
  }
}

}  // namespace

class CrasAudioClientTest : public testing::Test {
 public:
  CrasAudioClientTest()
      : interface_name_(cras::kCrasControlInterface), response_(nullptr) {}

  void SetUp() override {
    // Create a mock bus.
    dbus::Bus::Options options;
    options.bus_type = dbus::Bus::SYSTEM;
    mock_bus_ = new dbus::MockBus(options);

    // Create a mock cras proxy.
    mock_cras_proxy_ =
        new dbus::MockObjectProxy(mock_bus_.get(), cras::kCrasServiceName,
                                  dbus::ObjectPath(cras::kCrasServicePath));

    // Set an expectation so mock_cras_proxy's CallMethod() will use
    // OnCallMethod() to return responses.
    EXPECT_CALL(*mock_cras_proxy_.get(), DoCallMethod(_, _, _))
        .WillRepeatedly(Invoke(this, &CrasAudioClientTest::OnCallMethod));

    // Set an expectation so mock_cras_proxy's CallMethodWithErrorCallback()
    // will use OnCallMethodWithErrorCallback() to return responses.
    EXPECT_CALL(*mock_cras_proxy_.get(),
                DoCallMethodWithErrorCallback(_, _, _, _))
        .WillRepeatedly(
            Invoke(this, &CrasAudioClientTest::OnCallMethodWithErrorCallback));

    // Set an expectation so mock_cras_proxy's monitoring OutputMuteChanged
    // ConnectToSignal will use OnConnectToOutputMuteChanged() to run the
    // callback.
    EXPECT_CALL(
        *mock_cras_proxy_.get(),
        DoConnectToSignal(interface_name_, cras::kOutputMuteChanged, _, _))
        .WillRepeatedly(
            Invoke(this, &CrasAudioClientTest::OnConnectToOutputMuteChanged));

    // Set an expectation so mock_cras_proxy's monitoring InputMuteChanged
    // ConnectToSignal will use OnConnectToInputMuteChanged() to run the
    // callback.
    EXPECT_CALL(
        *mock_cras_proxy_.get(),
        DoConnectToSignal(interface_name_, cras::kInputMuteChanged, _, _))
        .WillRepeatedly(
            Invoke(this, &CrasAudioClientTest::OnConnectToInputMuteChanged));

    // Set an expectation so mock_cras_proxy's monitoring NodesChanged
    // ConnectToSignal will use OnConnectToNodesChanged() to run the callback.
    EXPECT_CALL(*mock_cras_proxy_.get(),
                DoConnectToSignal(interface_name_, cras::kNodesChanged, _, _))
        .WillRepeatedly(
            Invoke(this, &CrasAudioClientTest::OnConnectToNodesChanged));

    // Set an expectation so mock_cras_proxy's monitoring
    // ActiveOutputNodeChanged ConnectToSignal will use
    // OnConnectToActiveOutputNodeChanged() to run the callback.
    EXPECT_CALL(*mock_cras_proxy_.get(),
                DoConnectToSignal(interface_name_,
                                  cras::kActiveOutputNodeChanged, _, _))
        .WillRepeatedly(Invoke(
            this, &CrasAudioClientTest::OnConnectToActiveOutputNodeChanged));

    // Set an expectation so mock_cras_proxy's monitoring
    // ActiveInputNodeChanged ConnectToSignal will use
    // OnConnectToActiveInputNodeChanged() to run the callback.
    EXPECT_CALL(
        *mock_cras_proxy_.get(),
        DoConnectToSignal(interface_name_, cras::kActiveInputNodeChanged, _, _))
        .WillRepeatedly(Invoke(
            this, &CrasAudioClientTest::OnConnectToActiveInputNodeChanged));

    // Set an expectation so mock_cras_proxy's monitoring
    // OutputNodeVolumeChanged ConnectToSignal will use
    // OnConnectToOutputNodeVolumeChanged() to run the callback.
    EXPECT_CALL(*mock_cras_proxy_.get(),
                DoConnectToSignal(interface_name_,
                                  cras::kOutputNodeVolumeChanged, _, _))
        .WillRepeatedly(Invoke(
            this, &CrasAudioClientTest::OnConnectToOutputNodeVolumeChanged));

    // Set an expectation so mock_cras_proxy's monitoring
    // InputNodeGainChanged ConnectToSignal will use
    // OnConnectToInputNodeGainChanged() to run the callback.
    EXPECT_CALL(
        *mock_cras_proxy_.get(),
        DoConnectToSignal(interface_name_, cras::kInputNodeGainChanged, _, _))
        .WillRepeatedly(Invoke(
            this, &CrasAudioClientTest::OnConnectToInputNodeGainChanged));

    // Set an expectation so mock_cras_proxy's monitoring
    // HotwordTriggered ConnectToSignal will use OnHotwordTriggered() to
    // run the callback.
    EXPECT_CALL(
        *mock_cras_proxy_.get(),
        DoConnectToSignal(interface_name_, cras::kHotwordTriggered, _, _))
        .WillRepeatedly(Invoke(this, &CrasAudioClientTest::OnHotwordTriggered));

    // Set an expectation so mock_cras_proxy's monitoring
    // NumberOfActiveStreamsChanged ConnectToSignal will use
    // OnNumberOfActiveStreamsChanged() to run the callback.
    EXPECT_CALL(*mock_cras_proxy_.get(),
                DoConnectToSignal(interface_name_,
                                  cras::kNumberOfActiveStreamsChanged, _, _))
        .WillRepeatedly(
            Invoke(this, &CrasAudioClientTest::OnNumberOfActiveStreamsChanged));

    // Set and expectation so mock_cras_proxy's monitoring
    // NumberOfInputStreamsWithPermissionChanged ConnectToSignal will use
    // OnNumberOfInputStreamsWithPermissionChanged to run the callback.
    EXPECT_CALL(*mock_cras_proxy_.get(),
                DoConnectToSignal(
                    interface_name_,
                    cras::kNumberOfInputStreamsWithPermissionChanged, _, _))
        .WillRepeatedly(Invoke(
            this,
            &CrasAudioClientTest::OnNumberOfInputStreamsWithPermissionChanged));

    // Set an expectation so mock_cras_proxy's monitoring
    // BluetoothBatteryChanged ConnectToSignal will use
    // OnBluetoothBatteryChanged() to run the callback.
    EXPECT_CALL(*mock_cras_proxy_.get(),
                DoConnectToSignal(interface_name_,
                                  cras::kBluetoothBatteryChanged, _, _))
        .WillRepeatedly(
            Invoke(this, &CrasAudioClientTest::OnBluetoothBatteryChanged));

    // Set an expectation so mock_cras_proxy's monitoring
    // SurveyTrigger ConnectToSignal will use
    // OnSurveyTriggered() to run the callback.
    EXPECT_CALL(*mock_cras_proxy_.get(),
                DoConnectToSignal(interface_name_, cras::kSurveyTrigger, _, _))
        .WillRepeatedly(Invoke(this, &CrasAudioClientTest::OnSurveyTriggered));

    // Set an expectation so mock_cras_proxy's monitoring
    // SurveyTrigger ConnectToSignal will use
    // OnSpeakOnMuteDetected() to run the callback.
    EXPECT_CALL(
        *mock_cras_proxy_.get(),
        DoConnectToSignal(interface_name_,
                          cras::kNumberOfNonChromeOutputStreamsChanged, _, _))
        .WillRepeatedly(Invoke(
            this,
            &CrasAudioClientTest::OnNumberOfNonChromeOutputStreamsChanged));

    // Set an expectation so mock_cras_proxy's monitoring
    // SurveyTrigger ConnectToSignal will use
    // OnSpeakOnMuteDetected() to run the callback.
    EXPECT_CALL(
        *mock_cras_proxy_.get(),
        DoConnectToSignal(interface_name_, cras::kSpeakOnMuteDetected, _, _))
        .WillRepeatedly(
            Invoke(this, &CrasAudioClientTest::OnSpeakOnMuteDetected));

    // Set an expectation so mock_cras_proxy's monitoring
    // EwmaPowerReported ConnectToSignal will use
    // OnEwmaPowerReported() to run the callback.
    EXPECT_CALL(*mock_cras_proxy_.get(),
                DoConnectToSignal(interface_name_, "EwmaPowerReported", _, _))
        .WillRepeatedly(
            Invoke(this, &CrasAudioClientTest::OnEwmaPowerReported));

    // Set an expectation so mock_cras_proxy's monitoring
    // SurveyTrigger ConnectToSignal will use
    // OnNumStreamIgnoreUiGains() to run the callback.
    EXPECT_CALL(*mock_cras_proxy_.get(),
                DoConnectToSignal(interface_name_,
                                  cras::kNumStreamIgnoreUiGainsChanged, _, _))
        .WillRepeatedly(Invoke(
            this, &CrasAudioClientTest::OnNumStreamIgnoreUiGainsChanged));

    // Set an expectation so mock_cras_proxy's monitoring
    // NumberOfArcStreamsChanged ConnectToSignal will use
    // OnNumberOfArcStreamsChanged() to run the callback.
    EXPECT_CALL(*mock_cras_proxy_.get(),
                DoConnectToSignal(interface_name_,
                                  cras::kNumberOfArcStreamsChanged, _, _))
        .WillRepeatedly(
            Invoke(this, &CrasAudioClientTest::OnNumberOfArcStreamsChanged));

    // Set an expectation so mock_cras_proxy's monitoring
    // SidetoneSupportedChanged ConnectToSignal will use
    // OnSidetoneSupportedChanged() to run the callback.
    EXPECT_CALL(
        *mock_cras_proxy_.get(),
        DoConnectToSignal(interface_name_, "SidetoneSupportedChanged", _, _))
        .WillRepeatedly(
            Invoke(this, &CrasAudioClientTest::OnSidetoneSupportedChanged));

    // Set an expectation so mock_bus's GetObjectProxy() for the given
    // service name and the object path will return mock_cras_proxy_.
    EXPECT_CALL(*mock_bus_.get(),
                GetObjectProxy(cras::kCrasServiceName,
                               dbus::ObjectPath(cras::kCrasServicePath)))
        .WillOnce(Return(mock_cras_proxy_.get()));

    // ShutdownAndBlock() will be called in TearDown().
    EXPECT_CALL(*mock_bus_.get(), ShutdownAndBlock()).WillOnce(Return());

    // Create a client with the mock bus.
    CrasAudioClient::Initialize(mock_bus_.get());
    // Run the message loop to run the signal connection result callback.
    base::RunLoop().RunUntilIdle();
  }

  void TearDown() override {
    mock_bus_->ShutdownAndBlock();
    CrasAudioClient::Shutdown();
  }

 protected:
  // A callback to intercept and check the method call arguments.
  using ArgumentCheckCallback =
      base::RepeatingCallback<void(dbus::MessageReader* reader)>;

  // Sets expectations for called method name and arguments, and sets response.
  void PrepareForMethodCall(const std::string& method_name,
                            const ArgumentCheckCallback& argument_checker,
                            dbus::Response* response) {
    expected_method_name_ = method_name;
    argument_checker_ = argument_checker;
    response_ = response;
  }

  // Send output mute changed signal to the tested client.
  void SendOutputMuteChangedSignal(dbus::Signal* signal) {
    ASSERT_FALSE(output_mute_changed_handler_.is_null());
    output_mute_changed_handler_.Run(signal);
  }

  // Send input mute changed signal to the tested client.
  void SendInputMuteChangedSignal(dbus::Signal* signal) {
    ASSERT_FALSE(input_mute_changed_handler_.is_null());
    input_mute_changed_handler_.Run(signal);
  }

  // Send nodes changed signal to the tested client.
  void SendNodesChangedSignal(dbus::Signal* signal) {
    ASSERT_FALSE(nodes_changed_handler_.is_null());
    nodes_changed_handler_.Run(signal);
  }

  // Send active output node changed signal to the tested client.
  void SendActiveOutputNodeChangedSignal(dbus::Signal* signal) {
    ASSERT_FALSE(active_output_node_changed_handler_.is_null());
    active_output_node_changed_handler_.Run(signal);
  }

  // Send active input node changed signal to the tested client.
  void SendActiveInputNodeChangedSignal(dbus::Signal* signal) {
    ASSERT_FALSE(active_input_node_changed_handler_.is_null());
    active_input_node_changed_handler_.Run(signal);
  }

  // Send output node volume changed signal to the tested client.
  void SendOutputNodeVolumeChangedSignal(dbus::Signal* signal) {
    ASSERT_FALSE(output_node_volume_changed_handler_.is_null());
    output_node_volume_changed_handler_.Run(signal);
  }

  // Send output node volume changed signal to the tested client.
  void SendInputNodeGainChangedSignal(dbus::Signal* signal) {
    ASSERT_FALSE(input_node_gain_changed_handler_.is_null());
    input_node_gain_changed_handler_.Run(signal);
  }

  // Send hotword triggered signal to the tested client.
  void SendHotwordTriggeredSignal(dbus::Signal* signal) {
    ASSERT_FALSE(hotword_triggered_handler_.is_null());
    hotword_triggered_handler_.Run(signal);
  }

  // Send number-of-active-streams-changed signal to the tested client.
  void SendNumberOfActiveStreamsChangedSignal(dbus::Signal* signal) {
    ASSERT_FALSE(number_of_active_streams_changed_handler_.is_null());
    number_of_active_streams_changed_handler_.Run(signal);
  }

  // Send number-of-input-streams-with-permission-changed signal to the tested
  // client.
  void SendNumberOfInputStreamsWithPermissionChangedSignal(
      dbus::Signal* signal) {
    ASSERT_FALSE(
        number_of_input_streams_with_permission_changed_handler_.is_null());
    number_of_input_streams_with_permission_changed_handler_.Run(signal);
  }

  // Send Bluetooth battery changed signal to the tested client.
  void SendBluetoothBatteryChangedSignal(dbus::Signal* signal) {
    ASSERT_FALSE(bluetooth_battery_changed_handler_.is_null());
    bluetooth_battery_changed_handler_.Run(signal);
  }

  // Send survey-trigger signal to the tested client.
  void SendSurveyTriggerSignal(dbus::Signal* signal) {
    ASSERT_FALSE(survey_trigger_handler_.is_null());
    survey_trigger_handler_.Run(signal);
  }

  // Send speak-on-mute detected signal to the tested client.
  void SendSpeakOnMuteDetectedSignal(dbus::Signal* signal) {
    ASSERT_FALSE(speak_on_mute_detected_handler_.is_null());
    speak_on_mute_detected_handler_.Run(signal);
  }

  // Send EwmaPowerReported signal to the tested client.
  void SendEwmaPowerReportedSignal(dbus::Signal* signal) {
    ASSERT_FALSE(ewma_power_reported_handler_.is_null());
    ewma_power_reported_handler_.Run(signal);
  }

  void SendNumberOfNonChromeOutputStreamsChangedSignal(dbus::Signal* signal) {
    ASSERT_FALSE(
        number_of_non_chrome_output_streams_changed_handler_.is_null());
    number_of_non_chrome_output_streams_changed_handler_.Run(signal);
  }

  // Send num-stream-ignore-ui-gains changed signal to the tested client.
  void SendNumStreamIgnoreUiGainsSignal(dbus::Signal* signal) {
    ASSERT_FALSE(num_stream_ignore_ui_gains_handler_.is_null());
    num_stream_ignore_ui_gains_handler_.Run(signal);
  }

  // Send number of arc streams changed signal to the tested client.
  void SendNumberOfArcStreamsChangedSignal(dbus::Signal* signal) {
    ASSERT_FALSE(number_of_arc_streams_changed_handler_.is_null());
    number_of_arc_streams_changed_handler_.Run(signal);
  }

  // Send sidetone supported changed signal to the tested client.
  void SendSidetoneSupportedChangedSignal(dbus::Signal* signal) {
    ASSERT_FALSE(sidetone_supported_changed_handler_.is_null());
    sidetone_supported_changed_handler_.Run(signal);
  }

  CrasAudioClient* client() { return CrasAudioClient::Get(); }

  // The interface name.
  const std::string interface_name_;
  // A message loop to emulate asynchronous behavior.
  base::test::SingleThreadTaskEnvironment task_environment_;
  // The mock bus.
  scoped_refptr<dbus::MockBus> mock_bus_;
  // The mock object proxy.
  scoped_refptr<dbus::MockObjectProxy> mock_cras_proxy_;
  // The OutputMuteChanged signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback output_mute_changed_handler_;
  // The InputMuteChanged signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback input_mute_changed_handler_;
  // The NodesChanged signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback nodes_changed_handler_;
  // The ActiveOutputNodeChanged signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback active_output_node_changed_handler_;
  // The ActiveInputNodeChanged signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback active_input_node_changed_handler_;
  // The OutputNodeVolumeChanged signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback output_node_volume_changed_handler_;
  // The InputNodeGainChanged signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback input_node_gain_changed_handler_;

  // The HotwordTriggered signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback hotword_triggered_handler_;
  // The NumberOfActiveStreamsChanged signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback number_of_active_streams_changed_handler_;
  // The NumberOfInputStreamsWithPermissionChanged signal handler given by the
  // tested client.
  dbus::ObjectProxy::SignalCallback
      number_of_input_streams_with_permission_changed_handler_;
  // The BluetoothBatteryChanged signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback bluetooth_battery_changed_handler_;
  // The SurveyTrigger signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback survey_trigger_handler_;
  // The SpeakOnMuteDetected signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback speak_on_mute_detected_handler_;
  // The EwmaPowerReported signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback ewma_power_reported_handler_;
  // The NumberOfNonChromeOutputStreamsChanged signal handler given by the
  // tested client.
  dbus::ObjectProxy::SignalCallback
      number_of_non_chrome_output_streams_changed_handler_;
  // The NumStreamIgnoreUiGains signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback num_stream_ignore_ui_gains_handler_;
  // The NumberOfArcStreamsChanged signal handler given by the
  // tested client.
  dbus::ObjectProxy::SignalCallback number_of_arc_streams_changed_handler_;
  // The SidetoneSupportedChanged signal handler given by the tested client.
  dbus::ObjectProxy::SignalCallback sidetone_supported_changed_handler_;
  // The name of the method which is expected to be called.
  std::string expected_method_name_;
  // The response which the mock cras proxy returns.
  raw_ptr<dbus::Response, DanglingUntriaged> response_;
  // A callback to intercept and check the method call arguments.
  ArgumentCheckCallback argument_checker_;

 private:
  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnConnectToOutputMuteChanged(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    output_mute_changed_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnConnectToInputMuteChanged(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    input_mute_changed_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnConnectToNodesChanged(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    nodes_changed_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnConnectToActiveOutputNodeChanged(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    active_output_node_changed_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnConnectToActiveInputNodeChanged(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    active_input_node_changed_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnConnectToOutputNodeVolumeChanged(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    output_node_volume_changed_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnConnectToInputNodeGainChanged(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    input_node_gain_changed_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnHotwordTriggered(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    hotword_triggered_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnNumberOfActiveStreamsChanged(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    number_of_active_streams_changed_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  void OnNumberOfInputStreamsWithPermissionChanged(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    number_of_input_streams_with_permission_changed_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnBluetoothBatteryChanged(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    bluetooth_battery_changed_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnSurveyTriggered(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    survey_trigger_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  void OnNumberOfNonChromeOutputStreamsChanged(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    number_of_non_chrome_output_streams_changed_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }
  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnSpeakOnMuteDetected(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    speak_on_mute_detected_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnEwmaPowerReported(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    ewma_power_reported_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnNumStreamIgnoreUiGainsChanged(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    num_stream_ignore_ui_gains_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnNumberOfArcStreamsChanged(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    number_of_arc_streams_changed_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the requested interface name and signal name.
  // Used to implement the mock cras proxy.
  void OnSidetoneSupportedChanged(
      const std::string& interface_name,
      const std::string& signal_name,
      const dbus::ObjectProxy::SignalCallback& signal_callback,
      dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
    sidetone_supported_changed_handler_ = signal_callback;
    constexpr bool success = true;
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*on_connected_callback),
                                  interface_name, signal_name, success));
  }

  // Checks the content of the method call and returns the response.
  // Used to implement the mock cras proxy.
  void OnCallMethod(dbus::MethodCall* method_call,
                    int timeout_ms,
                    dbus::ObjectProxy::ResponseCallback* response) {
    EXPECT_EQ(interface_name_, method_call->GetInterface());
    EXPECT_EQ(expected_method_name_, method_call->GetMember());
    dbus::MessageReader reader(method_call);
    argument_checker_.Run(&reader);
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*response), response_));
  }

  // Checks the content of the method call and returns the response.
  // Used to implement the mock cras proxy.
  void OnCallMethodWithErrorCallback(
      dbus::MethodCall* method_call,
      int timeout_ms,
      dbus::ObjectProxy::ResponseCallback* response_callback,
      dbus::ObjectProxy::ErrorCallback* error_callback) {
    OnCallMethod(method_call, timeout_ms, response_callback);
  }
};

TEST_F(CrasAudioClientTest, OutputMuteChanged) {
  const bool kSystemMuteOn = false;
  const bool kUserMuteOn = true;
  // Create a signal.
  dbus::Signal signal(cras::kCrasControlInterface, cras::kOutputMuteChanged);
  dbus::MessageWriter writer(&signal);
  writer.AppendBool(kSystemMuteOn);
  writer.AppendBool(kUserMuteOn);

  // Set expectations.
  MockObserver observer;
  EXPECT_CALL(observer, OutputMuteChanged(kUserMuteOn)).Times(1);

  // Add the observer.
  client()->AddObserver(&observer);

  // Run the signal callback.
  SendOutputMuteChangedSignal(&signal);

  // Remove the observer.
  client()->RemoveObserver(&observer);

  EXPECT_CALL(observer, OutputMuteChanged(_)).Times(0);

  // Run the signal callback again and make sure the observer isn't called.
  SendOutputMuteChangedSignal(&signal);

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

TEST_F(CrasAudioClientTest, InputMuteChanged) {
  const bool kInputMuteOn = true;
  // Create a signal.
  dbus::Signal signal(cras::kCrasControlInterface, cras::kInputMuteChanged);
  dbus::MessageWriter writer(&signal);
  writer.AppendBool(kInputMuteOn);

  // Set expectations.
  MockObserver observer;
  EXPECT_CALL(observer, InputMuteChanged(kInputMuteOn)).Times(1);

  // Add the observer.
  client()->AddObserver(&observer);

  // Run the signal callback.
  SendInputMuteChangedSignal(&signal);

  // Remove the observer.
  client()->RemoveObserver(&observer);

  EXPECT_CALL(observer, InputMuteChanged(_)).Times(0);

  // Run the signal callback again and make sure the observer isn't called.
  SendInputMuteChangedSignal(&signal);

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

TEST_F(CrasAudioClientTest, HotwordTriggered) {
  dbus::Signal signal(cras::kCrasControlInterface, cras::kHotwordTriggered);
  dbus::MessageWriter writer(&signal);
  writer.AppendInt64(0);
  writer.AppendInt64(0);

  MockObserver observer;

  // Set expectations.
  EXPECT_CALL(observer, HotwordTriggered(_, _)).Times(1);

  // Add the observer.
  client()->AddObserver(&observer);

  // Run the signal callback.
  SendHotwordTriggeredSignal(&signal);

  // Remove the observer.
  client()->RemoveObserver(&observer);

  EXPECT_CALL(observer, HotwordTriggered(_, _)).Times(0);

  // Run the signal callback again and make sure the observer isn't called.
  SendHotwordTriggeredSignal(&signal);

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

TEST_F(CrasAudioClientTest, NumberOfActiveStreamsChanged) {
  dbus::Signal signal(cras::kCrasControlInterface,
                      cras::kNumberOfActiveStreamsChanged);
  MockObserver observer;
  EXPECT_CALL(observer, NumberOfActiveStreamsChanged()).Times(1);

  client()->AddObserver(&observer);

  SendNumberOfActiveStreamsChangedSignal(&signal);

  client()->RemoveObserver(&observer);

  EXPECT_CALL(observer, NumberOfActiveStreamsChanged()).Times(0);

  // Run the signal callback again and make sure the observer isn't called.
  SendNumberOfActiveStreamsChangedSignal(&signal);

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

TEST_F(CrasAudioClientTest, NumberOfInputStreamsWithPermissionChanged) {
  dbus::Signal signal(cras::kCrasControlInterface,
                      cras::kNumberOfInputStreamsWithPermissionChanged);
  dbus::MessageWriter writer(&signal);
  base::flat_map<std::string, uint32_t> num_input_streams = {
      {"CRAS_CLIENT_TYPE_CHROME", 1}, {"CRAS_CLIENT_TYPE_CROSVM", 0}};
  for (auto& it : num_input_streams) {
    dbus::MessageWriter sub_writer(nullptr);
    dbus::MessageWriter entry_writer(nullptr);
    writer.OpenArray("{sv}", &sub_writer);
    sub_writer.OpenDictEntry(&entry_writer);
    entry_writer.AppendString(cras::kClientType);
    entry_writer.AppendVariantOfString(it.first);
    sub_writer.CloseContainer(&entry_writer);
    sub_writer.OpenDictEntry(&entry_writer);
    entry_writer.AppendString(cras::kNumStreamsWithPermission);
    entry_writer.AppendVariantOfUint32(it.second);
    sub_writer.CloseContainer(&entry_writer);
    writer.CloseContainer(&sub_writer);
  }

  MockObserver observer;
  EXPECT_CALL(observer,
              NumberOfInputStreamsWithPermissionChanged(num_input_streams))
      .Times(1);

  client()->AddObserver(&observer);

  SendNumberOfInputStreamsWithPermissionChangedSignal(&signal);

  client()->RemoveObserver(&observer);

  EXPECT_CALL(observer,
              NumberOfInputStreamsWithPermissionChanged(num_input_streams))
      .Times(0);

  // Run the signal callback again and make sure the observer isn't called.
  SendNumberOfInputStreamsWithPermissionChangedSignal(&signal);

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

TEST_F(CrasAudioClientTest, BluetoothBatteryChanged) {
  const std::string address = "11:22:33:44:55:66";
  const uint32_t level = 82;
  dbus::Signal signal(cras::kCrasControlInterface,
                      cras::kBluetoothBatteryChanged);
  dbus::MessageWriter writer(&signal);
  writer.AppendString(address);
  writer.AppendUint32(level);
  MockObserver observer;
  EXPECT_CALL(observer, BluetoothBatteryChanged(address, level)).Times(1);

  client()->AddObserver(&observer);

  SendBluetoothBatteryChangedSignal(&signal);

  client()->RemoveObserver(&observer);

  EXPECT_CALL(observer, BluetoothBatteryChanged(_, _)).Times(0);

  // Run the signal callback again and make sure the observer isn't called.
  SendBluetoothBatteryChangedSignal(&signal);

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

TEST_F(CrasAudioClientTest, SurveyTrigger) {
  dbus::Signal signal(cras::kCrasControlInterface, cras::kSurveyTrigger);
  dbus::MessageWriter writer(&signal);
  dbus::MessageWriter sub_writer(nullptr);
  base::flat_map<std::string, std::string> survey_specific_data = {
      {"StreamType", "CRAS_STREAM_TYPE_DEFAULT"},
      {"ClientType", "CRAS_CLIENT_TYPE_CHROME"},
      {"NodeType", "USB_INTERNAL_SPEAKER"}};

  writer.OpenArray("{sv}", &sub_writer);
  for (auto& it : survey_specific_data) {
    dbus::MessageWriter entry_writer(nullptr);
    sub_writer.OpenDictEntry(&entry_writer);
    entry_writer.AppendString(it.first);
    entry_writer.AppendVariantOfString(it.second);
    sub_writer.CloseContainer(&entry_writer);
  }
  writer.CloseContainer(&sub_writer);

  MockObserver observer;
  EXPECT_CALL(observer, SurveyTriggered(survey_specific_data)).Times(1);

  client()->AddObserver(&observer);

  SendSurveyTriggerSignal(&signal);

  client()->RemoveObserver(&observer);

  EXPECT_CALL(observer, SurveyTriggered(survey_specific_data)).Times(0);

  // Run the signal callback again and make sure the observer isn't called.
  SendSurveyTriggerSignal(&signal);

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

TEST_F(CrasAudioClientTest, NumberOfNonChromeOutputStreamsChanged) {
  dbus::Signal signal(cras::kCrasControlInterface,
                      cras::kNumberOfNonChromeOutputStreamsChanged);
  MockObserver observer;
  EXPECT_CALL(observer, NumberOfNonChromeOutputStreamsChanged()).Times(1);
  client()->AddObserver(&observer);
  SendNumberOfNonChromeOutputStreamsChangedSignal(&signal);
  client()->RemoveObserver(&observer);

  EXPECT_CALL(observer, NumberOfNonChromeOutputStreamsChanged()).Times(0);
  // Run the signal callback again and make sure the observer isn't called.
  SendSpeakOnMuteDetectedSignal(&signal);

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

TEST_F(CrasAudioClientTest, SpeakOnMuteDetected) {
  dbus::Signal signal(cras::kCrasControlInterface, cras::kSpeakOnMuteDetected);
  MockObserver observer;
  EXPECT_CALL(observer, SpeakOnMuteDetected()).Times(1);

  client()->AddObserver(&observer);

  SendSpeakOnMuteDetectedSignal(&signal);

  client()->RemoveObserver(&observer);

  EXPECT_CALL(observer, SpeakOnMuteDetected()).Times(0);

  // Run the signal callback again and make sure the observer isn't called.
  SendSpeakOnMuteDetectedSignal(&signal);

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

TEST_F(CrasAudioClientTest, EwmaPowerReported) {
  const double kPower = 0.5;

  dbus::Signal signal(cras::kCrasControlInterface, "EwmaPowerReported");
  dbus::MessageWriter writer(&signal);
  writer.AppendDouble(kPower);

  MockObserver observer;
  EXPECT_CALL(observer, EwmaPowerReported(kPower)).Times(1);
  client()->AddObserver(&observer);
  SendEwmaPowerReportedSignal(&signal);
  client()->RemoveObserver(&observer);

  EXPECT_CALL(observer, EwmaPowerReported(kPower)).Times(0);
  // Run the signal callback again and make sure the observer isn't called.
  SendEwmaPowerReportedSignal(&signal);

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

TEST_F(CrasAudioClientTest, NumStreamIgnoreUiGainsChanged) {
  const int32_t kNumStream = 1;

  dbus::Signal signal(cras::kCrasControlInterface,
                      cras::kNumStreamIgnoreUiGainsChanged);
  dbus::MessageWriter writer(&signal);
  writer.AppendInt32(kNumStream);

  MockObserver observer;
  EXPECT_CALL(observer, NumStreamIgnoreUiGains(kNumStream)).Times(1);

  client()->AddObserver(&observer);

  SendNumStreamIgnoreUiGainsSignal(&signal);

  client()->RemoveObserver(&observer);

  EXPECT_CALL(observer, NumStreamIgnoreUiGains(kNumStream)).Times(0);

  // Run the signal callback again and make sure the observer isn't called.
  SendNumStreamIgnoreUiGainsSignal(&signal);

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

TEST_F(CrasAudioClientTest, NumberOfArcStreamsChanged) {
  dbus::Signal signal(cras::kCrasControlInterface,
                      cras::kNumberOfArcStreamsChanged);
  MockObserver observer;
  EXPECT_CALL(observer, NumberOfArcStreamsChanged()).Times(1);
  client()->AddObserver(&observer);
  SendNumberOfArcStreamsChangedSignal(&signal);
  client()->RemoveObserver(&observer);

  EXPECT_CALL(observer, NumberOfArcStreamsChanged()).Times(0);
  // Run the signal callback again and make sure the observer isn't called.
  SendNumberOfArcStreamsChangedSignal(&signal);

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

TEST_F(CrasAudioClientTest, SidetoneSupportedChanged) {
  const bool kSupported = true;

  dbus::Signal signal(cras::kCrasControlInterface, "SidetoneSupportedChanged");
  dbus::MessageWriter writer(&signal);
  writer.AppendBool(kSupported);

  MockObserver observer;
  EXPECT_CALL(observer, SidetoneSupportedChanged(kSupported)).Times(1);
  client()->AddObserver(&observer);
  SendSidetoneSupportedChangedSignal(&signal);
  client()->RemoveObserver(&observer);

  EXPECT_CALL(observer, SidetoneSupportedChanged(kSupported)).Times(0);
  // Run the signal callback again and make sure the observer isn't called.
  SendSidetoneSupportedChangedSignal(&signal);

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

TEST_F(CrasAudioClientTest, NodesChanged) {
  // Create a signal.
  dbus::Signal signal(cras::kCrasControlInterface, cras::kNodesChanged);
  // Set expectations.
  MockObserver observer;
  EXPECT_CALL(observer, NodesChanged()).Times(1);

  // Add the observer.
  client()->AddObserver(&observer);

  // Run the signal callback.
  SendNodesChangedSignal(&signal);

  // Remove the observer.
  client()->RemoveObserver(&observer);

  EXPECT_CALL(observer, NodesChanged()).Times(0);

  // Run the signal callback again and make sure the observer isn't called.
  SendNodesChangedSignal(&signal);

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

TEST_F(CrasAudioClientTest, ActiveOutputNodeChanged) {
  const uint64_t kNodeId = 10002;
  // Create a signal.
  dbus::Signal signal(cras::kCrasControlInterface,
                      cras::kActiveOutputNodeChanged);
  dbus::MessageWriter writer(&signal);
  writer.AppendUint64(kNodeId);

  // Set expectations.
  MockObserver observer;
  EXPECT_CALL(observer, ActiveOutputNodeChanged(kNodeId)).Times(1);

  // Add the observer.
  client()->AddObserver(&observer);

  // Run the signal callback.
  SendActiveOutputNodeChangedSignal(&signal);

  // Remove the observer.
  client()->RemoveObserver(&observer);
  EXPECT_CALL(observer, ActiveOutputNodeChanged(_)).Times(0);

  // Run the signal callback again and make sure the observer isn't called.
  SendActiveOutputNodeChangedSignal(&signal);

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

TEST_F(CrasAudioClientTest, ActiveInputNodeChanged) {
  const uint64_t kNodeId = 20002;
  // Create a signal.
  dbus::Signal signal(cras::kCrasControlInterface,
                      cras::kActiveInputNodeChanged);
  dbus::MessageWriter writer(&signal);
  writer.AppendUint64(kNodeId);

  // Set expectations.
  MockObserver observer;
  EXPECT_CALL(observer, ActiveInputNodeChanged(kNodeId)).Times(1);

  // Add the observer.
  client()->AddObserver(&observer);

  // Run the signal callback.
  SendActiveInputNodeChangedSignal(&signal);

  // Remove the observer.
  client()->RemoveObserver(&observer);
  EXPECT_CALL(observer, ActiveInputNodeChanged(_)).Times(0);

  // Run the signal callback again and make sure the observer isn't called.
  SendActiveInputNodeChangedSignal(&signal);

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

TEST_F(CrasAudioClientTest, OutputNodeVolumeChanged) {
  const uint64_t kNodeId = 20003;
  const int32_t volume = 82;
  // Create a signal
  dbus::Signal signal(cras::kCrasControlInterface,
                      cras::kOutputNodeVolumeChanged);
  dbus::MessageWriter writer(&signal);
  writer.AppendUint64(kNodeId);
  writer.AppendInt32(volume);

  // Set expectations
  MockObserver observer;
  EXPECT_CALL(observer, OutputNodeVolumeChanged(kNodeId, volume)).Times(1);

  // Add the observer.
  client()->AddObserver(&observer);

  // Run the signal callback.
  SendOutputNodeVolumeChangedSignal(&signal);

  // Remove the observer.
  client()->RemoveObserver(&observer);
  EXPECT_CALL(observer, OutputNodeVolumeChanged(_, _)).Times(0);

  // Run the signal callback again and make sure the observer isn't called.
  SendOutputNodeVolumeChangedSignal(&signal);

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

TEST_F(CrasAudioClientTest, InputNodeGainChanged) {
  const uint64_t kNodeId = 20003;
  const int32_t gain = 82;
  // Create a signal
  dbus::Signal signal(cras::kCrasControlInterface, cras::kInputNodeGainChanged);
  dbus::MessageWriter writer(&signal);
  writer.AppendUint64(kNodeId);
  writer.AppendInt32(gain);

  // Set expectations
  MockObserver observer;
  EXPECT_CALL(observer, InputNodeGainChanged(kNodeId, gain)).Times(1);

  // Add the observer.
  client()->AddObserver(&observer);

  // Run the signal callback.
  SendInputNodeGainChangedSignal(&signal);

  // Remove the observer.
  client()->RemoveObserver(&observer);
  EXPECT_CALL(observer, InputNodeGainChanged(_, _)).Times(0);

  // Run the signal callback again and make sure the observer isn't called.
  SendInputNodeGainChangedSignal(&signal);

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

TEST_F(CrasAudioClientTest, GetNodes) {
  // Create the expected value.
  AudioNodeList expected_node_list{kInternalSpeaker, kInternalMic};

  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
  dbus::MessageWriter writer(response.get());
  WriteNodesToResponse(expected_node_list, &writer);

  // Set expectations.
  PrepareForMethodCall(cras::kGetNodes, base::BindRepeating(&ExpectNoArgument),
                       response.get());
  // Call method.
  bool called = false;
  client()->GetNodes(
      base::BindOnce(&ExpectAudioNodeListResult, &called, expected_node_list));
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(called);
}

TEST_F(CrasAudioClientTest, GetNodesV2) {
  // Create the expected value.
  AudioNodeList expected_node_list{kInternalSpeakerV2, kInternalMicV2};

  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
  dbus::MessageWriter writer(response.get());
  WriteNodesToResponse(expected_node_list, &writer);

  // Set expectations.
  PrepareForMethodCall(cras::kGetNodes, base::BindRepeating(&ExpectNoArgument),
                       response.get());

  // Call method.
  bool called = false;
  client()->GetNodes(
      base::BindOnce(&ExpectAudioNodeListResult, &called, expected_node_list));
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(called);
}

TEST_F(CrasAudioClientTest, SetOutputNodeVolume) {
  const uint64_t kNodeId = 10003;
  const int32_t kVolume = 80;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(
      cras::kSetOutputNodeVolume,
      base::BindRepeating(&ExpectUint64AndInt32Arguments, kNodeId, kVolume),
      response.get());
  // Call method.
  client()->SetOutputNodeVolume(kNodeId, kVolume);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetOutputUserMute) {
  const bool kUserMuteOn = true;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(cras::kSetOutputUserMute,
                       base::BindRepeating(&ExpectBoolArgument, kUserMuteOn),
                       response.get());
  // Call method.
  client()->SetOutputUserMute(kUserMuteOn);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetInputNodeGain) {
  const uint64_t kNodeId = 20003;
  const int32_t kInputGain = 2;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(
      cras::kSetInputNodeGain,
      base::BindRepeating(&ExpectUint64AndInt32Arguments, kNodeId, kInputGain),
      response.get());
  // Call method.
  client()->SetInputNodeGain(kNodeId, kInputGain);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetInputMute) {
  const bool kInputMuteOn = true;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(cras::kSetInputMute,
                       base::BindRepeating(&ExpectBoolArgument, kInputMuteOn),
                       response.get());
  // Call method.
  client()->SetInputMute(kInputMuteOn);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetNoiseCancellationEnabled) {
  const bool kNoiseCancellationOn = true;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(
      cras::kSetNoiseCancellationEnabled,
      base::BindRepeating(&ExpectBoolArgument, kNoiseCancellationOn),
      response.get());
  // Call method.
  client()->SetNoiseCancellationEnabled(kNoiseCancellationOn);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetStyleTransferEnabled) {
  const bool kStyleTransferOn = true;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(
      cras::kSetStyleTransferEnabled,
      base::BindRepeating(&ExpectBoolArgument, kStyleTransferOn),
      response.get());
  // Call method.
  client()->SetStyleTransferEnabled(kStyleTransferOn);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetHfpMicSrEnabled) {
  const bool kHfpMicSrOn = true;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(cras::kSetHfpMicSrEnabled,
                       base::BindRepeating(&ExpectBoolArgument, kHfpMicSrOn),
                       response.get());
  // Call method.
  client()->SetHfpMicSrEnabled(kHfpMicSrOn);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetActiveOutputNode) {
  const uint64_t kNodeId = 10004;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(cras::kSetActiveOutputNode,
                       base::BindRepeating(&ExpectUint64Argument, kNodeId),
                       response.get());
  // Call method.
  client()->SetActiveOutputNode(kNodeId);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetActiveInputNode) {
  const uint64_t kNodeId = 20004;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(cras::kSetActiveInputNode,
                       base::BindRepeating(&ExpectUint64Argument, kNodeId),
                       response.get());
  // Call method.
  client()->SetActiveInputNode(kNodeId);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, AddActiveInputNode) {
  const uint64_t kNodeId = 20005;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(cras::kAddActiveInputNode,
                       base::BindRepeating(&ExpectUint64Argument, kNodeId),
                       response.get());
  // Call method.
  client()->AddActiveInputNode(kNodeId);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, RemoveActiveInputNode) {
  const uint64_t kNodeId = 20006;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(cras::kRemoveActiveInputNode,
                       base::BindRepeating(&ExpectUint64Argument, kNodeId),
                       response.get());
  // Call method.
  client()->RemoveActiveInputNode(kNodeId);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, AddActiveOutputNode) {
  const uint64_t kNodeId = 10005;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(cras::kAddActiveOutputNode,
                       base::BindRepeating(&ExpectUint64Argument, kNodeId),
                       response.get());
  // Call method.
  client()->AddActiveOutputNode(kNodeId);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, RemoveActiveOutputNode) {
  const uint64_t kNodeId = 10006;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(cras::kRemoveActiveOutputNode,
                       base::BindRepeating(&ExpectUint64Argument, kNodeId),
                       response.get());
  // Call method.
  client()->RemoveActiveOutputNode(kNodeId);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SwapLeftRight) {
  const uint64_t kNodeId = 10007;
  const bool kSwap = true;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(
      cras::kSwapLeftRight,
      base::BindRepeating(&ExpectUint64AndBoolArguments, kNodeId, kSwap),
      response.get());
  // Call method.
  client()->SwapLeftRight(kNodeId, kSwap);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetGlobalOutputChannelRemix) {
  const int32_t kChannels = 2;
  const std::vector<double> kMixer = {0, 0.1, 0.5, 1};
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(
      cras::kSetGlobalOutputChannelRemix,
      base::BindRepeating(&ExpectInt32AndArrayOfDoublesArguments, kChannels,
                          kMixer),
      response.get());

  // Call method.
  client()->SetGlobalOutputChannelRemix(kChannels, kMixer);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetPlayerPlaybackStatus) {
  const std::string kStatus = "paused";
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(cras::kSetPlayerPlaybackStatus,
                       base::BindRepeating(&ExpectStringArgument, kStatus),
                       response.get());

  // Call method.
  client()->SetPlayerPlaybackStatus(kStatus);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetPlayerIdentity) {
  const std::string kIdentity = "Chrome Player";
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(cras::kSetPlayerIdentity,
                       base::BindRepeating(&ExpectStringArgument, kIdentity),
                       response.get());

  // Call method.
  client()->SetPlayerIdentity(kIdentity);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetPlayerPosition) {
  const int64_t kPosition = 2020224;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(cras::kSetPlayerPosition,
                       base::BindRepeating(&ExpectInt64Argument, kPosition),
                       response.get());

  // Call method.
  client()->SetPlayerPosition(kPosition);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetPlayerDuration) {
  const int64_t kDuration = 20200302;
  const std::map<std::string, int64_t> kMetadata = {{"length", kDuration}};
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(
      cras::kSetPlayerMetadata,
      base::BindRepeating(&ExpectArrayOfDictOfStringAndVariantInt64Arguments,
                          kMetadata),
      response.get());

  // Call method.
  client()->SetPlayerDuration(kDuration);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetPlayerMetadata) {
  const std::string kTitle = "Chrome Metadata Title";
  const std::string kAlbum = "Chrome Metadata Album";
  const std::string kArtist = "Chrome Metadata Artist";
  const std::map<std::string, std::string> kMetadata = {
      {"title", kTitle}, {"album", kAlbum}, {"artist", kArtist}};
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(
      cras::kSetPlayerMetadata,
      base::BindRepeating(&ExpectArrayOfDictOfStringAndVariantStringArguments,
                          kMetadata),
      response.get());

  // Call method.
  client()->SetPlayerMetadata(kMetadata);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, ResendBluetoothBattery) {
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(cras::kResendBluetoothBattery,
                       base::BindRepeating(&ExpectNoArgument), response.get());

  // Call method.
  client()->ResendBluetoothBattery();
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetSpeakOnMuteDetection) {
  const bool kSpeakOnMuteDetectionOn = true;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(
      cras::kSetSpeakOnMuteDetection,
      base::BindRepeating(&ExpectBoolArgument, kSpeakOnMuteDetectionOn),
      response.get());
  // Call method.
  client()->SetSpeakOnMuteDetection(kSpeakOnMuteDetectionOn);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_F(CrasAudioClientTest, SetForceRespectUiGainsEnabled) {
  const bool kForceRespectUiGainsOn = true;
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  // Set expectations.
  PrepareForMethodCall(
      cras::kSetForceRespectUiGains,
      base::BindRepeating(&ExpectBoolArgument, kForceRespectUiGainsOn),
      response.get());
  // Call method.
  client()->SetForceRespectUiGains(kForceRespectUiGainsOn);
  // Run the message loop.
  base::RunLoop().RunUntilIdle();
}

}  // namespace ash