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

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

#include <utility>

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "build/build_config.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_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/desktop_media_id.h"
#include "content/public/browser/media_stream_request.h"
#include "content/public/common/content_switches.h"
#include "media/capture/video/fake_video_capture_device.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
#include "third_party/blink/public/common/mediastream/media_stream_request.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace content {

bool IsFeatureEnabled(RenderFrameHost* rfh,
                      bool tests_use_fake_render_frame_hosts,
                      network::mojom::PermissionsPolicyFeature feature) {
  // Some tests don't (or can't) set up the RenderFrameHost. In these cases we
  // just ignore permissions policy checks (there is no permissions policy to
  // test).
  if (!rfh && tests_use_fake_render_frame_hosts)
    return true;

  if (!rfh)
    return false;

  return rfh->IsFeatureEnabled(feature);
}

class MediaStreamUIProxy::Core {
 public:
  explicit Core(const base::WeakPtr<MediaStreamUIProxy>& proxy,
                RenderFrameHostDelegate* test_render_delegate);

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

  ~Core();

  void RequestAccess(std::unique_ptr<MediaStreamRequest> request);

  void RequestSelectAudioOutput(
      std::unique_ptr<SelectAudioOutputRequest> request,
      SelectAudioOutputCallback callback);

  void OnStarted(gfx::NativeViewId* window_id,
                 bool has_source_callback,
                 const std::string& label,
                 std::vector<DesktopMediaID> screen_capture_ids);
  void OnDeviceStopped(const std::string& label,
                       const DesktopMediaID& media_id);
  void OnDeviceStoppedForSourceChange(const std::string& label,
                                      const DesktopMediaID& old_media_id,
                                      const DesktopMediaID& new_media_id,
                                      bool captured_surface_control_active);

  void OnRegionCaptureRectChanged(
      const std::optional<gfx::Rect>& region_capture_rect);

  void SetFocus(const DesktopMediaID& media_id,
                bool focus,
                bool is_from_microtask,
                bool is_from_timer);

  // The type blink::mojom::StreamDevices is not movable, therefore stream
  // devices cannot be captured for usage with PostTask.
  void ProcessAccessRequestResponseForPostTask(
      int render_process_id,
      int render_frame_id,
      const url::Origin& request_origin,
      blink::mojom::StreamDevicesSetPtr stream_devices_set_ptr,
      blink::mojom::MediaStreamRequestResult result,
      std::unique_ptr<MediaStreamUI> stream_ui);

  void ProcessAccessRequestResponse(
      int render_process_id,
      int render_frame_id,
      const url::Origin& request_origin,
      const blink::mojom::StreamDevicesSet& stream_devices_set,
      blink::mojom::MediaStreamRequestResult result,
      std::unique_ptr<MediaStreamUI> stream_ui);

  base::WeakPtr<Core> GetWeakPtr() {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);
    // This weak pointer is created in the ctor, which runs on the IO thread.
    // This pointer is always posted from the IO thread to the UI thread,
    // meaning reading |weak_this_| happens on the IO thead, but dereferencing
    // the actual pointer happens in the UI thread. Invalidation happens in the
    // destructor, which runs on the UI thread, so this is safe.
    return weak_this_;
  }

 private:
  friend class FakeMediaStreamUIProxy;
  void ProcessStopRequestFromUI();
  void ProcessChangeSourceRequestFromUI(const DesktopMediaID& media_id,
                                        bool captured_surface_control_active);
  void ProcessStateChangeFromUI(const DesktopMediaID& media,
                                blink::mojom::MediaStreamStateChange);
  RenderFrameHostDelegate* GetRenderFrameHostDelegate(
      ChildProcessId render_process_id,
      int render_frame_id);

  base::WeakPtr<MediaStreamUIProxy> proxy_;
  std::unique_ptr<MediaStreamUI> ui_;

  bool tests_use_fake_render_frame_hosts_;
  const raw_ptr<RenderFrameHostDelegate> test_render_delegate_;

  base::WeakPtr<Core> weak_this_;

  base::WeakPtrFactory<Core> weak_factory_{this};

  // Used for calls supplied to `ui_`. Invalidated every time a new UI is
  // created.
  base::WeakPtrFactory<Core> weak_factory_for_ui_{this};
};

MediaStreamUIProxy::Core::Core(const base::WeakPtr<MediaStreamUIProxy>& proxy,
                               RenderFrameHostDelegate* test_render_delegate)
    : proxy_(proxy),
      tests_use_fake_render_frame_hosts_(test_render_delegate != nullptr),
      test_render_delegate_(test_render_delegate) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);
  weak_this_ = weak_factory_.GetWeakPtr();
}

MediaStreamUIProxy::Core::~Core() {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M155);
}

void MediaStreamUIProxy::Core::RequestAccess(
    std::unique_ptr<MediaStreamRequest> request) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M155);

  // TODO(crbug.com/379869738) Remove FromUnsafeValue.
  RenderFrameHostDelegate* render_delegate = GetRenderFrameHostDelegate(
      ChildProcessId::FromUnsafeValue(request->render_process_id),
      request->render_frame_id);

  // Tab may have gone away, or has no delegate from which to request access.
  if (!render_delegate) {
    ProcessAccessRequestResponse(request->render_process_id,
                                 request->render_frame_id, request->url_origin,
                                 blink::mojom::StreamDevicesSet(),
                                 blink::mojom::MediaStreamRequestResult::
                                     FAILED_DUE_TO_SHUTDOWN_NO_DELEGATE,
                                 std::unique_ptr<MediaStreamUI>());
    return;
  }

  RenderFrameHostImpl* host = RenderFrameHostImpl::FromID(
      request->render_process_id, request->render_frame_id);

  render_delegate->RequestMediaAccessPermission(
      host, *request,
      base::BindOnce(&Core::ProcessAccessRequestResponse, weak_this_,
                     request->render_process_id, request->render_frame_id,
                     request->url_origin));
}

void MediaStreamUIProxy::Core::RequestSelectAudioOutput(
    std::unique_ptr<SelectAudioOutputRequest> request,
    SelectAudioOutputCallback callback) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M155);

  RenderFrameHostDelegate* render_delegate = GetRenderFrameHostDelegate(
      request->render_frame_host_id().child_id,
      request->render_frame_host_id().frame_routing_id);
  if (!render_delegate) {
    content::GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback),
                                  base::unexpected(
                                      SelectAudioOutputError::kNotSupported)));
    return;
  }

  render_delegate->ProcessSelectAudioOutput(*request, std::move(callback));
}

void MediaStreamUIProxy::Core::OnStarted(
    gfx::NativeViewId* window_id,
    bool has_source_callback,
    const std::string& label,
    std::vector<DesktopMediaID> screen_share_ids) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M155);

  if (!ui_)
    return;

  MediaStreamUI::SourceCallback device_change_cb;
  if (has_source_callback) {
    device_change_cb =
        base::BindRepeating(&Core::ProcessChangeSourceRequestFromUI,
                            weak_factory_for_ui_.GetWeakPtr());
  }

  *window_id =
      ui_->OnStarted(base::BindRepeating(&Core::ProcessStopRequestFromUI,
                                         weak_factory_for_ui_.GetWeakPtr()),
                     device_change_cb, label, screen_share_ids,
                     base::BindRepeating(&Core::ProcessStateChangeFromUI,
                                         weak_factory_for_ui_.GetWeakPtr()));
}

void MediaStreamUIProxy::Core::OnDeviceStopped(const std::string& label,
                                               const DesktopMediaID& media_id) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M155);
  if (ui_) {
    ui_->OnDeviceStopped(label, media_id);
  }
}

void MediaStreamUIProxy::Core::OnDeviceStoppedForSourceChange(
    const std::string& label,
    const DesktopMediaID& old_media_id,
    const DesktopMediaID& new_media_id,
    bool captured_surface_control_active) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M155);
  if (ui_) {
    ui_->OnDeviceStoppedForSourceChange(label, old_media_id, new_media_id,
                                        captured_surface_control_active);
    ui_->OnDeviceStopped(label, old_media_id);
  }
}

void MediaStreamUIProxy::Core::OnRegionCaptureRectChanged(
    const std::optional<gfx::Rect>& region_capture_rec) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M155);
  if (ui_) {
    ui_->OnRegionCaptureRectChanged(region_capture_rec);
  }
}

void MediaStreamUIProxy::Core::SetFocus(const DesktopMediaID& media_id,
                                        bool focus,
                                        bool is_from_microtask,
                                        bool is_from_timer) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M155);
  if (ui_) {
    ui_->SetFocus(media_id, focus, is_from_microtask, is_from_timer);
  }
}

void MediaStreamUIProxy::Core::ProcessAccessRequestResponseForPostTask(
    int render_process_id,
    int render_frame_id,
    const url::Origin& request_origin,
    blink::mojom::StreamDevicesSetPtr stream_devices_set_ptr,
    blink::mojom::MediaStreamRequestResult result,
    std::unique_ptr<MediaStreamUI> stream_ui) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M155);
  CHECK(stream_devices_set_ptr, base::NotFatalUntil::M155);
  ProcessAccessRequestResponse(render_process_id, render_frame_id,
                               request_origin, *stream_devices_set_ptr, result,
                               std::move(stream_ui));
}

void MediaStreamUIProxy::Core::ProcessAccessRequestResponse(
    int render_process_id,
    int render_frame_id,
    const url::Origin& request_origin,
    const blink::mojom::StreamDevicesSet& stream_devices_set,
    blink::mojom::MediaStreamRequestResult result,
    std::unique_ptr<MediaStreamUI> stream_ui) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M155);
  CHECK((result != blink::mojom::MediaStreamRequestResult::OK &&
         stream_devices_set.stream_devices.empty()) ||
            (result == blink::mojom::MediaStreamRequestResult::OK &&
             stream_devices_set.stream_devices.size() == 1u),
        base::NotFatalUntil::M155);

  blink::mojom::StreamDevicesSetPtr filtered_devices_set =
      blink::mojom::StreamDevicesSet::New();
  blink::mojom::StreamDevices devices;
  if (!stream_devices_set.stream_devices.empty()) {
    devices = *stream_devices_set.stream_devices[0];
    filtered_devices_set->stream_devices.emplace_back(
        blink::mojom::StreamDevices::New());
  }

  auto* host = RenderFrameHostImpl::FromID(render_process_id, render_frame_id);
  bool is_allowed_while_screen_locked = false;
  if (result == blink::mojom::MediaStreamRequestResult::OK &&
      devices.video_device.has_value() &&
      devices.video_device->type ==
          blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) {
    is_allowed_while_screen_locked =
        GetContentClient()->browser()->IsVideoCaptureAllowedWhileScreenLocked(
            request_origin);
  }

  if (devices.audio_device.has_value()) {
    const blink::MediaStreamDevice& audio_device = devices.audio_device.value();
    if (audio_device.type !=
            blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE ||
        IsFeatureEnabled(
            host, tests_use_fake_render_frame_hosts_,
            network::mojom::PermissionsPolicyFeature::kMicrophone)) {
      filtered_devices_set->stream_devices[0]->audio_device = audio_device;
    }
  }

  if (devices.video_device.has_value()) {
    const blink::MediaStreamDevice& video_device = devices.video_device.value();
    if (video_device.type !=
            blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE ||
        IsFeatureEnabled(host, tests_use_fake_render_frame_hosts_,
                         network::mojom::PermissionsPolicyFeature::kCamera)) {
      filtered_devices_set->stream_devices[0]->video_device = video_device;
    }
  }

  if ((filtered_devices_set->stream_devices.empty() ||
       (!filtered_devices_set->stream_devices[0]->audio_device.has_value() &&
        !filtered_devices_set->stream_devices[0]->video_device.has_value())) &&
      result == blink::mojom::MediaStreamRequestResult::OK) {
    result = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
    filtered_devices_set->stream_devices.clear();
  }

  if (stream_ui) {
    // Callbacks that were supplied to the existing `ui_` are no longer
    // applicable. This is important as some implementions (TabSharingUIViews)
    // always run the callback when destroyed. However at the point the UI is
    // replaced while the screencast is ongoing. Invalidating ensures the
    // screencast is not terminated. See crbug.com/1155426 for details.
    weak_factory_for_ui_.InvalidateWeakPtrs();
    ui_ = std::move(stream_ui);
  }

  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&MediaStreamUIProxy::ProcessAccessRequestResponse, proxy_,
                     std::move(filtered_devices_set), result,
                     is_allowed_while_screen_locked));
}

void MediaStreamUIProxy::Core::ProcessStopRequestFromUI() {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M155);

  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&MediaStreamUIProxy::ProcessStopRequestFromUI, proxy_));
}

void MediaStreamUIProxy::Core::ProcessChangeSourceRequestFromUI(
    const DesktopMediaID& media_id,
    bool captured_surface_control_active) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M155);

  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&MediaStreamUIProxy::ProcessChangeSourceRequestFromUI,
                     proxy_, media_id, captured_surface_control_active));
}

void MediaStreamUIProxy::Core::ProcessStateChangeFromUI(
    const DesktopMediaID& media_id,
    blink::mojom::MediaStreamStateChange new_state) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M155);
  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(&MediaStreamUIProxy::ProcessStateChangeFromUI,
                                proxy_, media_id, new_state));
}

RenderFrameHostDelegate* MediaStreamUIProxy::Core::GetRenderFrameHostDelegate(
    ChildProcessId render_process_id,
    int render_frame_id) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M155);
  if (test_render_delegate_)
    return test_render_delegate_;
  RenderFrameHostImpl* host =
      RenderFrameHostImpl::FromID(render_process_id, render_frame_id);
  return host ? host->delegate() : nullptr;
}

// static
std::unique_ptr<MediaStreamUIProxy> MediaStreamUIProxy::Create() {
  return std::unique_ptr<MediaStreamUIProxy>(new MediaStreamUIProxy(nullptr));
}

// static
std::unique_ptr<MediaStreamUIProxy> MediaStreamUIProxy::CreateForTests(
    RenderFrameHostDelegate* render_delegate) {
  return std::unique_ptr<MediaStreamUIProxy>(
      new MediaStreamUIProxy(render_delegate));
}

MediaStreamUIProxy::MediaStreamUIProxy(
    RenderFrameHostDelegate* test_render_delegate) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);
  core_.reset(new Core(weak_factory_.GetWeakPtr(), test_render_delegate));
}

MediaStreamUIProxy::~MediaStreamUIProxy() {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);
}

void MediaStreamUIProxy::RequestAccess(
    std::unique_ptr<MediaStreamRequest> request,
    ResponseCallback response_callback) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);

  response_callback_ = std::move(response_callback);
  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(&Core::RequestAccess, core_->GetWeakPtr(),
                                std::move(request)));
}

void MediaStreamUIProxy::RequestSelectAudioOutput(
    std::unique_ptr<SelectAudioOutputRequest> request,
    SelectAudioOutputCallback callback) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);

  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&Core::RequestSelectAudioOutput, core_->GetWeakPtr(),
                     std::move(request), std::move(callback)));
}

void MediaStreamUIProxy::OnStarted(
    base::OnceClosure stop_callback,
    MediaStreamUI::SourceCallback source_callback,
    WindowIdCallback window_id_callback,
    const std::string& label,
    std::vector<DesktopMediaID> screen_share_ids,
    MediaStreamUI::StateChangeCallback state_change_callback) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);

  stop_callback_ = std::move(stop_callback);
  source_callback_ = std::move(source_callback);
  state_change_callback_ = std::move(state_change_callback);

  // Owned by the PostTaskAndReply callback.
  gfx::NativeViewId* window_id = new gfx::NativeViewId(0);

  GetUIThreadTaskRunner({})->PostTaskAndReply(
      FROM_HERE,
      base::BindOnce(&Core::OnStarted, core_->GetWeakPtr(), window_id,
                     !!source_callback_, label, screen_share_ids),
      base::BindOnce(&MediaStreamUIProxy::OnWindowId,
                     weak_factory_.GetWeakPtr(), std::move(window_id_callback),
                     base::Owned(window_id)));
}

void MediaStreamUIProxy::OnDeviceStopped(const std::string& label,
                                         const DesktopMediaID& media_id) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);

  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(&Core::OnDeviceStopped, core_->GetWeakPtr(),
                                label, media_id));
}

void MediaStreamUIProxy::OnDeviceStoppedForSourceChange(
    const std::string& label,
    const DesktopMediaID& old_media_id,
    const DesktopMediaID& new_media_id,
    bool captured_surface_control_active) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);

  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(&Core::OnDeviceStoppedForSourceChange,
                                core_->GetWeakPtr(), label, old_media_id,
                                new_media_id, captured_surface_control_active));
}

void MediaStreamUIProxy::OnRegionCaptureRectChanged(
    const std::optional<gfx::Rect>& region_capture_rec) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);

  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(&Core::OnRegionCaptureRectChanged,
                                core_->GetWeakPtr(), region_capture_rec));
}

void MediaStreamUIProxy::SetFocus(const DesktopMediaID& media_id,
                                  bool focus,
                                  bool is_from_microtask,
                                  bool is_from_timer) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);

  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(&Core::SetFocus, core_->GetWeakPtr(), media_id,
                                focus, is_from_microtask, is_from_timer));
}

void MediaStreamUIProxy::ProcessAccessRequestResponse(
    blink::mojom::StreamDevicesSetPtr stream_devices_set,
    blink::mojom::MediaStreamRequestResult result,
    bool is_allowed_while_screen_locked) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);

  if (response_callback_) {
    std::move(response_callback_)
        .Run(*stream_devices_set, result, is_allowed_while_screen_locked);
  }
}

void MediaStreamUIProxy::ProcessStopRequestFromUI() {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);
  // Careful when changing the following lines: upstream, this function is
  // wrapped into a RepeatingClosure, which allows duplicating it and enabling
  // multiple potentital sources to stop the stream; however only the first
  // invocation should actually stop the stream.
  if (stop_callback_)
    std::move(stop_callback_).Run();
}

void MediaStreamUIProxy::ProcessChangeSourceRequestFromUI(
    const DesktopMediaID& media_id,
    bool captured_surface_control_active) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);

  if (source_callback_)
    source_callback_.Run(media_id, captured_surface_control_active);
}

void MediaStreamUIProxy::ProcessStateChangeFromUI(
    const DesktopMediaID& media_id,
    blink::mojom::MediaStreamStateChange new_state) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);

  if (state_change_callback_)
    state_change_callback_.Run(media_id, new_state);
}

void MediaStreamUIProxy::OnWindowId(WindowIdCallback window_id_callback,
                                    gfx::NativeViewId* window_id) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);
  if (!window_id_callback.is_null())
    std::move(window_id_callback).Run(*window_id);
}

FakeMediaStreamUIProxy::FakeMediaStreamUIProxy(
    bool tests_use_fake_render_frame_hosts)
    : MediaStreamUIProxy(nullptr) {
  core_->tests_use_fake_render_frame_hosts_ = tests_use_fake_render_frame_hosts;
}

FakeMediaStreamUIProxy::~FakeMediaStreamUIProxy() = default;

void FakeMediaStreamUIProxy::AddAvailableDevices(
    const blink::MediaStreamDevices& devices) {
  devices_.insert(devices_.end(), devices.begin(), devices.end());
}

void FakeMediaStreamUIProxy::SetMicAccess(bool access) {
  mic_access_ = access;
}

void FakeMediaStreamUIProxy::SetCameraAccess(bool access) {
  camera_access_ = access;
}

void FakeMediaStreamUIProxy::SetAudioShare(bool audio_share) {
  audio_share_ = audio_share;
}

void FakeMediaStreamUIProxy::RequestAccess(
    std::unique_ptr<MediaStreamRequest> request,
    ResponseCallback response_callback) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M155);

  response_callback_ = std::move(response_callback);

  if (base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
          switches::kUseFakeUIForMediaStream) == "deny") {
    // Immediately deny the request.
    GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(
            &MediaStreamUIProxy::Core::ProcessAccessRequestResponseForPostTask,
            core_->GetWeakPtr(), request->render_process_id,
            request->render_frame_id, request->url_origin,
            blink::mojom::StreamDevicesSet::New(),
            blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
            std::unique_ptr<MediaStreamUI>()));
    return;
  }

  // Use the first capture device of the same media type in the list for the
  // fake UI.
  blink::mojom::StreamDevicesSetPtr devices_set_to_use =
      blink::mojom::StreamDevicesSet::New();
  devices_set_to_use->stream_devices.emplace_back(
      blink::mojom::StreamDevices::New());
  blink::mojom::StreamDevices& devices_to_use =
      *devices_set_to_use->stream_devices[0];
  for (const blink::MediaStreamDevice& device : devices_) {
    if (!devices_to_use.audio_device.has_value() &&
        blink::IsAudioInputMediaType(request->audio_type) &&
        blink::IsAudioInputMediaType(device.type) &&
        (request->requested_audio_device_ids.empty() ||
         request->requested_audio_device_ids.front().empty() ||
         request->requested_audio_device_ids.front() == device.id)) {
      devices_to_use.audio_device = device;
    } else if (!devices_to_use.video_device.has_value() &&
               blink::IsVideoInputMediaType(request->video_type) &&
               blink::IsVideoInputMediaType(device.type) &&
               (request->requested_video_device_ids.empty() ||
                request->requested_video_device_ids.front().empty() ||
                request->requested_video_device_ids.front() == device.id)) {
      devices_to_use.video_device = device;
    }
  }

  // Fail the request if a device doesn't exist for the requested type.
  if ((request->audio_type != blink::mojom::MediaStreamType::NO_SERVICE &&
       !devices_to_use.audio_device.has_value()) ||
      (request->video_type != blink::mojom::MediaStreamType::NO_SERVICE &&
       !devices_to_use.video_device.has_value())) {
    devices_to_use = blink::mojom::StreamDevices();
  }

  if (!audio_share_) {
    devices_to_use.audio_device = std::nullopt;
  }
  const bool is_devices_empty = !devices_to_use.audio_device.has_value() &&
                                !devices_to_use.video_device.has_value();
  if (is_devices_empty) {
    devices_set_to_use->stream_devices.clear();
  }
  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          &MediaStreamUIProxy::Core::ProcessAccessRequestResponseForPostTask,
          core_->GetWeakPtr(), request->render_process_id,
          request->render_frame_id, request->url_origin,
          std::move(devices_set_to_use),
          is_devices_empty ? blink::mojom::MediaStreamRequestResult::NO_HARDWARE
                           : blink::mojom::MediaStreamRequestResult::OK,
          std::unique_ptr<MediaStreamUI>()));
}

void FakeMediaStreamUIProxy::RequestSelectAudioOutput(
    std::unique_ptr<SelectAudioOutputRequest> request,
    SelectAudioOutputCallback callback) {
  std::move(callback).Run(
      base::unexpected(content::SelectAudioOutputError::kNoPermission));
}

void FakeMediaStreamUIProxy::OnStarted(
    base::OnceClosure stop_callback,
    MediaStreamUI::SourceCallback source_callback,
    WindowIdCallback window_id_callback,
    const std::string& label,
    std::vector<DesktopMediaID> screen_share_ids,
    MediaStreamUI::StateChangeCallback state_change_callback) {}

void FakeMediaStreamUIProxy::OnDeviceStopped(const std::string& label,
                                             const DesktopMediaID& media_id) {}

void FakeMediaStreamUIProxy::OnDeviceStoppedForSourceChange(
    const std::string& label,
    const DesktopMediaID& old_media_id,
    const DesktopMediaID& new_media_id,
    bool captured_surface_control_active) {}

}  // namespace content
