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

#include "content/browser/renderer_host/media/media_devices_dispatcher_host.h"

#include <stddef.h>

#include <algorithm>
#include <utility>

#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "build/build_config.h"
#include "content/browser/back_forward_cache/back_forward_cache_disable.h"
#include "content/browser/back_forward_cache/back_forward_cache_impl.h"
#include "content/browser/bad_message.h"
#include "content/browser/media/media_devices_permission_checker.h"
#include "content/browser/renderer_host/media/audio_output_authorization_handler.h"
#include "content/browser/renderer_host/media/media_stream_manager.h"
#include "content/browser/renderer_host/media/media_stream_ui_proxy.h"
#include "content/browser/renderer_host/media/preferred_audio_output_device_manager.h"
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include "content/browser/renderer_host/render_frame_host_delegate.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h"
#include "media/audio/audio_system.h"
#include "media/base/media_switches.h"
#include "media/base/video_facing.h"
#include "media/capture/mojom/video_capture_types.mojom.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/features_generated.h"
#include "third_party/blink/public/common/mediastream/media_devices.h"
#include "third_party/blink/public/common/mediastream/media_stream_request.h"
#include "url/origin.h"

#if BUILDFLAG(ENABLE_SCREEN_CAPTURE)
#include "content/browser/media/capture/sub_capture_target_id_web_contents_helper.h"
#endif  // BUILDFLAG(ENABLE_SCREEN_CAPTURE)

using blink::mojom::MediaDeviceType;

namespace content {

namespace {

std::vector<blink::mojom::AudioInputDeviceCapabilitiesPtr>
ToVectorAudioInputDeviceCapabilitiesPtr(
    const std::vector<blink::mojom::AudioInputDeviceCapabilities>&
        capabilities_vector,
    const MediaDeviceSaltAndOrigin& salt_and_origin) {
  std::vector<blink::mojom::AudioInputDeviceCapabilitiesPtr> result;
  result.reserve(capabilities_vector.size());
  for (auto& capabilities : capabilities_vector) {
    blink::mojom::AudioInputDeviceCapabilitiesPtr capabilities_ptr =
        blink::mojom::AudioInputDeviceCapabilities::New();
    capabilities_ptr->device_id =
        GetHMACForRawMediaDeviceID(salt_and_origin, capabilities.device_id);
    capabilities_ptr->group_id = GetHMACForRawMediaDeviceID(
        salt_and_origin, capabilities.group_id, /*use_group_salt=*/true);
    capabilities_ptr->parameters = capabilities.parameters;
    result.push_back(std::move(capabilities_ptr));
  }
  return result;
}

}  // namespace

struct MediaDevicesDispatcherHost::AudioInputCapabilitiesRequest {
  MediaDeviceSaltAndOrigin salt_and_origin;
  GetAudioInputCapabilitiesCallback client_callback;
};

// static
void MediaDevicesDispatcherHost::Create(
    const GlobalRenderFrameHostToken& main_frame_host_token,
    GlobalRenderFrameHostId render_frame_host_id,
    MediaStreamManager* media_stream_manager,
    bool is_outermost_main_frame,
    mojo::PendingReceiver<blink::mojom::MediaDevicesDispatcherHost> receiver) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  media_stream_manager->media_devices_manager()->RegisterDispatcherHost(
      std::make_unique<MediaDevicesDispatcherHost>(
          main_frame_host_token, render_frame_host_id, media_stream_manager,
          is_outermost_main_frame),
      std::move(receiver));
}

MediaDevicesDispatcherHost::MediaDevicesDispatcherHost(
    const GlobalRenderFrameHostToken& main_frame_host_token,
    GlobalRenderFrameHostId render_frame_host_id,
    MediaStreamManager* media_stream_manager,
    bool is_outermost_main_frame)
    : main_frame_host_token_(main_frame_host_token),
      render_frame_host_id_(render_frame_host_id),
      is_outermost_main_frame_(is_outermost_main_frame),
      media_stream_manager_(media_stream_manager),
      num_pending_audio_input_parameters_(0),
      authorization_handler_factory_callback_(base::BindRepeating(
          &MediaDevicesDispatcherHost::CreateAuthorizationHandler,
          // The callback is bound to the current instance of the dispatcher
          // host, so it is safe to pass an Unretained pointer to the callback.
          base::Unretained(this))) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  CHECK(media_stream_manager_, base::NotFatalUntil::M152);
}

MediaDevicesDispatcherHost::~MediaDevicesDispatcherHost() {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  if (!media_stream_manager_->media_devices_manager())
    return;

  for (auto subscription_id : subscription_ids_) {
    media_stream_manager_->media_devices_manager()
        ->UnsubscribeDeviceChangeNotifications(subscription_id);
  }
}

void MediaDevicesDispatcherHost::EnumerateDevices(
    bool request_audio_input,
    bool request_video_input,
    bool request_audio_output,
    bool request_video_input_capabilities,
    bool request_audio_input_capabilities,
    EnumerateDevicesCallback client_callback) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);

  if ((!request_audio_input && !request_video_input && !request_audio_output) ||
      (request_video_input_capabilities && !request_video_input) ||
      (request_audio_input_capabilities && !request_audio_input)) {
    ReceivedBadMessage(render_frame_host_id_.child_id,
                       bad_message::MDDH_INVALID_DEVICE_TYPE_REQUEST);
    return;
  }

  MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
  devices_to_enumerate[static_cast<size_t>(MediaDeviceType::kMediaAudioInput)] =
      request_audio_input;
  devices_to_enumerate[static_cast<size_t>(MediaDeviceType::kMediaVideoInput)] =
      request_video_input;
  devices_to_enumerate[static_cast<size_t>(MediaDeviceType::kMediaAudioOutput)] =
      request_audio_output;

  media_stream_manager_->media_devices_manager()->HandleEnumerateDevicesRequest(
      render_frame_host_id_, devices_to_enumerate,
      request_video_input_capabilities, request_audio_input_capabilities,
      std::move(client_callback));
}

void MediaDevicesDispatcherHost::GetVideoInputCapabilities(
    GetVideoInputCapabilitiesCallback client_callback) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          media_stream_manager_->media_devices_manager()
              ->get_salt_and_origin_cb(),
          render_frame_host_id_,
          base::BindPostTaskToCurrentDefault(base::BindOnce(
              &MediaDevicesDispatcherHost::OnVideoGotSaltAndOrigin,
              weak_factory_.GetWeakPtr(), std::move(client_callback)))));
}

void MediaDevicesDispatcherHost::GetAllVideoInputDeviceFormats(
    const std::string& hashed_device_id,
    GetAllVideoInputDeviceFormatsCallback client_callback) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);

  auto scoped_trace = ScopedMediaStreamTrace::CreateIfEnabled(__func__);
  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          media_stream_manager_->media_devices_manager()
              ->get_salt_and_origin_cb(),
          render_frame_host_id_,
          base::BindPostTaskToCurrentDefault(base::BindOnce(
              &MediaDevicesDispatcherHost::GetVideoInputDeviceFormats,
              weak_factory_.GetWeakPtr(), hashed_device_id,
              false /* try_in_use_first */, std::move(client_callback),
              std::move(scoped_trace)))));
}

void MediaDevicesDispatcherHost::GetAvailableVideoInputDeviceFormats(
    const std::string& hashed_device_id,
    GetAvailableVideoInputDeviceFormatsCallback client_callback) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);

  auto scoped_trace = ScopedMediaStreamTrace::CreateIfEnabled(__func__);
  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          media_stream_manager_->media_devices_manager()
              ->get_salt_and_origin_cb(),
          render_frame_host_id_,
          base::BindPostTaskToCurrentDefault(base::BindOnce(
              &MediaDevicesDispatcherHost::GetVideoInputDeviceFormats,
              weak_factory_.GetWeakPtr(), hashed_device_id,
              true /* try_in_use_first */, std::move(client_callback),
              std::move(scoped_trace)))));
}

void MediaDevicesDispatcherHost::GetAudioInputCapabilities(
    GetAudioInputCapabilitiesCallback client_callback) {
  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          media_stream_manager_->media_devices_manager()
              ->get_salt_and_origin_cb(),
          render_frame_host_id_,
          base::BindPostTaskToCurrentDefault(base::BindOnce(
              &MediaDevicesDispatcherHost::OnAudioGotSaltAndOrigin,
              weak_factory_.GetWeakPtr(), std::move(client_callback)))));
}

void MediaDevicesDispatcherHost::AddMediaDevicesListener(
    bool subscribe_audio_input,
    bool subscribe_video_input,
    bool subscribe_audio_output,
    mojo::PendingRemote<blink::mojom::MediaDevicesListener> listener) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);

  if (!subscribe_audio_input && !subscribe_video_input &&
      !subscribe_audio_output) {
    ReceivedBadMessage(render_frame_host_id_.child_id,
                       bad_message::MDDH_INVALID_DEVICE_TYPE_REQUEST);
    return;
  }

  MediaDevicesManager::BoolDeviceTypes devices_to_subscribe;
  devices_to_subscribe[static_cast<size_t>(MediaDeviceType::kMediaAudioInput)] =
      subscribe_audio_input;
  devices_to_subscribe[static_cast<size_t>(MediaDeviceType::kMediaVideoInput)] =
      subscribe_video_input;
  devices_to_subscribe[static_cast<size_t>(MediaDeviceType::kMediaAudioOutput)] =
      subscribe_audio_output;

  uint32_t subscription_id =
      media_stream_manager_->media_devices_manager()
          ->SubscribeDeviceChangeNotifications(
              render_frame_host_id_, devices_to_subscribe, std::move(listener));
  subscription_ids_.push_back(subscription_id);
}

void MediaDevicesDispatcherHost::SetCaptureHandleConfig(
    blink::mojom::CaptureHandleConfigPtr config) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);

  if (!config) {
    ReceivedBadMessage(render_frame_host_id_.child_id,
                       bad_message::MDDH_NULL_CAPTURE_HANDLE_CONFIG);
    return;
  }

  static_assert(sizeof(decltype(config->capture_handle)::value_type) == 2, "");
  if (config->capture_handle.length() > 1024) {
    ReceivedBadMessage(render_frame_host_id_.child_id,
                       bad_message::MDDH_INVALID_CAPTURE_HANDLE);
    return;
  }

  if (config->all_origins_permitted && !config->permitted_origins.empty()) {
    ReceivedBadMessage(render_frame_host_id_.child_id,
                       bad_message::MDDH_INVALID_ALL_ORIGINS_PERMITTED);
    return;
  }

  for (const auto& origin : config->permitted_origins) {
    if (origin.opaque()) {
      ReceivedBadMessage(render_frame_host_id_.child_id,
                         bad_message::MDDH_INVALID_PERMITTED_ORIGIN);
      return;
    }
  }

  if (capture_handle_config_callback_for_testing_) {
    capture_handle_config_callback_for_testing_.Run(
        render_frame_host_id_.child_id, render_frame_host_id_.frame_routing_id,
        config->Clone());
  }

  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          [](GlobalRenderFrameHostId render_frame_host_id,
             blink::mojom::CaptureHandleConfigPtr config) {
            CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M152);
            RenderFrameHostImpl* const rfhi =
                RenderFrameHostImpl::FromID(render_frame_host_id);
            if (!rfhi || !rfhi->IsActive()) {
              return;
            }
            if (rfhi->GetParentOrOuterDocument()) {
              // Would be overkill to add thread-hopping just to support a test,
              // so we execute directly.
              bad_message::ReceivedBadMessage(render_frame_host_id.child_id,
                                              bad_message::MDDH_NOT_TOP_LEVEL);
              return;
            }
            rfhi->GetPage().SetCaptureHandleConfig(std::move(config));
          },
          render_frame_host_id_, std::move(config)));
}

#if BUILDFLAG(ENABLE_SCREEN_CAPTURE)
void MediaDevicesDispatcherHost::CloseFocusWindowOfOpportunity(
    const std::string& label) {
  media_stream_manager_->SetCapturedDisplaySurfaceFocus(
      label, /*focus=*/true,
      /*is_from_microtask=*/true,
      /*is_from_timer=*/false);
}

void MediaDevicesDispatcherHost::ProduceSubCaptureTargetId(
    media::mojom::SubCaptureTargetType type,
    ProduceSubCaptureTargetIdCallback callback) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);

  GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(
          [](GlobalRenderFrameHostId rfh_id,
             media::mojom::SubCaptureTargetType type) {
            WebContents* const wc =
                SubCaptureTargetIdWebContentsHelper::GetRelevantWebContents(
                    rfh_id);
            if (!wc) {
              return std::string();  // Might have been asynchronously closed.
            }

            // No-op if already created.
            SubCaptureTargetIdWebContentsHelper::CreateForWebContents(wc);

            SubCaptureTargetIdWebContentsHelper* const helper =
                SubCaptureTargetIdWebContentsHelper::FromWebContents(wc);
            return helper->ProduceId(type);
          },
          render_frame_host_id_, type),
      std::move(callback));
}
#endif  // BUILDFLAG(ENABLE_SCREEN_CAPTURE)

void MediaDevicesDispatcherHost::SetPreferredSinkId(
    const std::string& hashed_sink_id,
    SetPreferredSinkIdCallback callback) {
  CHECK_CURRENTLY_ON(BrowserThread::IO);

  if (!is_outermost_main_frame_) {
    mojo::ReportBadMessage(
        "setPreferredSinkId can only be called from the top-level document.");
    std::move(callback).Run(
        media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED);
    return;
  }

  if (!base::FeatureList::IsEnabled(
          blink::features::kPreferredAudioOutputDevices)) {
    ReceivedBadMessage(render_frame_host_id_.child_id,
                       bad_message::MDDH_SET_PREFERRED_SINK_ID_WITHOUT_FEATURE);
    return;
  }

  CHECK(media_stream_manager_->preferred_audio_output_device_manager());

  // The first thing is to validate whether the caller is permitted to set the
  // preferred sink id, which uses the same permission like
  // HTMLMediaElement::setSinkId. AudioOutputAuthorizationHandler will validate
  // it.
  // We call the AudioOutputAuthorizationHandler whether the `hashed_sink_id`
  // is default id or not in order to prevent potential race issue.
  std::unique_ptr<AudioOutputAuthorizationHandler> authorization_handler =
      authorization_handler_factory_callback_.Run();

  AudioOutputAuthorizationHandler* handler = authorization_handler.get();
  handler->RequestDeviceAuthorization(
      base::UnguessableToken(), hashed_sink_id,
      base::BindOnce(&MediaDevicesDispatcherHost::AuthorizationCompleted,
                     weak_factory_.GetWeakPtr(),
                     std::move(authorization_handler), std::move(callback)));
}

void MediaDevicesDispatcherHost::OnVideoGotSaltAndOrigin(
    GetVideoInputCapabilitiesCallback client_callback,
    const MediaDeviceSaltAndOrigin& salt_and_origin) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  MediaDevicesManager::BoolDeviceTypes requested_types;
  // Also request audio devices to make sure the heuristic to determine
  // the video group ID works.
  requested_types[static_cast<size_t>(MediaDeviceType::kMediaVideoInput)] =
      true;
  media_stream_manager_->media_devices_manager()->EnumerateAndRankDevices(
      render_frame_host_id_, requested_types,
      base::BindOnce(
          &MediaDevicesDispatcherHost::FinalizeGetVideoInputCapabilities,
          weak_factory_.GetWeakPtr(), std::move(client_callback),
          salt_and_origin));
}

// If authorization passed, it gets main frame id from the native frame id
// it is already cached.
// `authorization_handler` will be deleted in this function.
// `raw_device_id` will be for the SetPreferredSinkId.
void MediaDevicesDispatcherHost::AuthorizationCompleted(
    std::unique_ptr<AudioOutputAuthorizationHandler> authorization_handler,
    SetPreferredSinkIdCallback callback,
    media::OutputDeviceStatus status,
    const media::AudioParameters&,
    const std::string& raw_device_id,
    const std::string& device_id_for_renderer) {
  CHECK_CURRENTLY_ON(BrowserThread::IO);

  if (status != media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK) {
    std::move(callback).Run(status);
    return;
  }

  media_stream_manager_->preferred_audio_output_device_manager()
      ->SetPreferredSinkId(main_frame_host_token_, raw_device_id,
                           std::move(callback));
}

std::unique_ptr<AudioOutputAuthorizationHandler>
MediaDevicesDispatcherHost::CreateAuthorizationHandler() {
  CHECK_CURRENTLY_ON(BrowserThread::IO);
  return std::make_unique<AudioOutputAuthorizationHandler>(
      media_stream_manager_->audio_system(), media_stream_manager_,
      render_frame_host_id_);
}

void MediaDevicesDispatcherHost::FinalizeGetVideoInputCapabilities(
    GetVideoInputCapabilitiesCallback client_callback,
    const MediaDeviceSaltAndOrigin& salt_and_origin,
    const MediaDeviceEnumeration& enumeration) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  std::vector<blink::mojom::VideoInputDeviceCapabilitiesPtr>
      video_input_capabilities;
  for (const auto& device_info :
       enumeration[static_cast<size_t>(MediaDeviceType::kMediaVideoInput)]) {
    std::string hmac_device_id =
        GetHMACForRawMediaDeviceID(salt_and_origin, device_info.device_id);
    std::string hmac_group_id = GetHMACForRawMediaDeviceID(
        salt_and_origin, device_info.group_id, /*use_group_salt=*/true);
    blink::mojom::VideoInputDeviceCapabilitiesPtr capabilities =
        blink::mojom::VideoInputDeviceCapabilities::New();
    capabilities->device_id = std::move(hmac_device_id);
    capabilities->group_id = std::move(hmac_group_id);
    capabilities->control_support = device_info.video_control_support;
    capabilities->formats =
        media_stream_manager_->media_devices_manager()->GetVideoInputFormats(
            device_info.device_id, true /* try_in_use_first */);
    capabilities->facing_mode = device_info.video_facing;
    video_input_capabilities.push_back(std::move(capabilities));
  }

  std::move(client_callback).Run(std::move(video_input_capabilities));
}

void MediaDevicesDispatcherHost::GetVideoInputDeviceFormats(
    const std::string& hashed_device_id,
    bool try_in_use_first,
    GetVideoInputDeviceFormatsCallback client_callback,
    std::unique_ptr<ScopedMediaStreamTrace> scoped_trace,
    const MediaDeviceSaltAndOrigin& salt_and_origin) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  if (scoped_trace)
    scoped_trace->AddStep(__func__);
  MediaStreamManager::SendMessageToNativeLog(base::StringPrintf(
      "MDDH::GetVideoInputDeviceFormats({hashed_device_id=%s}, "
      "{try_in_use_first=%s})",
      hashed_device_id.c_str(), base::ToString(try_in_use_first)));
  GetRawDeviceIDForMediaStreamHMAC(
      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, salt_and_origin,
      hashed_device_id, base::SequencedTaskRunner::GetCurrentDefault(),
      base::BindOnce(
          &MediaDevicesDispatcherHost::GetVideoInputDeviceFormatsWithRawId,
          weak_factory_.GetWeakPtr(), hashed_device_id, try_in_use_first,
          std::move(client_callback), std::move(scoped_trace)));
}

void MediaDevicesDispatcherHost::GetVideoInputDeviceFormatsWithRawId(
    const std::string& hashed_device_id,
    bool try_in_use_first,
    GetVideoInputDeviceFormatsCallback client_callback,
    std::unique_ptr<ScopedMediaStreamTrace> scoped_trace,
    const std::optional<std::string>& raw_id) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  if (scoped_trace)
    scoped_trace->AddStep(__func__);
  if (!raw_id) {
    // TODO(crbug.com/40848542): return an error.
    MediaStreamManager::SendMessageToNativeLog(
        base::StringPrintf("MDDH::GetVideoInputDeviceFormats: Failed to find "
                           "raw device id for '%s'",
                           hashed_device_id.c_str()));
    std::move(client_callback).Run(media::VideoCaptureFormats());
    return;
  }
  std::move(client_callback)
      .Run(media_stream_manager_->media_devices_manager()->GetVideoInputFormats(
          *raw_id, try_in_use_first));
}

void MediaDevicesDispatcherHost::OnAudioGotSaltAndOrigin(
    GetAudioInputCapabilitiesCallback client_callback,
    const MediaDeviceSaltAndOrigin& salt_and_origin) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  pending_audio_input_capabilities_requests_.push_back(
      AudioInputCapabilitiesRequest{salt_and_origin,
                                    std::move(client_callback)});

  MediaStreamManager::SendMessageToNativeLog(
      base::StringPrintf("MDDH::OnAudioGotSaltAndOrigin: "
                         "pending_audio_input_capabilities_requests_.size()=%d",
                         pending_audio_input_capabilities_requests_.size()));

  if (pending_audio_input_capabilities_requests_.size() > 1U) {
    return;
  }

  CHECK_GT(pending_audio_input_capabilities_requests_.size(), 0U,
           base::NotFatalUntil::M152);
  CHECK(current_audio_input_capabilities_.empty(), base::NotFatalUntil::M152);
  MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
  devices_to_enumerate[static_cast<size_t>(MediaDeviceType::kMediaAudioInput)] =
      true;
  media_stream_manager_->media_devices_manager()->EnumerateAndRankDevices(
      render_frame_host_id_, devices_to_enumerate,
      base::BindOnce(&MediaDevicesDispatcherHost::GotAudioInputEnumeration,
                     weak_factory_.GetWeakPtr()));
}

void MediaDevicesDispatcherHost::SelectAudioOutput(
    const std::string& hashed_device_id,
    SelectAudioOutputCallback select_audio_output_callback) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  if (!base::FeatureList::IsEnabled(blink::features::kSelectAudioOutput)) {
    ReceivedBadMessage(render_frame_host_id_.child_id,
                       bad_message::MDDH_SELECT_AUDIO_OUTPUT_WITHOUT_FEATURE);
    return;
  }

#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA)
  auto result = blink::mojom::SelectAudioOutputResult::New();
  result->status = blink::mojom::AudioOutputStatus::kUnknown;
  std::move(select_audio_output_callback).Run(std::move(result));
#else
  if (select_audio_output_callback_) {
    auto result = blink::mojom::SelectAudioOutputResult::New();
    result->status =
        blink::mojom::AudioOutputStatus::kErrorOtherRequestInProgress;
    std::move(select_audio_output_callback).Run(std::move(result));
    return;
  }
  select_audio_output_callback_ = std::move(select_audio_output_callback);

  // Check for user activation on the UI thread.
  GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(
          [](GlobalRenderFrameHostId render_frame_host_id) {
            RenderFrameHostImpl* render_frame_host =
                RenderFrameHostImpl::FromID(render_frame_host_id);
            return render_frame_host &&
                   render_frame_host->HasTransientUserActivation();
          },
          render_frame_host_id_),
      base::BindOnce(
          &MediaDevicesDispatcherHost::OnGotTransientUserActivationResult,
          weak_factory_.GetWeakPtr(), hashed_device_id));
#endif
}

void MediaDevicesDispatcherHost::OnGotTransientUserActivationResult(
    const std::string& hashed_device_id,
    bool has_user_activation) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);

  if (!has_user_activation) {
    auto result = blink::mojom::SelectAudioOutputResult::New();
    result->status = blink::mojom::AudioOutputStatus::kNoUserActivation;
    std::move(select_audio_output_callback_).Run(std::move(result));
    return;
  }

  media_stream_manager_->media_devices_manager()
      ->GetSpeakerSelectionAndMicrophonePermissionState(
          render_frame_host_id_,
          base::BindOnce(
              &MediaDevicesDispatcherHost::OnAudioOutputPermissionResult,
              weak_factory_.GetWeakPtr(), hashed_device_id));
}

void MediaDevicesDispatcherHost::OnAudioOutputPermissionResult(
    const std::string& hashed_device_id,
    MediaDevicesManager::PermissionDeniedState
        speaker_selection_permission_state,
    bool has_microphone_permission) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  if (speaker_selection_permission_state ==
      MediaDevicesManager::PermissionDeniedState::kDenied) {
    auto result = blink::mojom::SelectAudioOutputResult::New();
    result->status = blink::mojom::AudioOutputStatus::kNoPermission;
    std::move(select_audio_output_callback_).Run(std::move(result));
    return;
  }
  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          media_stream_manager_->media_devices_manager()
              ->get_salt_and_origin_cb(),
          render_frame_host_id_,
          base::BindPostTaskToCurrentDefault(base::BindOnce(
              &MediaDevicesDispatcherHost::OnGotSaltAndOriginForAudioOutput,
              weak_factory_.GetWeakPtr(), hashed_device_id,
              has_microphone_permission))));
}

void MediaDevicesDispatcherHost::OnGotSaltAndOriginForAudioOutput(
    const std::string& hashed_device_id,
    bool has_microphone_permission,
    const MediaDeviceSaltAndOrigin& salt_and_origin) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);

  MediaDevicesManager::BoolDeviceTypes requested_types;
  requested_types[static_cast<size_t>(MediaDeviceType::kMediaAudioOutput)] =
      true;
  media_stream_manager_->media_devices_manager()->EnumerateDevices(
      requested_types,
      base::BindOnce(
          &MediaDevicesDispatcherHost::OnEnumeratedAudioOutputDevices,
          weak_factory_.GetWeakPtr(), hashed_device_id,
          has_microphone_permission, salt_and_origin));
}

void MediaDevicesDispatcherHost::OnEnumeratedAudioOutputDevices(
    const std::string& hashed_device_id,
    bool has_microphone_permission,
    const MediaDeviceSaltAndOrigin& salt_and_origin,
    const MediaDeviceEnumeration& enumeration) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);

  if (!hashed_device_id.empty()) {
    for (const auto& device_info :
         enumeration[static_cast<size_t>(MediaDeviceType::kMediaAudioOutput)]) {
      std::string current_hashed_id =
          GetHMACForRawMediaDeviceID(salt_and_origin, device_info.device_id);
      if (current_hashed_id == hashed_device_id &&
          (has_microphone_permission ||
           media_stream_manager_->media_devices_manager()
               ->IsAudioOutputDeviceExplicitlyAuthorized(
                   render_frame_host_id_, device_info.device_id))) {
        std::move(select_audio_output_callback_)
            .Run(CreateSelectAudioOutputResult(device_info, salt_and_origin));
        return;
      }
    }
  }

  std::vector<content::AudioOutputDeviceInfo> audio_output_devices;
  for (const auto& device_info :
       enumeration[static_cast<size_t>(MediaDeviceType::kMediaAudioOutput)]) {
    audio_output_devices.push_back({device_info.device_id, device_info.label});
  }

  std::unique_ptr<MediaStreamUIProxy> ui_proxy =
      base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kUseFakeUIForMediaStream)
          ? std::make_unique<FakeMediaStreamUIProxy>(
                /*tests_use_fake_render_frame_hosts=*/false)
          : MediaStreamUIProxy::Create();

  ui_proxy->RequestSelectAudioOutput(
      std::make_unique<SelectAudioOutputRequest>(
          render_frame_host_id_, std::move(audio_output_devices)),
      base::BindOnce(&MediaDevicesDispatcherHost::OnSelectedDeviceInfo,
                     weak_factory_.GetWeakPtr(), std::move(enumeration)));
}

void MediaDevicesDispatcherHost::OnSelectedDeviceInfo(
    MediaDeviceEnumeration enumeration,
    base::expected<std::string, SelectAudioOutputError>
        selected_device_id_or_error) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);

  auto result = blink::mojom::SelectAudioOutputResult::New();

  if (selected_device_id_or_error.has_value()) {
    // Get salt and origin for hashing selected device.
    GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(
            media_stream_manager_->media_devices_manager()
                ->get_salt_and_origin_cb(),
            render_frame_host_id_,
            base::BindPostTaskToCurrentDefault(base::BindOnce(
                &MediaDevicesDispatcherHost::FinalizeSelectAudioOutput,
                weak_factory_.GetWeakPtr(), enumeration,
                std::move(selected_device_id_or_error.value())))));
    return;
  }
  content::SelectAudioOutputError error = selected_device_id_or_error.error();
  switch (error) {
    case content::SelectAudioOutputError::kNotSupported:
      result->status = blink::mojom::AudioOutputStatus::kNotSupported;
      break;
    case content::SelectAudioOutputError::kUserCancelled:
      result->status = blink::mojom::AudioOutputStatus::kNoPermission;
      break;
    case content::SelectAudioOutputError::kNoPermission:
      result->status = blink::mojom::AudioOutputStatus::kNoPermission;
      break;
    case content::SelectAudioOutputError::kOtherError:
      result->status = blink::mojom::AudioOutputStatus::kUnknown;
      break;
  }
  std::move(select_audio_output_callback_).Run(std::move(result));
  return;
}

void MediaDevicesDispatcherHost::FinalizeSelectAudioOutput(
    MediaDeviceEnumeration enumeration,
    const std::string& selected_device_id,
    const MediaDeviceSaltAndOrigin& salt_and_origin) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  auto result = blink::mojom::SelectAudioOutputResult::New();

  // Find the selected device and hash its info.
  for (auto& device_info :
       enumeration[static_cast<size_t>(MediaDeviceType::kMediaAudioOutput)]) {
    if (device_info.device_id == selected_device_id) {
      media_stream_manager_->media_devices_manager()->AddAudioDeviceToOriginMap(
          render_frame_host_id_, device_info);
      std::move(select_audio_output_callback_)
          .Run(CreateSelectAudioOutputResult(device_info, salt_and_origin));
      return;
    }
  }

  result->status = blink::mojom::AudioOutputStatus::kDeviceNotFound;
  std::move(select_audio_output_callback_).Run(std::move(result));
}

blink::mojom::SelectAudioOutputResultPtr
MediaDevicesDispatcherHost::CreateSelectAudioOutputResult(
    const blink::WebMediaDeviceInfo& device_info,
    const MediaDeviceSaltAndOrigin& salt_and_origin) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  auto result = blink::mojom::SelectAudioOutputResult::New();
  result->device_info = device_info;
  result->device_info.device_id =
      GetHMACForRawMediaDeviceID(salt_and_origin, device_info.device_id);
  result->device_info.group_id = GetHMACForRawMediaDeviceID(
      salt_and_origin, device_info.group_id, /*use_group_salt=*/true);
  result->status = blink::mojom::AudioOutputStatus::kSuccess;
  return result;
}

void MediaDevicesDispatcherHost::GotAudioInputEnumeration(
    const MediaDeviceEnumeration& enumeration) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  CHECK_GT(pending_audio_input_capabilities_requests_.size(), 0U,
           base::NotFatalUntil::M152);
  CHECK(current_audio_input_capabilities_.empty(), base::NotFatalUntil::M152);
  CHECK_EQ(num_pending_audio_input_parameters_, 0U, base::NotFatalUntil::M152);
  for (const auto& device_info :
       enumeration[static_cast<size_t>(MediaDeviceType::kMediaAudioInput)]) {
    auto parameters = media::AudioParameters::UnavailableDeviceParams();
    blink::mojom::AudioInputDeviceCapabilities capabilities(
        device_info.device_id, device_info.group_id, parameters,
        parameters.IsValid(), parameters.channels(), parameters.sample_rate(),
        parameters.GetBufferDuration());
    current_audio_input_capabilities_.push_back(std::move(capabilities));
  }
  // No devices or fake devices, no need to read audio parameters.
  if (current_audio_input_capabilities_.empty() ||
      base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kUseFakeDeviceForMediaStream)) {
    MediaStreamManager::SendMessageToNativeLog(base::StringPrintf(
        "MDDH::%s: no devices found or fake device command flag is passed,",
        __func__));
    FinalizeGetAudioInputCapabilities();
    return;
  }

  num_pending_audio_input_parameters_ =
      current_audio_input_capabilities_.size();
  for (size_t i = 0; i < num_pending_audio_input_parameters_; ++i) {
    media_stream_manager_->audio_system()->GetInputStreamParameters(
        current_audio_input_capabilities_[i].device_id,
        base::BindOnce(&MediaDevicesDispatcherHost::GotAudioInputParameters,
                       weak_factory_.GetWeakPtr(), i));
  }
}

void MediaDevicesDispatcherHost::GotAudioInputParameters(
    size_t index,
    const std::optional<media::AudioParameters>& parameters) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  CHECK_GT(pending_audio_input_capabilities_requests_.size(), 0U,
           base::NotFatalUntil::M152);
  CHECK_GT(current_audio_input_capabilities_.size(), index,
           base::NotFatalUntil::M152);
  CHECK_GT(num_pending_audio_input_parameters_, 0U, base::NotFatalUntil::M152);

  if (parameters)
    current_audio_input_capabilities_[index].parameters = *parameters;
  CHECK(current_audio_input_capabilities_[index].parameters.IsValid(),
        base::NotFatalUntil::M152);

  num_pending_audio_input_parameters_ -= 1;
  MediaStreamManager::SendMessageToNativeLog(base::StringPrintf(
      "MDDH::%s: num_pending_audio_input_parameters_.size()= %d,", __func__,
      num_pending_audio_input_parameters_));

  if (num_pending_audio_input_parameters_ == 0U) {
    FinalizeGetAudioInputCapabilities();
  }
}

void MediaDevicesDispatcherHost::FinalizeGetAudioInputCapabilities() {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  CHECK_GT(pending_audio_input_capabilities_requests_.size(), 0U,
           base::NotFatalUntil::M152);
  CHECK_EQ(num_pending_audio_input_parameters_, 0U, base::NotFatalUntil::M152);

  MediaStreamManager::SendMessageToNativeLog(base::StringPrintf(
      "MDDH::%s: pending_audio_input_capabilities_requests_.size()= %d,",
      __func__, pending_audio_input_capabilities_requests_.size()));

  for (auto& request : pending_audio_input_capabilities_requests_) {
    std::move(request.client_callback)
        .Run(ToVectorAudioInputDeviceCapabilitiesPtr(
            current_audio_input_capabilities_, request.salt_and_origin));
  }

  current_audio_input_capabilities_.clear();
  pending_audio_input_capabilities_requests_.clear();
}

void MediaDevicesDispatcherHost::ReceivedBadMessage(
    ChildProcessId render_process_id,
    bad_message::BadMessageReason reason) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);

  if (bad_message_callback_for_testing_) {
    bad_message_callback_for_testing_.Run(render_process_id, reason);
  }

  bad_message::ReceivedBadMessage(render_process_id, reason);
}

void MediaDevicesDispatcherHost::SetBadMessageCallbackForTesting(
    base::RepeatingCallback<void(ChildProcessId, bad_message::BadMessageReason)>
        callback) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  CHECK(!bad_message_callback_for_testing_, base::NotFatalUntil::M152);
  bad_message_callback_for_testing_ = callback;
}

void MediaDevicesDispatcherHost::SetCaptureHandleConfigCallbackForTesting(
    base::RepeatingCallback<
        void(ChildProcessId, int, blink::mojom::CaptureHandleConfigPtr)>
        callback) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M152);
  CHECK(!capture_handle_config_callback_for_testing_,
        base::NotFatalUntil::M152);
  capture_handle_config_callback_for_testing_ = std::move(callback);
}

void MediaDevicesDispatcherHost::SetAuthorizationForTesting(
    AuthorizationHandlerCreateFactoryCallback authorization_handler) {
  authorization_handler_factory_callback_ = std::move(authorization_handler);
}

}  // namespace content
