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

#include "third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_audio.h"

#include <algorithm>
#include <cmath>
#include <memory>
#include <string>
#include <utility>

#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "base/types/optional_util.h"
#include "build/build_config.h"
#include "media/base/audio_parameters.h"
#include "media/base/media_switches.h"
#include "media/media_buildflags.h"
#include "media/webrtc/constants.h"
#include "media/webrtc/webrtc_features.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/mediastream/media_stream_controls.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-blink-forward.h"
#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/modules/mediastream/local_media_stream_audio_source.h"
#include "third_party/blink/renderer/modules/mediastream/media_constraints.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_constraints_util.h"
#include "third_party/blink/renderer/modules/mediastream/mock_constraint_factory.h"
#include "third_party/blink/renderer/modules/mediastream/processed_local_audio_source.h"
#include "third_party/blink/renderer/modules/webrtc/webrtc_audio_device_impl.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_audio_source.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_audio_track.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_component_impl.h"
#include "third_party/blink/renderer/platform/testing/io_task_runner_testing_platform_support.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include "third_party/webrtc/rtc_base/ref_counted_object.h"

namespace blink {

using blink::AudioCaptureSettings;
using blink::AudioProcessingProperties;
using ProcessingType = AudioCaptureSettings::ProcessingType;

namespace {

using BoolSetFunction = void (blink::BooleanConstraint::*)(bool);
using StringSetFunction =
    void (blink::StringConstraint::*)(const blink::WebString&);
using BoolOrStringSetBooleanFunction =
    void (blink::BooleanOrStringConstraint::*)(bool);
using MockFactoryAccessor =
    MediaTrackConstraintSetPlatform& (blink::MockConstraintFactory::*)();

const std::array<BoolSetFunction, 2> kBoolSetFunctions = {
    &blink::BooleanConstraint::SetExact,
    &blink::BooleanConstraint::SetIdeal,
};

const std::array<BoolOrStringSetBooleanFunction, 2>
    kBoolOrStringSetBooleanFunctions = {
        &blink::BooleanOrStringConstraint::SetExactBoolean,
        &blink::BooleanOrStringConstraint::SetIdealBoolean,
};

const std::array<MockFactoryAccessor, 2> kFactoryAccessors = {
    &blink::MockConstraintFactory::basic,
    &blink::MockConstraintFactory::AddAdvanced};

const std::array<bool, 2> kBoolValues = {true, false};

const int kMinChannels = 1;

using AudioSettingsBoolMembers = Vector<bool (AudioCaptureSettings::*)() const>;
using AudioPropertiesBoolMembers = Vector<bool AudioProcessingProperties::*>;

template <typename T>
static bool Contains(const Vector<T>& vector, T value) {
  return std::ranges::contains(vector, value);
}

}  // namespace

class MediaStreamConstraintsUtilAudioTestBase : public SimTest {
 protected:
  void SetMediaStreamSource(const std::string& source) {}

  void ResetFactory() {
    constraint_factory_.Reset();
    constraint_factory_.basic().media_stream_source.SetExact(
        String::FromUtf8(GetMediaStreamSource()));
  }

  // If not overridden, this function will return device capture by default.
  virtual std::string GetMediaStreamSource() { return std::string(); }
  bool IsDeviceCapture() { return GetMediaStreamSource().empty(); }
  static AudioPropertiesBoolMembers GetAudioProcessingProperties() {
    return {&AudioProcessingProperties::auto_gain_control,
            &AudioProcessingProperties::noise_suppression};
  }

  blink::mojom::MediaStreamType GetMediaStreamType() {
    std::string media_source = GetMediaStreamSource();
    if (media_source.empty())
      return blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE;
    else if (media_source == blink::kMediaStreamSourceTab)
      return blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE;
    return blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE;
  }

  std::unique_ptr<ProcessedLocalAudioSource> GetProcessedLocalAudioSource(
      const AudioProcessingProperties& properties,
      bool disable_local_echo,
      bool render_to_associated_sink,
      int effects,
      int num_requested_channels) {
    blink::MediaStreamDevice device;
    device.id = "processed_source";
    device.type = GetMediaStreamType();
    if (render_to_associated_sink)
      device.matched_output_device_id = std::string("some_device_id");
    device.input.set_effects(effects);

    return std::make_unique<ProcessedLocalAudioSource>(
        *MainFrame().GetFrame(), device, disable_local_echo,
        MediaStreamAudioProcessingLayout(properties, effects,
                                         num_requested_channels),
        base::NullCallback(),
        blink::scheduler::GetSingleThreadTaskRunnerForTesting());
  }

  std::unique_ptr<ProcessedLocalAudioSource> GetProcessedLocalAudioSource(
      const AudioProcessingProperties& properties,
      bool disable_local_echo,
      bool render_to_associated_sink) {
    return GetProcessedLocalAudioSource(
        properties, disable_local_echo, render_to_associated_sink,
        media::AudioParameters::PlatformEffectsMask::NO_EFFECTS,
        1 /* num_requested_channels */);
  }

  std::unique_ptr<blink::LocalMediaStreamAudioSource>
  GetLocalMediaStreamAudioSource(
      bool enable_system_echo_canceller,
      bool disable_local_echo,
      bool render_to_associated_sink,
      const int* requested_buffer_size = nullptr) {
    blink::MediaStreamDevice device;
    device.type = GetMediaStreamType();

    int effects = 0;
    if (enable_system_echo_canceller)
      effects |= media::AudioParameters::ECHO_CANCELLER;
    device.input.set_effects(effects);

    if (render_to_associated_sink)
      device.matched_output_device_id = std::string("some_device_id");

    return std::make_unique<blink::LocalMediaStreamAudioSource>(
        /*blink::WebLocalFrame=*/nullptr, device, requested_buffer_size,
        /*disable_local_echo=*/disable_local_echo,
        MediaStreamAudioProcessingLayout::MakeForUnprocessedLocalSourceForTests(
            enable_system_echo_canceller, effects),
        blink::WebPlatformMediaStreamSource::ConstraintsRepeatingCallback(),
        blink::scheduler::GetSingleThreadTaskRunnerForTesting());
  }

  AudioCaptureSettings SelectSettings(
      bool is_full_reconfiguration_allowed = false,
      std::optional<AudioDeviceCaptureCapabilities> capabilities =
          std::nullopt) {
    MediaConstraints constraints = constraint_factory_.CreateMediaConstraints();
    if (capabilities) {
      return SelectSettingsAudioCapture(
          *capabilities, constraints, GetMediaStreamType(), is_full_reconfiguration_allowed);
    } else {
      return SelectSettingsAudioCapture(
          capabilities_, constraints, GetMediaStreamType(), is_full_reconfiguration_allowed);
    }
  }

  base::expected<Vector<blink::AudioCaptureSettings>, std::string>
  SelectEligibleSettings(bool is_full_reconfiguration_allowed = false) {
    MediaConstraints constraints = constraint_factory_.CreateMediaConstraints();
    return SelectEligibleSettingsAudioCapture(
        capabilities_, constraints, GetMediaStreamType(), is_full_reconfiguration_allowed);
  }

  void CheckBoolDefaultsDeviceCapture(
      const AudioSettingsBoolMembers& exclude_main_settings,
      const AudioPropertiesBoolMembers& exclude_audio_properties,
      const AudioCaptureSettings& result) {
    if (!Contains(exclude_main_settings,
                  &AudioCaptureSettings::disable_local_echo)) {
      EXPECT_TRUE(result.disable_local_echo());
    }
    if (!Contains(exclude_main_settings,
                  &AudioCaptureSettings::render_to_associated_sink)) {
      EXPECT_FALSE(result.render_to_associated_sink());
    }

    const auto& properties = result.audio_processing_properties();
    if (!Contains(exclude_audio_properties,
                  &AudioProcessingProperties::auto_gain_control)) {
      EXPECT_TRUE(properties.auto_gain_control);
    }
    if (!Contains(exclude_audio_properties,
                  &AudioProcessingProperties::noise_suppression)) {
      EXPECT_TRUE(properties.noise_suppression);
    }
  }

  void CheckBoolDefaultsContentCapture(
      const AudioSettingsBoolMembers& exclude_main_settings,
      const AudioPropertiesBoolMembers& exclude_audio_properties,
      const AudioCaptureSettings& result) {
    if (!Contains(exclude_main_settings,
                  &AudioCaptureSettings::disable_local_echo)) {
      EXPECT_EQ(GetMediaStreamSource() != blink::kMediaStreamSourceDesktop,
                result.disable_local_echo());
    }
    if (!Contains(exclude_main_settings,
                  &AudioCaptureSettings::render_to_associated_sink)) {
      EXPECT_FALSE(result.render_to_associated_sink());
    }

    const auto& properties = result.audio_processing_properties();
    if (!Contains(exclude_audio_properties,
                  &AudioProcessingProperties::auto_gain_control)) {
      EXPECT_FALSE(properties.auto_gain_control);
    }
    if (!Contains(exclude_audio_properties,
                  &AudioProcessingProperties::noise_suppression)) {
      EXPECT_FALSE(properties.noise_suppression);
    }
  }

  void CheckBoolDefaults(
      const AudioSettingsBoolMembers& exclude_main_settings,
      const AudioPropertiesBoolMembers& exclude_audio_properties,
      const AudioCaptureSettings& result) {
    if (IsDeviceCapture()) {
      CheckBoolDefaultsDeviceCapture(exclude_main_settings,
                                     exclude_audio_properties, result);
    } else {
      CheckBoolDefaultsContentCapture(exclude_main_settings,
                                      exclude_audio_properties, result);
    }
  }

  void CheckEchoCancellationModeDefault(const AudioCaptureSettings& result) {
    const auto& properties = result.audio_processing_properties();
    if (IsDeviceCapture()) {
      EXPECT_EQ(properties.echo_cancellation_mode,
                EchoCancellationMode::kBrowserDecides);
    } else {
      EXPECT_EQ(properties.echo_cancellation_mode,
                EchoCancellationMode::kDisabled);
    }
  }

  void CheckProcessingType(const AudioCaptureSettings& result) {
    ProcessingType expected_type = ProcessingType::kUnprocessed;
    const auto& properties = result.audio_processing_properties();
    bool properties_value = false;
    for (wtf_size_t i = 0; i < GetAudioProcessingProperties().size(); ++i) {
      properties_value |= properties.*GetAudioProcessingProperties()[i];
    }

    if (properties_value) {
      expected_type = ProcessingType::kApmProcessed;
    }

    // Finally, if the chosen echo cancellation type is either AEC3 or AEC2, the
    // only possible processing type to expect is kWebRtcProcessed.
    if (properties.echo_cancellation_mode ==
        EchoCancellationMode::kBrowserDecides) {
      expected_type = ProcessingType::kApmProcessed;
    }
    EXPECT_EQ(result.processing_type(), expected_type);
  }

  void CheckDevice(const AudioDeviceCaptureCapability& expected_device,
                   const AudioCaptureSettings& result) {
    EXPECT_EQ(expected_device.DeviceID().Utf8(), result.device_id());
  }

  void CheckDeviceDefaults(const AudioCaptureSettings& result) {
    if (IsDeviceCapture())
      CheckDevice(*default_device_, result);
    else
      EXPECT_TRUE(result.device_id().empty());
  }

  void CheckAllDefaults(
      const AudioSettingsBoolMembers& exclude_main_settings,
      const AudioPropertiesBoolMembers& exclude_audio_properties,
      const AudioCaptureSettings& result) {
    CheckProcessingType(result);
    CheckBoolDefaults(exclude_main_settings, exclude_audio_properties, result);
    CheckEchoCancellationModeDefault(result);
    CheckDeviceDefaults(result);
  }

  void CheckAudioProcessingPropertiesForIdealEchoCancellationMode(
      const AudioCaptureSettings& result) {
    const AudioProcessingProperties& properties =
        result.audio_processing_properties();

    EXPECT_EQ(EchoCancellationMode::kAll, properties.echo_cancellation_mode);
    EXPECT_TRUE(properties.auto_gain_control);
    EXPECT_TRUE(properties.noise_suppression);

    // The following are not audio processing.
    EXPECT_EQ(GetMediaStreamSource() != blink::kMediaStreamSourceDesktop,
              result.disable_local_echo());
    EXPECT_FALSE(result.render_to_associated_sink());
    CheckDevice(*system_echo_canceller_device_, result);
  }

  void CheckLatencyConstraint(const AudioDeviceCaptureCapability* device,
                              double min_latency,
                              double max_latency) {
    constraint_factory_.Reset();
    constraint_factory_.basic().device_id.SetExact(device->DeviceID());
    constraint_factory_.basic().echo_cancellation.SetExactBoolean(false);
    constraint_factory_.basic().latency.SetExact(0.0);
    auto result = SelectSettings();
    EXPECT_FALSE(result.HasValue());

    constraint_factory_.Reset();
    constraint_factory_.basic().device_id.SetExact(device->DeviceID());
    constraint_factory_.basic().echo_cancellation.SetExactBoolean(false);
    constraint_factory_.basic().latency.SetMin(max_latency + 0.001);
    result = SelectSettings();
    EXPECT_FALSE(result.HasValue());

    constraint_factory_.Reset();
    constraint_factory_.basic().device_id.SetExact(device->DeviceID());
    constraint_factory_.basic().echo_cancellation.SetExactBoolean(false);
    constraint_factory_.basic().latency.SetMax(min_latency - 0.001);
    result = SelectSettings();
    EXPECT_FALSE(result.HasValue());

    CheckLocalMediaStreamAudioSourceLatency(
        device, 0.001, min_latency * device->Parameters().sample_rate());
    CheckLocalMediaStreamAudioSourceLatency(
        device, 1.0, max_latency * device->Parameters().sample_rate());
  }

  void CheckLocalMediaStreamAudioSourceLatency(
      const AudioDeviceCaptureCapability* device,
      double requested_latency,
      int expected_buffer_size) {
    constraint_factory_.Reset();
    constraint_factory_.basic().device_id.SetExact(device->DeviceID());
    constraint_factory_.basic().echo_cancellation.SetExactBoolean(false);
    constraint_factory_.basic().latency.SetIdeal(requested_latency);
    auto result = SelectSettings();
    EXPECT_TRUE(result.HasValue());

    std::unique_ptr<blink::LocalMediaStreamAudioSource> local_source =
        GetLocalMediaStreamAudioSource(
            false /* enable_system_echo_canceller */,
            false /* disable_local_echo */,
            false /* render_to_associated_sink */,
            base::OptionalToPtr(result.requested_buffer_size()));
    EXPECT_EQ(local_source->GetAudioParameters().frames_per_buffer(),
              expected_buffer_size);
  }

  blink::MockConstraintFactory constraint_factory_;
  AudioDeviceCaptureCapabilities capabilities_;
  raw_ptr<const AudioDeviceCaptureCapability> default_device_ = nullptr;
  raw_ptr<const AudioDeviceCaptureCapability> system_echo_canceller_device_ =
      nullptr;
  raw_ptr<const AudioDeviceCaptureCapability> four_channels_device_ = nullptr;
  raw_ptr<const AudioDeviceCaptureCapability> variable_latency_device_ =
      nullptr;
  std::unique_ptr<ProcessedLocalAudioSource> system_echo_canceller_source_;
  const Vector<media::Point> kMicPositions = {{8, 8, 8}, {4, 4, 4}};

 private:
  ScopedTestingPlatformSupport<IOTaskRunnerTestingPlatformSupport> platform_;
};

class MediaStreamConstraintsUtilAudioTest
    : public MediaStreamConstraintsUtilAudioTestBase,
      public testing::WithParamInterface<std::string> {
 public:
  void SetUp() override {
    MediaStreamConstraintsUtilAudioTestBase::SetUp();
    ResetFactory();
    if (IsDeviceCapture()) {
      media::AudioParameters default_params(
          media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
          media::ChannelLayoutConfig::Stereo(),
          media::AudioParameters::kAudioCDSampleRate, 1000);
      default_params.set_effects(
          media::AudioParameters::VOICE_ISOLATION_SUPPORTED);
      capabilities_.emplace_back("default_device", "fake_group1",
                                 default_params);

      media::AudioParameters system_echo_canceller_parameters(
          media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
          media::ChannelLayoutConfig::Stereo(),
          media::AudioParameters::kAudioCDSampleRate, 1000);
      system_echo_canceller_parameters.set_effects(
          media::AudioParameters::ECHO_CANCELLER);
      capabilities_.emplace_back("system_echo_canceller_device", "fake_group2",
                                 system_echo_canceller_parameters);

      capabilities_.emplace_back(
          "4_channels_device", "fake_group3",
          media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
                                 media::ChannelLayoutConfig::FromLayout<
                                     media::CHANNEL_LAYOUT_4_0>(),
                                 media::AudioParameters::kAudioCDSampleRate,
                                 1000));

      capabilities_.emplace_back(
          "8khz_sample_rate_device", "fake_group4",
          media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
                                 media::ChannelLayoutConfig::Stereo(),
                                 webrtc::AudioProcessing::kSampleRate8kHz,
                                 1000));

      capabilities_.emplace_back(
          "variable_latency_device", "fake_group5",
          media::AudioParameters(
              media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
              media::ChannelLayoutConfig::Stereo(),
              media::AudioParameters::kAudioCDSampleRate, 512,
              media::AudioParameters::HardwareCapabilities(128, 4096)));

      default_device_ = &capabilities_[0];
      system_echo_canceller_device_ = &capabilities_[1];
      four_channels_device_ = &capabilities_[2];
      variable_latency_device_ = &capabilities_[4];
    } else {
      // For content capture, use a single capability that admits all possible
      // settings.
      capabilities_.emplace_back();
    }
  }

  std::string GetMediaStreamSource() override { return GetParam(); }
};

enum class ChromeWideAecExperiment { kDisabled, kEnabled };

class MediaStreamConstraintsRemoteAPMTest
    : public MediaStreamConstraintsUtilAudioTestBase,
      public testing::WithParamInterface<
          std::tuple<std::string, ChromeWideAecExperiment>> {
 protected:
  std::string GetMediaStreamSource() override {
    return std::get<0>(GetParam());
  }

  ChromeWideAecExperiment GetChromeWideAecExperiment() {
    return std::get<1>(GetParam());
  }

  testing::Message GetMessageForScopedTrace() {
    std::string experiment_string;
    switch (GetChromeWideAecExperiment()) {
      case ChromeWideAecExperiment::kDisabled:
        experiment_string = "disabled";
        break;
      case ChromeWideAecExperiment::kEnabled:
        experiment_string = "enabled";
        break;
    }
    return testing::Message()
           << "GetMediaStreamSource()=\"" << GetMediaStreamSource()
           << "\", GetChromeWideAecExperiment()=" << experiment_string;
  }

  // Indicates where and how audio processing is applied.
  enum class ApmLocation {
    kProcessedLocalAudioSource,
    kAudioService,
    kAudioServiceAvoidResampling
  };

  ApmLocation GetApmLocation() {
    if (GetMediaStreamType() !=
        mojom::blink::MediaStreamType::DEVICE_AUDIO_CAPTURE) {
      // Non-mic input sources cannot run APM in the audio service:
      // https://crbug.com/1328012
      return ApmLocation::kProcessedLocalAudioSource;
    }

    switch (GetChromeWideAecExperiment()) {
      case ChromeWideAecExperiment::kDisabled:
        return ApmLocation::kProcessedLocalAudioSource;
      case ChromeWideAecExperiment::kEnabled:
        return ApmLocation::kAudioService;
    }
    NOTREACHED();
  }

 private:
  void SetUp() override {
    MediaStreamConstraintsUtilAudioTestBase::SetUp();

#if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
    switch (GetChromeWideAecExperiment()) {
      case ChromeWideAecExperiment::kDisabled:
        scoped_feature_list_.InitAndDisableFeature(
            media::kChromeWideEchoCancellation);
        break;
      case ChromeWideAecExperiment::kEnabled:
        scoped_feature_list_.InitAndEnableFeature(
            media::kChromeWideEchoCancellation);
        break;
    }
#endif

    // Setup the capabilities.
    ResetFactory();
    capabilities_.emplace_back(
        "default_device", "fake_group1",
        media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
                               media::ChannelLayoutConfig::Stereo(),
                               media::AudioParameters::kAudioCDSampleRate,
                               1000));
    default_device_ = &capabilities_[0];
  }

  base::test::ScopedFeatureList scoped_feature_list_;
};

// The Unconstrained test checks the default selection criteria.
TEST_P(MediaStreamConstraintsUtilAudioTest, Unconstrained) {
  auto result = SelectSettings();

  // All settings should have default values.
  EXPECT_TRUE(result.HasValue());
  CheckAllDefaults(AudioSettingsBoolMembers(), AudioPropertiesBoolMembers(),
                   result);
}

// This test checks all possible ways to set boolean constraints (except
// echo cancellation constraints, which are not mapped 1:1 to output audio
// processing properties).
TEST_P(MediaStreamConstraintsUtilAudioTest, SingleBoolConstraint) {
  AudioSettingsBoolMembers kMainSettings = {
      &AudioCaptureSettings::disable_local_echo,
      &AudioCaptureSettings::render_to_associated_sink};

  const Vector<blink::BooleanConstraint MediaTrackConstraintSetPlatform::*>
      kMainBoolConstraints = {
          &MediaTrackConstraintSetPlatform::disable_local_echo,
          &MediaTrackConstraintSetPlatform::render_to_associated_sink};

  ASSERT_EQ(kMainSettings.size(), kMainBoolConstraints.size());
  for (auto set_function : kBoolSetFunctions) {
    for (auto accessor : kFactoryAccessors) {
      // Ideal advanced is ignored by the SelectSettings algorithm.
      // Using array elements instead of pointer values due to the comparison
      // failing on some build configurations.
      if (set_function == kBoolSetFunctions[1] &&
          accessor == kFactoryAccessors[1]) {
        continue;
      }
      for (wtf_size_t i = 0; i < kMainSettings.size(); ++i) {
        for (bool value : kBoolValues) {
          ResetFactory();
          (((constraint_factory_.*accessor)().*kMainBoolConstraints[i]).*
           set_function)(value);
          auto result = SelectSettings();
          EXPECT_TRUE(result.HasValue());
          EXPECT_EQ(value, (result.*kMainSettings[i])());
          CheckAllDefaults({kMainSettings[i]}, AudioPropertiesBoolMembers(),
                           result);
        }
      }
    }
  }

  const Vector<blink::BooleanConstraint MediaTrackConstraintSetPlatform::*>
      kAudioProcessingConstraints = {
          &MediaTrackConstraintSetPlatform::auto_gain_control,
          &MediaTrackConstraintSetPlatform::noise_suppression,
      };

  ASSERT_EQ(GetAudioProcessingProperties().size(),
            kAudioProcessingConstraints.size());
  for (auto set_function : kBoolSetFunctions) {
    for (auto accessor : kFactoryAccessors) {
      // Ideal advanced is ignored by the SelectSettings algorithm.
      // Using array elements instead of pointer values due to the comparison
      // failing on some build configurations.
      if (set_function == kBoolSetFunctions[1] &&
          accessor == kFactoryAccessors[1]) {
        continue;
      }
      for (wtf_size_t i = 0; i < GetAudioProcessingProperties().size(); ++i) {
        for (bool value : kBoolValues) {
          ResetFactory();
          (((constraint_factory_.*accessor)().*kAudioProcessingConstraints[i]).*
           set_function)(value);
          auto result = SelectSettings();
          EXPECT_TRUE(result.HasValue());
          EXPECT_EQ(value, result.audio_processing_properties().*
                               GetAudioProcessingProperties()[i]);
          CheckAllDefaults(AudioSettingsBoolMembers(),
                           {GetAudioProcessingProperties()[i]}, result);
        }
      }
    }
  }
}

TEST_P(MediaStreamConstraintsUtilAudioTest, SampleSize) {
  ResetFactory();
  constraint_factory_.basic().sample_size.SetExact(16);
  auto result = SelectSettings();
  EXPECT_TRUE(result.HasValue());

  ResetFactory();
  constraint_factory_.basic().sample_size.SetExact(0);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Only set a min value for the constraint.
  ResetFactory();
  constraint_factory_.basic().sample_size.SetMin(16);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());

  ResetFactory();
  constraint_factory_.basic().sample_size.SetMin(17);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Only set a max value for the constraint.
  ResetFactory();
  constraint_factory_.basic().sample_size.SetMax(16);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());

  ResetFactory();
  constraint_factory_.basic().sample_size.SetMax(15);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Define a bounded range for the constraint.
  ResetFactory();
  constraint_factory_.basic().sample_size.SetMin(10);
  constraint_factory_.basic().sample_size.SetMax(20);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());

  ResetFactory();
  constraint_factory_.basic().sample_size.SetMin(-10);
  constraint_factory_.basic().sample_size.SetMax(10);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  ResetFactory();
  constraint_factory_.basic().sample_size.SetMin(20);
  constraint_factory_.basic().sample_size.SetMax(30);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Test ideal constraints.
  ResetFactory();
  constraint_factory_.basic().sample_size.SetIdeal(16);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());

  ResetFactory();
  constraint_factory_.basic().sample_size.SetIdeal(0);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
}

TEST_P(MediaStreamConstraintsUtilAudioTest, Channels) {
  int channel_count = kMinChannels;
  AudioCaptureSettings result;

  // Test set exact channelCount.
  for (; channel_count <= media::limits::kMaxChannels; ++channel_count) {
    ResetFactory();
    constraint_factory_.basic().channel_count.SetExact(channel_count);
    result = SelectSettings();

    if (!IsDeviceCapture()) {
      // The source capture configured above is actually using a channel count
      // set to 2 channels.
      if (channel_count <= 2)
        EXPECT_TRUE(result.HasValue());
      else
        EXPECT_FALSE(result.HasValue());
      continue;
    }

    if (channel_count == 3 || channel_count > 4) {
      EXPECT_FALSE(result.HasValue());
      continue;
    }

    EXPECT_TRUE(result.HasValue());
    if (channel_count == 4)
      EXPECT_EQ(result.device_id(), "4_channels_device");
    else
      EXPECT_EQ(result.device_id(), "default_device");
  }

  // Only set a min value for the constraint.
  ResetFactory();
  constraint_factory_.basic().channel_count.SetMin(media::limits::kMaxChannels +
                                                   1);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  constraint_factory_.basic().channel_count.SetMin(kMinChannels);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());

  // Only set a max value for the constraint.
  ResetFactory();
  constraint_factory_.basic().channel_count.SetMax(kMinChannels - 1);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  constraint_factory_.basic().channel_count.SetMax(kMinChannels);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());

  // Define a bounded range for the constraint.
  ResetFactory();
  constraint_factory_.basic().channel_count.SetMin(kMinChannels);
  constraint_factory_.basic().channel_count.SetMax(media::limits::kMaxChannels);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());

  constraint_factory_.basic().channel_count.SetMin(kMinChannels - 10);
  constraint_factory_.basic().channel_count.SetMax(kMinChannels - 1);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  constraint_factory_.basic().channel_count.SetMin(media::limits::kMaxChannels +
                                                   1);
  constraint_factory_.basic().channel_count.SetMax(media::limits::kMaxChannels +
                                                   10);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Test ideal constraints.
  for (; channel_count <= media::limits::kMaxChannels; ++channel_count) {
    ResetFactory();
    constraint_factory_.basic().channel_count.SetExact(channel_count);
    result = SelectSettings();

    EXPECT_TRUE(result.HasValue());
    if (IsDeviceCapture()) {
      if (channel_count == 4)
        EXPECT_EQ(result.device_id(), "4_channels_device");
      else
        EXPECT_EQ(result.device_id(), "default_device");
    }
  }
}

TEST_P(MediaStreamConstraintsUtilAudioTest, MultiChannelEchoCancellation) {
  if (!IsDeviceCapture())
    return;

  AudioCaptureSettings result;

  ResetFactory();
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  EXPECT_EQ(result.device_id(), "default_device");
  // By default, use the default deevice with echo cancellation enabled
  // and 1 channel,
  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kBrowserDecides);
  EXPECT_EQ(result.num_channels(), 1);

  ResetFactory();
  constraint_factory_.basic().device_id.SetExact("default_device");
  constraint_factory_.basic().echo_cancellation.SetExactBoolean(true);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  EXPECT_EQ(result.device_id(), "default_device");
  // By default, use 1 channel, even with a stereo device.
  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kBrowserDecides);
  EXPECT_EQ(result.num_channels(), 1);

  ResetFactory();
  constraint_factory_.basic().channel_count.SetExact(2);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  EXPECT_EQ(result.device_id(), "default_device");
  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kBrowserDecides);
  EXPECT_EQ(result.num_channels(), 2);

  ResetFactory();
  constraint_factory_.basic().channel_count.SetIdeal(2);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  EXPECT_EQ(result.device_id(), "default_device");
  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kBrowserDecides);
  EXPECT_EQ(result.num_channels(), 2);

  ResetFactory();
  constraint_factory_.basic().channel_count.SetIdeal(4);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  EXPECT_EQ(result.device_id(), "4_channels_device");
  EXPECT_EQ(result.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kBrowserDecides);
  EXPECT_EQ(result.num_channels(), 4);
}

TEST_P(MediaStreamConstraintsUtilAudioTest, ChannelsWithSource) {
  if (!IsDeviceCapture())
    return;

  std::unique_ptr<blink::LocalMediaStreamAudioSource> source =
      GetLocalMediaStreamAudioSource(false /* enable_system_echo_canceller */,
                                     false /* disable_local_echo */,
                                     false /* render_to_associated_sink */);
  int channel_count = kMinChannels;
  for (; channel_count <= media::limits::kMaxChannels; ++channel_count) {
    ResetFactory();
    constraint_factory_.basic().channel_count.SetExact(channel_count);
    auto result = SelectSettingsAudioCapture(
        source.get(), constraint_factory_.CreateMediaConstraints());
    if (channel_count == 2)
      EXPECT_TRUE(result.HasValue());
    else
      EXPECT_FALSE(result.HasValue());
  }
}

TEST_P(MediaStreamConstraintsUtilAudioTest, SampleRate) {
  AudioCaptureSettings result;
  int exact_sample_rate = webrtc::AudioProcessing::kSampleRate8kHz;
  int min_sample_rate = webrtc::AudioProcessing::kSampleRate8kHz;
  // |max_sample_rate| is different based on architecture, namely due to a
  // difference on Android.
  int max_sample_rate =
      std::max(static_cast<int>(media::AudioParameters::kAudioCDSampleRate),
               media::WebRtcAudioProcessingSampleRateHz());
  int ideal_sample_rate = webrtc::AudioProcessing::kSampleRate8kHz;
  if (!IsDeviceCapture()) {
    exact_sample_rate = media::AudioParameters::kAudioCDSampleRate;
    min_sample_rate =
        std::min(static_cast<int>(media::AudioParameters::kAudioCDSampleRate),
                 media::WebRtcAudioProcessingSampleRateHz());
    ideal_sample_rate = media::AudioParameters::kAudioCDSampleRate;
  }

  // Test set exact sampleRate.
  ResetFactory();
  constraint_factory_.basic().sample_rate.SetExact(exact_sample_rate);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  if (IsDeviceCapture())
    EXPECT_EQ(result.device_id(), "8khz_sample_rate_device");

  constraint_factory_.basic().sample_rate.SetExact(11111);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Only set a min value for the constraint.
  ResetFactory();
  constraint_factory_.basic().sample_rate.SetMin(max_sample_rate);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  if (IsDeviceCapture())
    EXPECT_EQ(result.device_id(), "default_device");

  constraint_factory_.basic().sample_rate.SetMin(max_sample_rate + 1);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Only set a max value for the constraint.
  ResetFactory();
  constraint_factory_.basic().sample_rate.SetMax(min_sample_rate);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  if (IsDeviceCapture())
    EXPECT_EQ(result.device_id(), "8khz_sample_rate_device");

  constraint_factory_.basic().sample_rate.SetMax(min_sample_rate - 1);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Define a bounded range for the constraint.
  ResetFactory();
  constraint_factory_.basic().sample_rate.SetMin(min_sample_rate);
  constraint_factory_.basic().sample_rate.SetMax(max_sample_rate);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  if (IsDeviceCapture())
    EXPECT_EQ(result.device_id(), "default_device");

  constraint_factory_.basic().sample_rate.SetMin(min_sample_rate - 1000);
  constraint_factory_.basic().sample_rate.SetMax(min_sample_rate - 1);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  constraint_factory_.basic().sample_rate.SetMin(max_sample_rate + 1);
  constraint_factory_.basic().sample_rate.SetMax(max_sample_rate + 1000);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Test ideal constraints.
  ResetFactory();
  constraint_factory_.basic().sample_rate.SetIdeal(ideal_sample_rate);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  if (IsDeviceCapture())
    EXPECT_EQ(result.device_id(), "8khz_sample_rate_device");

  constraint_factory_.basic().sample_rate.SetIdeal(ideal_sample_rate);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  if (IsDeviceCapture())
    EXPECT_EQ(result.device_id(), "8khz_sample_rate_device");

  if (IsDeviceCapture()) {
    constraint_factory_.basic().sample_rate.SetIdeal(
        webrtc::AudioProcessing::kSampleRate48kHz + 1000);
    result = SelectSettings();
    EXPECT_TRUE(result.HasValue());
    EXPECT_EQ(result.device_id(), "default_device");
  }
}

TEST_P(MediaStreamConstraintsUtilAudioTest, SampleRateWithSource) {
  if (!IsDeviceCapture())
    return;

  std::unique_ptr<blink::LocalMediaStreamAudioSource> source =
      GetLocalMediaStreamAudioSource(false /* enable_system_echo_canceller */,
                                     false /* disable_local_echo */,
                                     false /* render_to_associated_sink */);

  // Test set exact sampleRate.
  ResetFactory();
  constraint_factory_.basic().sample_rate.SetExact(
      media::AudioParameters::kAudioCDSampleRate);
  auto result = SelectSettingsAudioCapture(
      source.get(), constraint_factory_.CreateMediaConstraints());
  EXPECT_TRUE(result.HasValue());

  constraint_factory_.basic().sample_rate.SetExact(11111);
  result = SelectSettingsAudioCapture(
      source.get(), constraint_factory_.CreateMediaConstraints());
  EXPECT_FALSE(result.HasValue());

  // Test set min sampleRate.
  ResetFactory();
  constraint_factory_.basic().sample_rate.SetMin(
      media::AudioParameters::kAudioCDSampleRate);
  result = SelectSettingsAudioCapture(
      source.get(), constraint_factory_.CreateMediaConstraints());
  EXPECT_TRUE(result.HasValue());

  constraint_factory_.basic().sample_rate.SetMin(
      media::AudioParameters::kAudioCDSampleRate + 1);
  result = SelectSettingsAudioCapture(
      source.get(), constraint_factory_.CreateMediaConstraints());
  EXPECT_FALSE(result.HasValue());

  // Test set max sampleRate.
  ResetFactory();
  constraint_factory_.basic().sample_rate.SetMax(
      media::AudioParameters::kAudioCDSampleRate);
  result = SelectSettingsAudioCapture(
      source.get(), constraint_factory_.CreateMediaConstraints());
  EXPECT_TRUE(result.HasValue());

  constraint_factory_.basic().sample_rate.SetMax(
      media::AudioParameters::kAudioCDSampleRate - 1);
  result = SelectSettingsAudioCapture(
      source.get(), constraint_factory_.CreateMediaConstraints());
  EXPECT_FALSE(result.HasValue());

  // Test set ideal sampleRate.
  ResetFactory();
  constraint_factory_.basic().sample_rate.SetIdeal(
      media::AudioParameters::kAudioCDSampleRate);
  result = SelectSettingsAudioCapture(
      source.get(), constraint_factory_.CreateMediaConstraints());
  EXPECT_TRUE(result.HasValue());

  constraint_factory_.basic().sample_rate.SetIdeal(
      media::AudioParameters::kAudioCDSampleRate - 1);
  result = SelectSettingsAudioCapture(
      source.get(), constraint_factory_.CreateMediaConstraints());
  EXPECT_TRUE(result.HasValue());
}

TEST_P(MediaStreamConstraintsUtilAudioTest, Latency) {
  // Test set exact sampleRate.
  ResetFactory();
  if (IsDeviceCapture())
    constraint_factory_.basic().latency.SetExact(0.125);
  else
    constraint_factory_.basic().latency.SetExact(0.01);
  auto result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  if (IsDeviceCapture())
    EXPECT_EQ(result.device_id(), "8khz_sample_rate_device");

  constraint_factory_.basic().latency.SetExact(
      static_cast<double>(blink::kFallbackAudioLatencyMs) / 1000);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  if (IsDeviceCapture())
    EXPECT_EQ(result.device_id(), "default_device");

  constraint_factory_.basic().latency.SetExact(0.0);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Test set min sampleRate.
  ResetFactory();
  if (IsDeviceCapture())
    constraint_factory_.basic().latency.SetMin(0.125);
  else
    constraint_factory_.basic().latency.SetMin(0.01);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  if (IsDeviceCapture())
    EXPECT_EQ(result.device_id(), "8khz_sample_rate_device");

  constraint_factory_.basic().latency.SetMin(0.126);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Test set max sampleRate.
  ResetFactory();
  constraint_factory_.basic().latency.SetMax(0.1);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  if (IsDeviceCapture())
    EXPECT_EQ(result.device_id(), "default_device");

  constraint_factory_.basic().latency.SetMax(0.001);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Test set bounded sampleRate range.
  ResetFactory();
  if (IsDeviceCapture()) {
    constraint_factory_.basic().latency.SetMin(0.1);
    constraint_factory_.basic().latency.SetMax(0.125);
  } else {
    constraint_factory_.basic().latency.SetMin(0.01);
    constraint_factory_.basic().latency.SetMax(0.1);
  }
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  if (IsDeviceCapture())
    EXPECT_EQ(result.device_id(), "8khz_sample_rate_device");

  constraint_factory_.basic().latency.SetMin(0.0001);
  constraint_factory_.basic().latency.SetMax(0.001);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  constraint_factory_.basic().latency.SetMin(0.126);
  constraint_factory_.basic().latency.SetMax(0.2);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Test set ideal sampleRate range.
  ResetFactory();
  if (IsDeviceCapture())
    constraint_factory_.basic().latency.SetIdeal(0.125);
  else
    constraint_factory_.basic().latency.SetIdeal(0.01);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  if (IsDeviceCapture())
    EXPECT_EQ(result.device_id(), "8khz_sample_rate_device");

  constraint_factory_.basic().latency.SetIdeal(0.0);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  if (IsDeviceCapture())
    EXPECT_EQ(result.device_id(), "default_device");
}

TEST_P(MediaStreamConstraintsUtilAudioTest, LatencyWithSource) {
  if (!IsDeviceCapture())
    return;

  std::unique_ptr<blink::LocalMediaStreamAudioSource> source =
      GetLocalMediaStreamAudioSource(false /* enable_system_echo_canceller */,
                                     false /* disable_local_echo */,
                                     false /* render_to_associated_sink */);
  // Test set exact sampleRate.
  ResetFactory();
  constraint_factory_.basic().latency.SetExact(0.01);
  auto result = SelectSettingsAudioCapture(
      source.get(), constraint_factory_.CreateMediaConstraints());
  EXPECT_TRUE(result.HasValue());

  constraint_factory_.basic().latency.SetExact(0.1234);
  result = SelectSettingsAudioCapture(
      source.get(), constraint_factory_.CreateMediaConstraints());
  EXPECT_FALSE(result.HasValue());

  // Test set min sampleRate.
  ResetFactory();
  constraint_factory_.basic().latency.SetMin(0.01);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());

  constraint_factory_.basic().latency.SetMin(0.2);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Test set max sampleRate.
  ResetFactory();
  constraint_factory_.basic().latency.SetMax(
      static_cast<double>(blink::kFallbackAudioLatencyMs) / 1000);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());

  constraint_factory_.basic().latency.SetMax(0.001);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Test set bounded sampleRate range.
  ResetFactory();
  constraint_factory_.basic().latency.SetMin(0.01);
  constraint_factory_.basic().latency.SetMax(0.1);
  result = SelectSettings();
  EXPECT_TRUE(result.HasValue());

  constraint_factory_.basic().latency.SetMin(0.0001);
  constraint_factory_.basic().latency.SetMax(0.001);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  constraint_factory_.basic().latency.SetMin(0.2);
  constraint_factory_.basic().latency.SetMax(0.4);
  result = SelectSettings();
  EXPECT_FALSE(result.HasValue());

  // Test set ideal sampleRate.
  ResetFactory();
  constraint_factory_.basic().latency.SetIdeal(0.01);
  result = SelectSettingsAudioCapture(
      source.get(), constraint_factory_.CreateMediaConstraints());
  EXPECT_TRUE(result.HasValue());

  constraint_factory_.basic().latency.SetIdeal(0.1234);
  result = SelectSettingsAudioCapture(
      source.get(), constraint_factory_.CreateMediaConstraints());
  EXPECT_TRUE(result.HasValue());
}

// DeviceID tests.
TEST_P(MediaStreamConstraintsUtilAudioTest, ExactArbitraryDeviceID) {
  const String kArbitraryDeviceID = "arbitrary";
  constraint_factory_.basic().device_id.SetExact(kArbitraryDeviceID);
  auto result = SelectSettings();
  // kArbitraryDeviceID is invalid for device capture, but it is considered
  // valid for content capture. For content capture, validation of device
  // capture is performed by the getUserMedia() implementation.
  if (IsDeviceCapture()) {
    EXPECT_FALSE(result.HasValue());
    EXPECT_EQ(std::string(constraint_factory_.basic().device_id.GetName()),
              std::string(result.failed_constraint_name()));
  } else {
    EXPECT_TRUE(result.HasValue());
    EXPECT_EQ(kArbitraryDeviceID.Utf8(), result.device_id());
    CheckBoolDefaults(AudioSettingsBoolMembers(), AudioPropertiesBoolMembers(),
                      result);
    CheckEchoCancellationModeDefault(result);
  }
}

// DeviceID tests check various ways to deal with the device_id constraint.
TEST_P(MediaStreamConstraintsUtilAudioTest, IdealArbitraryDeviceID) {
  const String kArbitraryDeviceID = "arbitrary";
  constraint_factory_.basic().device_id.SetIdeal(kArbitraryDeviceID);
  auto result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  // kArbitraryDeviceID is invalid for device capture, but it is considered
  // valid for content capture. For content capture, validation of device
  // capture is performed by the getUserMedia() implementation.
  if (IsDeviceCapture())
    CheckDeviceDefaults(result);
  else
    EXPECT_EQ(kArbitraryDeviceID.Utf8(), result.device_id());
  CheckProcessingType(result);
  CheckBoolDefaults(AudioSettingsBoolMembers(), AudioPropertiesBoolMembers(),
                    result);
  CheckEchoCancellationModeDefault(result);
}

TEST_P(MediaStreamConstraintsUtilAudioTest, ExactValidDeviceID) {
  for (const auto& device : capabilities_) {
    constraint_factory_.basic().device_id.SetExact(device.DeviceID());
    auto result = SelectSettings();
    EXPECT_TRUE(result.HasValue());
    CheckDevice(device, result);
    CheckProcessingType(result);
    CheckBoolDefaults(AudioSettingsBoolMembers(), AudioPropertiesBoolMembers(),
                      result);
    EchoCancellationMode expected_echo_cancellation_mode =
        EchoCancellationMode::kDisabled;
    if (IsDeviceCapture()) {
      expected_echo_cancellation_mode = EchoCancellationMode::kBrowserDecides;
    }
    EXPECT_EQ(expected_echo_cancellation_mode,
              result.audio_processing_properties().echo_cancellation_mode);
  }
}

TEST_P(MediaStreamConstraintsUtilAudioTest, ExactGroupID) {
  for (const auto& device : capabilities_) {
    constraint_factory_.basic().group_id.SetExact(device.GroupID());
    auto result = SelectSettings();
    EXPECT_TRUE(result.HasValue());
    CheckDevice(device, result);
    CheckProcessingType(result);
    CheckBoolDefaults(AudioSettingsBoolMembers(), AudioPropertiesBoolMembers(),
                      result);
    EchoCancellationMode expected_echo_cancellation_mode =
        EchoCancellationMode::kDisabled;
    if (IsDeviceCapture()) {
      expected_echo_cancellation_mode = EchoCancellationMode::kBrowserDecides;
    }
    EXPECT_EQ(expected_echo_cancellation_mode,
              result.audio_processing_properties().echo_cancellation_mode);
  }
}

// Tests the echoCancellation constraint with a device without system echo
// cancellation.
TEST_P(MediaStreamConstraintsUtilAudioTest, EchoCancellationWithWebRtc) {
  for (auto set_function : kBoolOrStringSetBooleanFunctions) {
    for (auto accessor : kFactoryAccessors) {
      // Ideal advanced is ignored by the SelectSettings algorithm.
      // Using array elements instead of pointer values due to the comparison
      // failing on some build configurations.
      if (set_function == kBoolOrStringSetBooleanFunctions[1] &&
          accessor == kFactoryAccessors[1]) {
        continue;
      }
      for (bool value : kBoolValues) {
        ResetFactory();
        ((constraint_factory_.*accessor)().echo_cancellation.*
         set_function)(value);
        auto result = SelectSettings();
        EXPECT_TRUE(result.HasValue());
        const AudioProcessingProperties& properties =
            result.audio_processing_properties();
        // With device capture, the echo_cancellation constraint
        // enables/disables all audio processing by default.
        // With content capture, the echo_cancellation constraint controls
        // only the echo_cancellation properties. The other audio processing
        // properties default to false.
        const EchoCancellationMode expected_echo_cancellation_mode =
            value ? EchoCancellationMode::kBrowserDecides
                  : EchoCancellationMode::kDisabled;
        EXPECT_EQ(expected_echo_cancellation_mode,
                  properties.echo_cancellation_mode);
        const bool enable_webrtc_audio_processing =
            IsDeviceCapture() ? value : false;
        EXPECT_EQ(enable_webrtc_audio_processing, properties.auto_gain_control);
        EXPECT_EQ(enable_webrtc_audio_processing, properties.noise_suppression);

        // The following are not audio processing.
        EXPECT_EQ(GetMediaStreamSource() != blink::kMediaStreamSourceDesktop,
                  result.disable_local_echo());
        EXPECT_FALSE(result.render_to_associated_sink());
        CheckProcessingType(result);
        if (IsDeviceCapture()) {
          CheckDevice(*default_device_, result);
        } else {
          EXPECT_TRUE(result.device_id().empty());
        }
      }
    }
  }
}

// Tests the echoCancellation constraint with a device with system echo
// cancellation.
TEST_P(MediaStreamConstraintsUtilAudioTest, EchoCancellationWithSystem) {
  // With content capture, there is no system echo cancellation, so
  // nothing to test.
  if (!IsDeviceCapture())
    return;

  for (auto set_function : kBoolOrStringSetBooleanFunctions) {
    for (auto accessor : kFactoryAccessors) {
      // Ideal advanced is ignored by the SelectSettings algorithm.
      // Using array elements instead of pointer values due to the comparison
      // failing on some build configurations.
      if (set_function == kBoolOrStringSetBooleanFunctions[1] &&
          accessor == kFactoryAccessors[1]) {
        continue;
      }
      for (bool value : kBoolValues) {
        ResetFactory();
        constraint_factory_.basic().device_id.SetExact(
            system_echo_canceller_device_->DeviceID());
        ((constraint_factory_.*accessor)().echo_cancellation.*
         set_function)(value);
        auto result = SelectSettings();
        EXPECT_TRUE(result.HasValue());
        const AudioProcessingProperties& properties =
            result.audio_processing_properties();
        // With system echo cancellation, the echo_cancellation constraint
        // enables/disables all audio processing by default, WebRTC echo
        // cancellation is always disabled, and system echo cancellation is
        // disabled if the echo_cancellation constraint is false.
        const EchoCancellationMode expected_echo_cancellation_mode =
            value ? EchoCancellationMode::kBrowserDecides
                  : EchoCancellationMode::kDisabled;
        EXPECT_EQ(expected_echo_cancellation_mode,
                  properties.echo_cancellation_mode);
        EXPECT_EQ(value, properties.auto_gain_control);
        EXPECT_EQ(value, properties.noise_suppression);

        // The following are not audio processing.
        EXPECT_EQ(GetMediaStreamSource() != blink::kMediaStreamSourceDesktop,
                  result.disable_local_echo());
        EXPECT_FALSE(result.render_to_associated_sink());
        CheckProcessingType(result);
        CheckDevice(*system_echo_canceller_device_, result);
      }
    }
  }
}

// Tests that individual boolean audio-processing constraints override the
// default value set by the echoCancellation constraint.
TEST_P(MediaStreamConstraintsUtilAudioTest,
       EchoCancellationAndSingleBoolConstraint) {
  const Vector<blink::BooleanConstraint MediaTrackConstraintSetPlatform::*>
      kAudioProcessingConstraints = {
          &MediaTrackConstraintSetPlatform::auto_gain_control,
          &MediaTrackConstraintSetPlatform::noise_suppression,
      };

  ASSERT_EQ(GetAudioProcessingProperties().size(),
            kAudioProcessingConstraints.size());
  for (size_t function_idx = 0; function_idx < kBoolSetFunctions.size();
       ++function_idx) {
    for (auto accessor : kFactoryAccessors) {
      // Ideal advanced is ignored by the SelectSettings algorithm.
      // Using array elements instead of pointer values due to the comparison
      // failing on some build configurations.
      if (function_idx == 1 && accessor == kFactoryAccessors[1]) {
        continue;
      }
      auto set_bool_function = kBoolSetFunctions[function_idx];
      auto set_bool_or_string_function =
          kBoolOrStringSetBooleanFunctions[function_idx];
      for (wtf_size_t i = 0; i < GetAudioProcessingProperties().size(); ++i) {
        ResetFactory();
        ((constraint_factory_.*accessor)().echo_cancellation.*
         set_bool_or_string_function)(false);
        (((constraint_factory_.*accessor)().*kAudioProcessingConstraints[i]).*
         set_bool_function)(true);
        auto result = SelectSettings();
        EXPECT_TRUE(result.HasValue());
        CheckProcessingType(result);
        EXPECT_EQ(EchoCancellationMode::kDisabled,
                  result.audio_processing_properties().echo_cancellation_mode);
        EXPECT_TRUE(result.audio_processing_properties().*
                    GetAudioProcessingProperties()[i]);
        for (wtf_size_t j = 0; j < GetAudioProcessingProperties().size(); ++j) {
          if (i == j)
            continue;
          EXPECT_FALSE(result.audio_processing_properties().*
                       GetAudioProcessingProperties()[j]);
        }
      }
    }
  }
}

TEST_P(MediaStreamConstraintsUtilAudioTest,
       ReconfigurationSystemEchoCancellation) {
  // This test is relevant only for device capture, where HW EC can be found.
  if (!IsDeviceCapture())
    return;

  // Create a capability that is based on a already opened source with system
  // echo cancellation enabled.
  AudioProcessingProperties properties;
  properties.echo_cancellation_mode = EchoCancellationMode::kBrowserDecides;
  std::unique_ptr<ProcessedLocalAudioSource> system_echo_canceller_source =
      GetProcessedLocalAudioSource(
          properties, false /* disable_local_echo */,
          false /* render_to_associated_sink */,
          media::AudioParameters::PlatformEffectsMask::ECHO_CANCELLER,
          1 /* num_requested_channels */);
  AudioDeviceCaptureCapabilities capabilities = {
      AudioDeviceCaptureCapability(system_echo_canceller_source.get())};
  AudioDeviceCaptureCapability* system_echo_canceller_with_source =
      &capabilities[0];

  constraint_factory_.Reset();
  constraint_factory_.basic().device_id.SetExact(
      system_echo_canceller_with_source->DeviceID());
  constraint_factory_.basic().echo_cancellation.SetExactBoolean(true);
  auto result = SelectSettings(true, capabilities);
  EXPECT_TRUE(result.HasValue());

  constraint_factory_.Reset();
  constraint_factory_.basic().device_id.SetExact(
      system_echo_canceller_with_source->DeviceID());
  constraint_factory_.basic().echo_cancellation.SetExactBoolean(false);
  result = SelectSettings(true, capabilities);
#if BUILDFLAG(IS_CHROMEOS)
  // ChromeOS supports reopening a device with a different system AEC setting.
  EXPECT_TRUE(result.HasValue());
#else
  EXPECT_FALSE(result.HasValue());
#endif
}

#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
// Tests voice isolation constraints on a device that has the
// media::AudioParameters::VOICE_ISOLATION_SUPPORTED effect set.
TEST_P(MediaStreamConstraintsUtilAudioTest, VoiceIsolationEnabledOnSupportedDevice) {
  if (!IsDeviceCapture()) {
    return;
  }

  // Exact true: Requesting exact voice isolation enables voice isolation.
  constraint_factory_.Reset();
  constraint_factory_.basic().voice_isolation.SetExact(true);
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(
      settings.audio_processing_properties().voice_isolation,
      AudioProcessingProperties::VoiceIsolationType::kVoiceIsolationEnabled);

  // Exact false: Explicitly requesting disabled voice isolation disables it.
  constraint_factory_.Reset();
  constraint_factory_.basic().voice_isolation.SetExact(false);
  settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(
      settings.audio_processing_properties().voice_isolation,
      AudioProcessingProperties::VoiceIsolationType::kVoiceIsolationDisabled);

  // Ideal true: Requesting ideal voice isolation enables voice isolation.
  constraint_factory_.Reset();
  constraint_factory_.basic().voice_isolation.SetIdeal(true);
  settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(
      settings.audio_processing_properties().voice_isolation,
      AudioProcessingProperties::VoiceIsolationType::kVoiceIsolationEnabled);

  // Ideal false: Requesting ideal disabled voice isolation disables it.
  constraint_factory_.Reset();
  constraint_factory_.basic().voice_isolation.SetIdeal(false);
  settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(
      settings.audio_processing_properties().voice_isolation,
      AudioProcessingProperties::VoiceIsolationType::kVoiceIsolationDisabled);

  // Unconstrained: When no voice isolation constraint is given, default
  // settings are used.
  constraint_factory_.Reset();
  settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().voice_isolation,
            kVoiceIsolationDefaultValue);
}

#if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
// Tests voice isolation constraints on a device that does not have the
// media::AudioParameters::VOICE_ISOLATION_SUPPORTED effect set.
TEST_P(MediaStreamConstraintsUtilAudioTest,
       VoiceIsolationNotEnabledOnUnsupportedDevice) {
  if (!IsDeviceCapture()) {
    return;
  }

  // "system_echo_canceller_device" only has the ECHO_CANCELLER effect bit set
  // (and does not have VOICE_ISOLATION_SUPPORTED).

  // Exact true: Settings selection fails because voice isolation is
  // unsupported on the device.
  constraint_factory_.Reset();
  constraint_factory_.basic().device_id.SetExact(
      "system_echo_canceller_device");
  constraint_factory_.basic().voice_isolation.SetExact(true);
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_FALSE(settings.HasValue());

  // Exact false: Explicitly requesting disabled voice isolation succeeds.
  constraint_factory_.Reset();
  constraint_factory_.basic().device_id.SetExact(
      "system_echo_canceller_device");
  constraint_factory_.basic().voice_isolation.SetExact(false);
  settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(
      settings.audio_processing_properties().voice_isolation,
      AudioProcessingProperties::VoiceIsolationType::kVoiceIsolationDisabled);

  // Ideal true: Selection succeeds since ideal constraints are not mandatory,
  // but voice isolation is disabled because the device lacks support.
  constraint_factory_.Reset();
  constraint_factory_.basic().device_id.SetExact(
      "system_echo_canceller_device");
  constraint_factory_.basic().voice_isolation.SetIdeal(true);
  settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(
      settings.audio_processing_properties().voice_isolation,
      AudioProcessingProperties::VoiceIsolationType::kVoiceIsolationDisabled);

  // Ideal false: Selection succeeds and voice isolation is disabled.
  constraint_factory_.Reset();
  constraint_factory_.basic().device_id.SetExact(
      "system_echo_canceller_device");
  constraint_factory_.basic().voice_isolation.SetIdeal(false);
  settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(
      settings.audio_processing_properties().voice_isolation,
      AudioProcessingProperties::VoiceIsolationType::kVoiceIsolationDisabled);

  // Unconstrained: Selection succeeds and voice isolation is disabled because
  // the device lacks support.
  constraint_factory_.Reset();
  constraint_factory_.basic().device_id.SetExact(
      "system_echo_canceller_device");
  settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(
      settings.audio_processing_properties().voice_isolation,
      AudioProcessingProperties::VoiceIsolationType::kVoiceIsolationDisabled);
}
#endif
#else
TEST_P(MediaStreamConstraintsUtilAudioTest, VoiceIsolationControl) {
  if (!IsDeviceCapture()) {
    return;
  }
  constraint_factory_.Reset();
  constraint_factory_.basic().voice_isolation.SetExact(true);
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_FALSE(settings.HasValue());
  constraint_factory_.Reset();
  constraint_factory_.basic().voice_isolation.SetExact(false);
  settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(
      settings.audio_processing_properties().voice_isolation,
      AudioProcessingProperties::VoiceIsolationType::kVoiceIsolationDisabled);
  constraint_factory_.Reset();
  constraint_factory_.basic().voice_isolation.SetIdeal(true);
  settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(
      settings.audio_processing_properties().voice_isolation,
      AudioProcessingProperties::VoiceIsolationType::kVoiceIsolationDisabled);
  constraint_factory_.Reset();
  constraint_factory_.basic().voice_isolation.SetIdeal(false);
  settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(
      settings.audio_processing_properties().voice_isolation,
      AudioProcessingProperties::VoiceIsolationType::kVoiceIsolationDisabled);
  constraint_factory_.Reset();
  settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(
      settings.audio_processing_properties().voice_isolation,
      AudioProcessingProperties::VoiceIsolationType::kVoiceIsolationDisabled);
}
#endif

#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
class TestAudioSource : public blink::MediaStreamAudioSource {
 public:
  TestAudioSource()
      : blink::MediaStreamAudioSource(
            blink::scheduler::GetSingleThreadTaskRunnerForTesting(),
            true /* is_local_source */) {}

  std::optional<AudioProcessingProperties> GetAudioProcessingProperties()
      const override {
    return audio_properties_;
  }
  void SetAudioProcessingProperties(
      const AudioProcessingProperties& properties) override {
    audio_properties_ = properties;
  }
  void SetFormatForTesting(const media::AudioParameters& params) {
    SetFormat(params);
  }
  bool IsApmProcessedSource() const override { return is_processed_; }
  void SetIsApmProcessed(bool is_processed) { is_processed_ = is_processed; }

 private:
  std::optional<AudioProcessingProperties> audio_properties_;
  bool is_processed_ = false;
};

TEST_P(MediaStreamConstraintsUtilAudioTest,
       VoiceIsolationExactConstraintConflictChecking) {
  if (!IsDeviceCapture()) {
    return;
  }

  // 1. Create a running source.
  auto platform_source_unique = std::make_unique<TestAudioSource>();
  TestAudioSource* platform_source = platform_source_unique.get();
  platform_source->SetAudioProcessingProperties(AudioProcessingProperties());
  platform_source->SetIsApmProcessed(true);
  media::AudioParameters params(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
                                media::ChannelLayoutConfig::Stereo(),
                                media::AudioParameters::kAudioCDSampleRate, 1000);
  params.set_effects(media::AudioParameters::VOICE_ISOLATION_SUPPORTED);
  platform_source->SetFormatForTesting(params);
  MediaStreamDevice device(mojom::blink::MediaStreamType::DEVICE_AUDIO_CAPTURE,
                           "processed_source", "processed_source_name");
  device.input = params;
  platform_source->SetDevice(device);

  MediaStreamSource* source = MakeGarbageCollected<MediaStreamSource>(
      "processed_source", MediaStreamSource::kTypeAudio,
      "processed_source_name", false /* remote */,
      std::move(platform_source_unique));

  // 2. Create track 1 with voice isolation = true exact constraint, and
  // connect to source.
  auto platform_track1 =
      std::make_unique<MediaStreamAudioTrack>(true /* is_local_track */);
  platform_track1->SetVoiceIsolationExactConstraint(true);
  MediaStreamComponent* component1 =
      MakeGarbageCollected<MediaStreamComponentImpl>(
          source->Id(), source, std::move(platform_track1));
  EXPECT_TRUE(platform_source->ConnectToInitializedTrack(component1));

  // 3. Create capabilities based on this active source.
  AudioDeviceCaptureCapabilities capabilities = {
      AudioDeviceCaptureCapability(platform_source)};

  // 4. Try to select settings with voice isolation = false exact. This should
  // FAIL because it conflicts with Track 1's "true" exact constraint.
  constraint_factory_.Reset();
  constraint_factory_.basic().device_id.SetExact(capabilities[0].DeviceID());
  constraint_factory_.basic().echo_cancellation.SetExactBoolean(true);
  constraint_factory_.basic().voice_isolation.SetExact(false);
  AudioCaptureSettings settings = SelectSettings(true, capabilities);
  EXPECT_FALSE(settings.HasValue());

  // 5. Try to select settings with voice isolation = true exact. This should
  // SUCCEED.
  constraint_factory_.Reset();
  constraint_factory_.basic().device_id.SetExact(capabilities[0].DeviceID());
  constraint_factory_.basic().echo_cancellation.SetExactBoolean(true);
  constraint_factory_.basic().voice_isolation.SetExact(true);
  settings = SelectSettings(true, capabilities);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(
      settings.audio_processing_properties().voice_isolation,
      AudioProcessingProperties::VoiceIsolationType::kVoiceIsolationEnabled);

  // 6. Try to select settings without voice isolation constraint. This should
  // SUCCEED and resolve to VoiceIsolationEnabled (matching the active track).
  constraint_factory_.Reset();
  constraint_factory_.basic().device_id.SetExact(capabilities[0].DeviceID());
  constraint_factory_.basic().echo_cancellation.SetExactBoolean(true);
  settings = SelectSettings(true, capabilities);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(
      settings.audio_processing_properties().voice_isolation,
      AudioProcessingProperties::VoiceIsolationType::kVoiceIsolationEnabled);
}

TEST_P(MediaStreamConstraintsUtilAudioTest,
       UnprocessedRequestWithActiveProcessedSource) {
  // 1. Create a processed active source.
  auto platform_source_unique = std::make_unique<TestAudioSource>();
  TestAudioSource* platform_source = platform_source_unique.get();
  platform_source->SetAudioProcessingProperties(AudioProcessingProperties());
  platform_source->SetIsApmProcessed(true);
  platform_source->SetFormatForTesting(
      media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
                             media::ChannelLayoutConfig::Stereo(),
                             media::AudioParameters::kAudioCDSampleRate, 1000));

  MediaStreamSource* source = MakeGarbageCollected<MediaStreamSource>(
      "processed_source", MediaStreamSource::kTypeAudio,
      "processed_source_name", false /* remote */,
      std::move(platform_source_unique));

  // Connect a track to make it active.
  auto platform_track =
      std::make_unique<MediaStreamAudioTrack>(true /* is_local_track */);
  MediaStreamComponent* component =
      MakeGarbageCollected<MediaStreamComponentImpl>(source->Id(), source,
                                                     std::move(platform_track));
  EXPECT_TRUE(platform_source->ConnectToInitializedTrack(component));

  AudioDeviceCaptureCapabilities capabilities = {
      AudioDeviceCaptureCapability(platform_source)};

  // 2. Request unprocessed settings (echoCancellation = false) with
  // is_full_reconfiguration_allowed = true (getUserMedia).
  // This should SUCCEED because we can create a new unprocessed source.
  constraint_factory_.Reset();
  constraint_factory_.basic().device_id.SetExact(capabilities[0].DeviceID());
  constraint_factory_.basic().echo_cancellation.SetExactBoolean(false);
  constraint_factory_.basic().auto_gain_control.SetExact(false);
  constraint_factory_.basic().noise_suppression.SetExact(false);

  AudioCaptureSettings settings = SelectSettings(true, capabilities);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kDisabled);
  EXPECT_FALSE(settings.audio_processing_properties().auto_gain_control);

  // 3. Request unprocessed settings with is_full_reconfiguration_allowed = false
  // (applyConstraints).
  // This should FAIL because we cannot change the source type of the existing
  // processed source to unprocessed.
  settings = SelectSettings(false, capabilities);
  EXPECT_FALSE(settings.HasValue());
}
#endif

// Test advanced constraints sets that can be satisfied.
TEST_P(MediaStreamConstraintsUtilAudioTest, AdvancedCompatibleConstraints) {
  constraint_factory_.AddAdvanced().render_to_associated_sink.SetExact(true);
  auto result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  CheckDeviceDefaults(result);
  CheckProcessingType(result);
  CheckBoolDefaults({&AudioCaptureSettings::render_to_associated_sink}, {},
                    result);
  CheckEchoCancellationModeDefault(result);
  EXPECT_TRUE(result.render_to_associated_sink());
}

// Test that an advanced constraint set that contradicts a previous constraint
// set is ignored, but that further constraint sets that can be satisfied are
// applied.
TEST_P(MediaStreamConstraintsUtilAudioTest,
       AdvancedConflictingMiddleConstraints) {
  constraint_factory_.AddAdvanced().noise_suppression.SetExact(true);
  auto& advanced2 = constraint_factory_.AddAdvanced();
  advanced2.noise_suppression.SetExact(false);
  auto result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  CheckProcessingType(result);
  CheckDeviceDefaults(result);
  CheckBoolDefaults({}, {&AudioProcessingProperties::noise_suppression},
                    result);
  CheckEchoCancellationModeDefault(result);
  EXPECT_TRUE(result.audio_processing_properties().noise_suppression);
}

// Test that an advanced constraint set that contradicts a previous constraint
// set with a boolean constraint is ignored.
TEST_P(MediaStreamConstraintsUtilAudioTest, AdvancedConflictingLastConstraint) {
  constraint_factory_.AddAdvanced().noise_suppression.SetExact(true);
  auto result = SelectSettings();
  EXPECT_TRUE(result.HasValue());
  CheckProcessingType(result);
  CheckDeviceDefaults(result);
  CheckBoolDefaults({}, {&AudioProcessingProperties::noise_suppression},
                    result);
  CheckEchoCancellationModeDefault(result);
  // The fourth advanced set is ignored because it contradicts the second set.
  EXPECT_TRUE(result.audio_processing_properties().noise_suppression);
}

// NoDevices tests verify that the case with no devices is handled correctly.
TEST_P(MediaStreamConstraintsUtilAudioTest, NoDevicesNoConstraints) {
  // This test makes sense only for device capture.
  if (!IsDeviceCapture())
    return;

  AudioDeviceCaptureCapabilities capabilities;
  auto result = SelectSettingsAudioCapture(
      capabilities, constraint_factory_.CreateMediaConstraints(),
      GetMediaStreamType(), false);
  EXPECT_FALSE(result.HasValue());
  EXPECT_TRUE(std::string(result.failed_constraint_name()).empty());
}

TEST_P(MediaStreamConstraintsUtilAudioTest, NoDevicesWithConstraints) {
  // This test makes sense only for device capture.
  if (!IsDeviceCapture())
    return;

  AudioDeviceCaptureCapabilities capabilities;
  constraint_factory_.basic().sample_size.SetExact(16);
  auto result = SelectSettingsAudioCapture(
      capabilities, constraint_factory_.CreateMediaConstraints(),
      GetMediaStreamType(), false);
  EXPECT_FALSE(result.HasValue());
  EXPECT_TRUE(std::string(result.failed_constraint_name()).empty());
}

// Test functionality to support applyConstraints() for tracks attached to
// sources that have no audio processing.
TEST_P(MediaStreamConstraintsUtilAudioTest, SourceWithNoAudioProcessing) {
  for (bool enable_properties : {true, false}) {
    if (enable_properties && media::IsSystemLoopbackAsAecReferenceEnabled()) {
      // LocalMediaStreamAudioSource is never created with system echo canceller
      // when loopback AEC is enabled.
      continue;
    }

    SCOPED_TRACE(enable_properties);
    std::unique_ptr<blink::LocalMediaStreamAudioSource> source =
        GetLocalMediaStreamAudioSource(
            enable_properties /* enable_system_echo_canceller */,
            enable_properties /* disable_local_echo */,
            enable_properties /* render_to_associated_sink */);

    // These constraints are false in |source|.
    const Vector<blink::BooleanConstraint MediaTrackConstraintSetPlatform::*>
        kConstraints = {
            &MediaTrackConstraintSetPlatform::disable_local_echo,
            &MediaTrackConstraintSetPlatform::render_to_associated_sink,
        };

    for (wtf_size_t i = 0; i < kConstraints.size(); ++i) {
      SCOPED_TRACE(i);
      constraint_factory_.Reset();
      (constraint_factory_.basic().*kConstraints[i])
          .SetExact(enable_properties);
      auto result = SelectSettingsAudioCapture(
          source.get(), constraint_factory_.CreateMediaConstraints());
      EXPECT_TRUE(result.HasValue());

      constraint_factory_.Reset();
      (constraint_factory_.basic().*kConstraints[i])
          .SetExact(!enable_properties);
      result = SelectSettingsAudioCapture(
          source.get(), constraint_factory_.CreateMediaConstraints());
      EXPECT_FALSE(result.HasValue());

      // Setting just ideal values should always succeed.
      constraint_factory_.Reset();
      (constraint_factory_.basic().*kConstraints[i]).SetIdeal(true);
      result = SelectSettingsAudioCapture(
          source.get(), constraint_factory_.CreateMediaConstraints());
      EXPECT_TRUE(result.HasValue());

      constraint_factory_.Reset();
      (constraint_factory_.basic().*kConstraints[i]).SetIdeal(false);
      result = SelectSettingsAudioCapture(
          source.get(), constraint_factory_.CreateMediaConstraints());
      EXPECT_TRUE(result.HasValue());
    }
  }
}

// Test functionality to support applyConstraints() for tracks attached to
// sources that have audio processing.
TEST_P(MediaStreamConstraintsUtilAudioTest, SourceWithAudioProcessing) {
  // Processed audio sources are supported only for device capture.
  if (!IsDeviceCapture())
    return;

  for (bool use_defaults : {true, false}) {
    AudioProcessingProperties properties;
    if (!use_defaults) {
      properties.echo_cancellation_mode = EchoCancellationMode::kDisabled;
      properties.auto_gain_control = !properties.auto_gain_control;
      properties.noise_suppression = !properties.noise_suppression;
    }

    std::unique_ptr<ProcessedLocalAudioSource> source =
        GetProcessedLocalAudioSource(
            properties, use_defaults /* disable_local_echo */,
            use_defaults /* render_to_associated_sink */);
    const Vector<blink::BooleanConstraint MediaTrackConstraintSetPlatform::*>
        kAudioProcessingConstraints = {
            &MediaTrackConstraintSetPlatform::auto_gain_control,
            &MediaTrackConstraintSetPlatform::noise_suppression,
        };
    ASSERT_EQ(kAudioProcessingConstraints.size(),
              GetAudioProcessingProperties().size());

    for (wtf_size_t i = 0; i < kAudioProcessingConstraints.size(); ++i) {
      constraint_factory_.Reset();
      (constraint_factory_.basic().*kAudioProcessingConstraints[i])
          .SetExact(properties.*GetAudioProcessingProperties()[i]);
      blink::AudioCaptureSettings result = SelectSettingsAudioCapture(
          source.get(), constraint_factory_.CreateMediaConstraints());
      EXPECT_TRUE(result.HasValue());

      constraint_factory_.Reset();
      (constraint_factory_.basic().*kAudioProcessingConstraints[i])
          .SetExact(!(properties.*GetAudioProcessingProperties()[i]));
      result = SelectSettingsAudioCapture(
          source.get(), constraint_factory_.CreateMediaConstraints());
      EXPECT_FALSE(result.HasValue());

      // Setting just ideal values should always succeed.
      constraint_factory_.Reset();
      (constraint_factory_.basic().*kAudioProcessingConstraints[i])
          .SetIdeal(true);
      result = SelectSettingsAudioCapture(
          source.get(), constraint_factory_.CreateMediaConstraints());
      EXPECT_TRUE(result.HasValue());

      constraint_factory_.Reset();
      (constraint_factory_.basic().*kAudioProcessingConstraints[i])
          .SetIdeal(false);
      result = SelectSettingsAudioCapture(
          source.get(), constraint_factory_.CreateMediaConstraints());
      EXPECT_TRUE(result.HasValue());
    }

    // Test same as above but for echo cancellation.
    constraint_factory_.Reset();
    constraint_factory_.basic().echo_cancellation.SetExactBoolean(
        properties.echo_cancellation_mode ==
        EchoCancellationMode::kBrowserDecides);
    auto result = SelectSettingsAudioCapture(
        source.get(), constraint_factory_.CreateMediaConstraints());
    EXPECT_TRUE(result.HasValue());

    constraint_factory_.Reset();
    constraint_factory_.basic().echo_cancellation.SetExactBoolean(
        properties.echo_cancellation_mode !=
        EchoCancellationMode::kBrowserDecides);
    result = SelectSettingsAudioCapture(
        source.get(), constraint_factory_.CreateMediaConstraints());
    EXPECT_FALSE(result.HasValue());

    constraint_factory_.Reset();
    constraint_factory_.basic().echo_cancellation.SetIdealBoolean(true);
    result = SelectSettingsAudioCapture(
        source.get(), constraint_factory_.CreateMediaConstraints());
    EXPECT_TRUE(result.HasValue());

    constraint_factory_.Reset();
    constraint_factory_.basic().echo_cancellation.SetIdealBoolean(false);
    result = SelectSettingsAudioCapture(
        source.get(), constraint_factory_.CreateMediaConstraints());
    EXPECT_TRUE(result.HasValue());

    // These constraints are false in |source|.
    const Vector<blink::BooleanConstraint MediaTrackConstraintSetPlatform::*>
        kAudioBrowserConstraints = {
            &MediaTrackConstraintSetPlatform::disable_local_echo,
            &MediaTrackConstraintSetPlatform::render_to_associated_sink,
        };
    for (wtf_size_t i = 0; i < kAudioBrowserConstraints.size(); ++i) {
      constraint_factory_.Reset();
      (constraint_factory_.basic().*kAudioBrowserConstraints[i])
          .SetExact(use_defaults);
      result = SelectSettingsAudioCapture(
          source.get(), constraint_factory_.CreateMediaConstraints());
      EXPECT_TRUE(result.HasValue());

      constraint_factory_.Reset();
      (constraint_factory_.basic().*kAudioBrowserConstraints[i])
          .SetExact(!use_defaults);
      result = SelectSettingsAudioCapture(
          source.get(), constraint_factory_.CreateMediaConstraints());
      EXPECT_FALSE(result.HasValue());

      constraint_factory_.Reset();
      (constraint_factory_.basic().*kAudioBrowserConstraints[i]).SetIdeal(true);
      result = SelectSettingsAudioCapture(
          source.get(), constraint_factory_.CreateMediaConstraints());
      EXPECT_TRUE(result.HasValue());

      constraint_factory_.Reset();
      (constraint_factory_.basic().*kAudioBrowserConstraints[i])
          .SetIdeal(false);
      result = SelectSettingsAudioCapture(
          source.get(), constraint_factory_.CreateMediaConstraints());
      EXPECT_TRUE(result.HasValue());
    }

    // Test same as above for echo cancellation.
    constraint_factory_.Reset();
    constraint_factory_.basic().echo_cancellation.SetExactBoolean(use_defaults);
    result = SelectSettingsAudioCapture(
        source.get(), constraint_factory_.CreateMediaConstraints());
    EXPECT_TRUE(result.HasValue());

    constraint_factory_.Reset();
    constraint_factory_.basic().echo_cancellation.SetExactBoolean(
        !use_defaults);
    result = SelectSettingsAudioCapture(
        source.get(), constraint_factory_.CreateMediaConstraints());
    EXPECT_FALSE(result.HasValue());

    constraint_factory_.Reset();
    constraint_factory_.basic().echo_cancellation.SetIdealBoolean(true);
    result = SelectSettingsAudioCapture(
        source.get(), constraint_factory_.CreateMediaConstraints());
    EXPECT_TRUE(result.HasValue());

    constraint_factory_.Reset();
    constraint_factory_.basic().echo_cancellation.SetIdealBoolean(false);
    result = SelectSettingsAudioCapture(
        source.get(), constraint_factory_.CreateMediaConstraints());
    EXPECT_TRUE(result.HasValue());
  }
}

TEST_P(MediaStreamConstraintsUtilAudioTest, UsedAndUnusedSources) {
  // The distinction of used and unused sources is relevant only for device
  // capture.
  if (!IsDeviceCapture())
    return;

  AudioProcessingProperties properties;
  std::unique_ptr<ProcessedLocalAudioSource> processed_source =
      GetProcessedLocalAudioSource(properties, false /* disable_local_echo */,
                                   false /* render_to_associated_sink */);

  const String kUnusedDeviceID = "unused_device";
  const String kGroupID = "fake_group";
  AudioDeviceCaptureCapabilities capabilities;
  capabilities.emplace_back(processed_source.get());
  capabilities.emplace_back(kUnusedDeviceID, kGroupID,
                            media::AudioParameters::UnavailableDeviceParams());

  {
    constraint_factory_.Reset();
    constraint_factory_.basic().echo_cancellation.SetExactBoolean(false);

    auto result = SelectSettingsAudioCapture(
        capabilities, constraint_factory_.CreateMediaConstraints(),
        GetMediaStreamType(),
        /*is_full_reconfiguration_allowed=*/false);
    EXPECT_TRUE(result.HasValue());
    EXPECT_EQ(result.device_id(), kUnusedDeviceID.Utf8());
    EXPECT_EQ(result.audio_processing_properties().echo_cancellation_mode,
              EchoCancellationMode::kDisabled);
  }

  {
    constraint_factory_.Reset();
    constraint_factory_.basic().echo_cancellation.SetExactBoolean(true);
    auto result = SelectSettingsAudioCapture(
        capabilities, constraint_factory_.CreateMediaConstraints(),
        GetMediaStreamType(),
        /*is_full_reconfiguration_allowed=*/false);
    EXPECT_TRUE(result.HasValue());
    EXPECT_EQ(result.device_id(), processed_source->device().id);
    EXPECT_EQ(result.audio_processing_properties().echo_cancellation_mode,
              EchoCancellationMode::kBrowserDecides);
  }
}

TEST_P(MediaStreamConstraintsUtilAudioTest,
       SelectEligibleSettingsAudioDeviceCapture_NoEligibleDevices) {
  if (!IsDeviceCapture()) {
    // This test is irrelevant for non-device captures.
    return;
  }
  constraint_factory_.Reset();
  constraint_factory_.basic().device_id.SetExact("NONEXISTING");
  auto result = SelectEligibleSettings();
  EXPECT_FALSE(result.has_value());
  EXPECT_EQ(constraint_factory_.basic().device_id.GetName(), result.error());
}

TEST_P(MediaStreamConstraintsUtilAudioTest,
       SelectEligibleSettingsAudioDeviceCapture_IncludesEligibleDevices) {
  if (!IsDeviceCapture()) {
    // This test is irrelevant for non-device captures.
    return;
  }
  constraint_factory_.Reset();
  constraint_factory_.basic().sample_rate.SetExact(
      media::AudioParameters::kAudioCDSampleRate);
  auto result = SelectEligibleSettings();
  EXPECT_TRUE(result.has_value());
  EXPECT_EQ(4u, result.value().size());
  EXPECT_EQ("default_device", result.value()[0].device_id());
  EXPECT_EQ("system_echo_canceller_device", result.value()[1].device_id());
  EXPECT_EQ("4_channels_device", result.value()[2].device_id());
  EXPECT_EQ("variable_latency_device", result.value()[3].device_id());
}

TEST_P(MediaStreamConstraintsRemoteAPMTest, DeviceSampleRate) {
  SCOPED_TRACE(GetMessageForScopedTrace());

  AudioCaptureSettings result;
  ResetFactory();
  constraint_factory_.basic().sample_rate.SetExact(
      media::AudioParameters::kAudioCDSampleRate);
  constraint_factory_.basic().echo_cancellation.SetExactBoolean(true);
  result = SelectSettings();

  EXPECT_FALSE(result.HasValue());
}

TEST_P(MediaStreamConstraintsRemoteAPMTest,
       WebRtcSampleRateButNotDeviceSampleRate) {
  SCOPED_TRACE(GetMessageForScopedTrace());

  AudioCaptureSettings result;
  ResetFactory();
  constraint_factory_.basic().sample_rate.SetExact(
      media::WebRtcAudioProcessingSampleRateHz());
  constraint_factory_.basic().echo_cancellation.SetExactBoolean(true);
  result = SelectSettings();

  EXPECT_TRUE(result.HasValue());
}

TEST_P(MediaStreamConstraintsUtilAudioTest, LatencyConstraint) {
  if (!IsDeviceCapture())
    return;

  // The minimum is 10ms because the AudioParameters used in
  // GetLocalMediaStreamAudioSource() device.input come from the default
  // constructor to blink::MediaStreamDevice, which sets them to
  // AudioParameters::UnavailableDeviceParams(), which uses a 10ms buffer size.
  double default_device_min =
      10 / static_cast<double>(base::Time::kMillisecondsPerSecond);
  double default_device_max =
      1000 / static_cast<double>(media::AudioParameters::kAudioCDSampleRate);

  CheckLatencyConstraint(default_device_, default_device_min,
                         default_device_max);
  CheckLocalMediaStreamAudioSourceLatency(
      default_device_, 0.003,
      default_device_min *
          static_cast<double>(media::AudioParameters::kAudioCDSampleRate));
  CheckLocalMediaStreamAudioSourceLatency(
      default_device_, 0.015,
      default_device_min *
          static_cast<double>(media::AudioParameters::kAudioCDSampleRate));
  CheckLocalMediaStreamAudioSourceLatency(default_device_, 0.022, 1000);
  CheckLocalMediaStreamAudioSourceLatency(default_device_, 0.04, 1000);

  double variable_latency_device_min =
      128 / static_cast<double>(media::AudioParameters::kAudioCDSampleRate);
  double variable_latency_device_max =
      4096 / static_cast<double>(media::AudioParameters::kAudioCDSampleRate);

  CheckLatencyConstraint(variable_latency_device_, variable_latency_device_min,
                         variable_latency_device_max);

  // Values here are the closest match to the requested latency as returned by
  // media::AudioLatency::GetExactBufferSize().
  CheckLocalMediaStreamAudioSourceLatency(variable_latency_device_, 0.001, 128);
  CheckLocalMediaStreamAudioSourceLatency(variable_latency_device_, 0.011, 512);
#if BUILDFLAG(IS_WIN)
  // Windows only uses exactly the minimum or else multiples of the
  // hardware_buffer_size (512 for the variable_latency_device_).
  CheckLocalMediaStreamAudioSourceLatency(variable_latency_device_, 0.020,
                                          1024);
#else
  CheckLocalMediaStreamAudioSourceLatency(variable_latency_device_, 0.020, 896);
#endif
  CheckLocalMediaStreamAudioSourceLatency(variable_latency_device_, 0.2, 4096);
}

INSTANTIATE_TEST_SUITE_P(All,
                         MediaStreamConstraintsUtilAudioTest,
                         testing::Values("",
                                         blink::kMediaStreamSourceTab,
                                         blink::kMediaStreamSourceSystem,
                                         blink::kMediaStreamSourceDesktop));
#if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
INSTANTIATE_TEST_SUITE_P(
    All,
    MediaStreamConstraintsRemoteAPMTest,
    testing::Combine(testing::Values("",
                                     blink::kMediaStreamSourceTab,
                                     blink::kMediaStreamSourceSystem,
                                     blink::kMediaStreamSourceDesktop),
                     testing::Values(ChromeWideAecExperiment::kDisabled,
                                     ChromeWideAecExperiment::kEnabled)));
#else
INSTANTIATE_TEST_SUITE_P(
    All,
    MediaStreamConstraintsRemoteAPMTest,
    testing::Combine(testing::Values("",
                                     blink::kMediaStreamSourceTab,
                                     blink::kMediaStreamSourceSystem,
                                     blink::kMediaStreamSourceDesktop),
                     testing::Values(ChromeWideAecExperiment::kDisabled)));
#endif

class MediaStreamConstraintsEchoCancellationModeTest
    : public MediaStreamConstraintsUtilAudioTestBase {
 protected:
  static bool PlatformSupportsRemoteOnly() {
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
    return false;
#else
    return true;
#endif
  }

 private:
  void SetUp() override {
    MediaStreamConstraintsUtilAudioTestBase::SetUp();
    ResetFactory();
    CHECK(IsDeviceCapture());
    capabilities_.emplace_back(
        "default_device", "fake_group1",
        media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
                               media::ChannelLayoutConfig::Stereo(),
                               media::AudioParameters::kAudioCDSampleRate,
                               1000));

    media::AudioParameters system_echo_canceller_parameters(
        media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
        media::ChannelLayoutConfig::Stereo(),
        media::AudioParameters::kAudioCDSampleRate, 1000);
    system_echo_canceller_parameters.set_effects(
        media::AudioParameters::ECHO_CANCELLER);
    capabilities_.emplace_back("system_echo_canceller_device", "fake_group2",
                               system_echo_canceller_parameters);

    default_device_ = &capabilities_[0];
    system_echo_canceller_device_ = &capabilities_[1];
  }
};

TEST_F(MediaStreamConstraintsEchoCancellationModeTest, ExactAll) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetExactString("all");
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kAll);
  // If loopback AEC is enabled, it can be used to provide system AEC for the
  // default device. Otherwise, the device that supports system AEC will be
  // selected.
  const AudioDeviceCaptureCapability* expected_device =
      media::IsSystemLoopbackAsAecReferenceEnabled()
          ? default_device_
          : system_echo_canceller_device_;
  CheckDevice(*expected_device, settings);
}

TEST_F(MediaStreamConstraintsEchoCancellationModeTest, IdealAll) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetIdealString("all");
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kAll);
  // If loopback AEC is enabled, it can be used to provide system AEC for the
  // default device. Otherwise, the device that supports system AEC will be
  // selected.
  const AudioDeviceCaptureCapability* expected_device =
      media::IsSystemLoopbackAsAecReferenceEnabled()
          ? default_device_
          : system_echo_canceller_device_;
  CheckDevice(*expected_device, settings);
}

TEST_F(MediaStreamConstraintsEchoCancellationModeTest,
       ExactAllUnsupportedDevice) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetExactString("all");
  // Exclude the device with system AEC from consideration.
  constraint_factory_.basic().device_id.SetExact(default_device_->DeviceID());
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  if (media::IsSystemLoopbackAsAecReferenceEnabled()) {
    CheckDevice(*default_device_, settings);
  } else {
    EXPECT_FALSE(settings.HasValue());
    EXPECT_THAT(settings.failed_constraint_name(),
                testing::MatchesRegex("echoCancellation|deviceId"));
  }
}

TEST_F(MediaStreamConstraintsEchoCancellationModeTest,
       IdealAllUnsupportedDevice) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetIdealString("all");
  // Exclude the device with system AEC from consideration.
  constraint_factory_.basic().device_id.SetExact(default_device_->DeviceID());
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  // If loopback AEC is available, we use it to provide All AEC. Otherwise, fall
  // back to BrowserDecides AEC.
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            media::IsSystemLoopbackAsAecReferenceEnabled()
                ? EchoCancellationMode::kAll
                : EchoCancellationMode::kBrowserDecides);
  CheckDevice(*default_device_, settings);
}

TEST_F(MediaStreamConstraintsEchoCancellationModeTest, ExactRemoteOnly) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetExactString("remote-only");
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_EQ(settings.HasValue(), PlatformSupportsRemoteOnly());
  if (PlatformSupportsRemoteOnly()) {
    EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
              EchoCancellationMode::kRemoteOnly);
  } else {
    EXPECT_THAT(settings.failed_constraint_name(),
                testing::StrEq("echoCancellation"));
  }
}

TEST_F(MediaStreamConstraintsEchoCancellationModeTest, IdealRemoteOnly) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetIdealString("remote-only");
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            PlatformSupportsRemoteOnly()
                ? EchoCancellationMode::kRemoteOnly
                : EchoCancellationMode::kBrowserDecides);
}

TEST_F(MediaStreamConstraintsEchoCancellationModeTest, ExactInvalidMode) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetExactString("invalid");
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_FALSE(settings.HasValue());
  EXPECT_THAT(settings.failed_constraint_name(),
              testing::StrEq("echoCancellation"));
}

TEST_F(MediaStreamConstraintsEchoCancellationModeTest, IdealInvalidMode) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetIdealString("invalid");
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kBrowserDecides);
}

TEST_F(MediaStreamConstraintsEchoCancellationModeTest, ExactTrue) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetExactBoolean(true);
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kBrowserDecides);
}

#if BUILDFLAG(SYSTEM_LOOPBACK_AS_AEC_REFERENCE)
TEST_F(MediaStreamConstraintsEchoCancellationModeTest,
       AecRequiresApmOrSystemAec) {
  // Enable system loopback as AEC reference, to simulate the conditions of
  // http://crbug.com/441029775
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      media::kSystemLoopbackAsAecReference);
  constraint_factory_.Reset();
  // APM does not support this sample rate, so this rules out APM.
  constraint_factory_.basic().sample_rate.SetExact(
      media::AudioParameters::kAudioCDSampleRate);
  // Delete the device that supports system AEC. It is no longer possible to
  // provide AEC, because both system AEC and APM are ruled out.
  capabilities_.pop_back();
  // Demand AEC.
  constraint_factory_.basic().echo_cancellation.SetExactBoolean(true);
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  // We have demanded an impossible combination of sample rate and AEC, so
  // constraint matching will fail.
  EXPECT_FALSE(settings.HasValue());
}
#endif  // BUILDFLAG(SYSTEM_LOOPBACK_AS_AEC_REFERENCE)

TEST_F(MediaStreamConstraintsEchoCancellationModeTest, IdealTrue) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetIdealBoolean(true);
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kBrowserDecides);
}

TEST_F(MediaStreamConstraintsEchoCancellationModeTest, ExactFalse) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetExactBoolean(false);
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kDisabled);
}

TEST_F(MediaStreamConstraintsEchoCancellationModeTest, IdealFalse) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetIdealBoolean(false);
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kDisabled);
}

TEST_F(MediaStreamConstraintsEchoCancellationModeTest, ExactOverrulesIdeal) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetExactBoolean(true);
  constraint_factory_.basic().echo_cancellation.SetIdealBoolean(false);
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kBrowserDecides);
}

class MediaStreamConstraintsEchoCancellationModeNoDevicesTest
    : public MediaStreamConstraintsUtilAudioTestBase {
 private:
  void SetUp() override {
    MediaStreamConstraintsUtilAudioTestBase::SetUp();
    ResetFactory();
    CHECK(IsDeviceCapture());
    CHECK(capabilities_.empty());
  }
};

TEST_F(MediaStreamConstraintsEchoCancellationModeNoDevicesTest, ExactString) {
  for (const String& value : {"all", "remote-only", "invalid"}) {
    constraint_factory_.Reset();
    constraint_factory_.basic().echo_cancellation.SetExactString(value);
    AudioCaptureSettings settings = SelectSettings(true, capabilities_);
    EXPECT_FALSE(settings.HasValue());
  }
}

TEST_F(MediaStreamConstraintsEchoCancellationModeNoDevicesTest, IdealString) {
  for (const String& value : {"all", "remote-only", "invalid"}) {
    constraint_factory_.Reset();
    constraint_factory_.basic().echo_cancellation.SetIdealString("all");
    AudioCaptureSettings settings = SelectSettings(true, capabilities_);
    EXPECT_FALSE(settings.HasValue());
  }
}

TEST_F(MediaStreamConstraintsEchoCancellationModeNoDevicesTest, ExactBoolean) {
  for (bool value : {true, false}) {
    constraint_factory_.Reset();
    constraint_factory_.basic().echo_cancellation.SetExactBoolean(value);
    AudioCaptureSettings settings = SelectSettings(true, capabilities_);
    EXPECT_FALSE(settings.HasValue());
  }
}

TEST_F(MediaStreamConstraintsEchoCancellationModeNoDevicesTest, IdealBoolean) {
  for (bool value : {true, false}) {
    constraint_factory_.Reset();
    constraint_factory_.basic().echo_cancellation.SetIdealBoolean(value);
    AudioCaptureSettings settings = SelectSettings(true, capabilities_);
    EXPECT_FALSE(settings.HasValue());
  }
}

class MediaStreamConstraintsEchoCancellationModeScreenCaptureTest
    : public MediaStreamConstraintsUtilAudioTestBase {
  void SetUp() override {
    MediaStreamConstraintsUtilAudioTestBase::SetUp();
    ResetFactory();
    CHECK(!IsDeviceCapture());
    capabilities_.emplace_back();
  }

  std::string GetMediaStreamSource() override {
    return blink::kMediaStreamSourceTab;
  }
};

TEST_F(MediaStreamConstraintsEchoCancellationModeScreenCaptureTest,
       Unconstrained) {
  constraint_factory_.Reset();
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kBrowserDecides);
}

TEST_F(MediaStreamConstraintsEchoCancellationModeScreenCaptureTest, ExactAll) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetExactString("all");
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_FALSE(settings.HasValue());
}

TEST_F(MediaStreamConstraintsEchoCancellationModeScreenCaptureTest, IdealAll) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetIdealString("all");
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kBrowserDecides);
}

TEST_F(MediaStreamConstraintsEchoCancellationModeScreenCaptureTest,
       ExactRemoteOnly) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetExactString("remote-only");
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_FALSE(settings.HasValue());
}

TEST_F(MediaStreamConstraintsEchoCancellationModeScreenCaptureTest,
       IdealRemoteOnly) {
  constraint_factory_.Reset();
  constraint_factory_.basic().echo_cancellation.SetIdealString("remote-only");
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kBrowserDecides);
}

TEST_F(MediaStreamConstraintsEchoCancellationModeScreenCaptureTest,
       MediaStreamSourceEmpty) {
  constraint_factory_.Reset();
  constraint_factory_.basic().media_stream_source.SetExact("");
  AudioCaptureSettings settings = SelectSettings(true, capabilities_);
  EXPECT_TRUE(settings.HasValue());
  EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
            EchoCancellationMode::kBrowserDecides);
}

TEST_F(MediaStreamConstraintsEchoCancellationModeScreenCaptureTest,
       MediaStreamSourceNonEmpty) {
  for (const auto& source :
       {kMediaStreamSourceTab, kMediaStreamSourceScreen,
        kMediaStreamSourceDesktop, kMediaStreamSourceSystem}) {
    constraint_factory_.Reset();
    constraint_factory_.basic().media_stream_source.SetExact(source);
    AudioCaptureSettings settings = SelectSettings(true, capabilities_);
    EXPECT_TRUE(settings.HasValue());
    EXPECT_EQ(settings.audio_processing_properties().echo_cancellation_mode,
              EchoCancellationMode::kDisabled);
  }
}

}  // namespace blink
