chromium/chrome/browser/chromeos/video_conference/video_conference_media_listener.cc

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

#include "chrome/browser/chromeos/video_conference/video_conference_media_listener.h"

#include <string>
#include <utility>

#include "base/memory/scoped_refptr.h"
#include "chrome/browser/chromeos/video_conference/video_conference_manager_client_common.h"
#include "chrome/browser/chromeos/video_conference/video_conference_web_app.h"
#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
#include "chromeos/crosapi/mojom/video_conference.mojom.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_user_data.h"

namespace video_conference {

VideoConferenceMediaListener::VideoConferenceMediaListener(
    base::RepeatingCallback<void()> media_usage_update_callback,
    base::RepeatingCallback<VideoConferenceWebApp*(content::WebContents*)>
        create_vc_web_app_callback,
    base::RepeatingCallback<void(crosapi::mojom::VideoConferenceMediaDevice,
                                 const std::u16string&)>
        device_used_while_disabled_callback)
    : media_usage_update_callback_(std::move(media_usage_update_callback)),
      create_vc_web_app_callback_(std::move(create_vc_web_app_callback)),
      device_used_while_disabled_callback_(
          std::move(device_used_while_disabled_callback)) {
  observation_.Observe(MediaCaptureDevicesDispatcher::GetInstance()
                           ->GetMediaStreamCaptureIndicator()
                           .get());
}

VideoConferenceMediaListener::~VideoConferenceMediaListener() = default;

void VideoConferenceMediaListener::SetSystemMediaDeviceStatus(
    crosapi::mojom::VideoConferenceMediaDevice device,
    bool disabled) {
  switch (device) {
    case crosapi::mojom::VideoConferenceMediaDevice::kCamera:
      camera_system_disabled_ = disabled;
      break;
    case crosapi::mojom::VideoConferenceMediaDevice::kMicrophone:
      microphone_system_disabled_ = disabled;
      break;
    case crosapi::mojom::VideoConferenceMediaDevice::kUnusedDefault:
      return;
  }
}

void VideoConferenceMediaListener::OnIsCapturingVideoChanged(
    content::WebContents* contents,
    bool is_capturing_video) {
  VideoConferenceWebApp* vc_app =
      GetOrCreateVcWebApp(contents, is_capturing_video);
  // It is normal for `vc_app` to be a nullptr, e.g. when this method is called
  // upon the deletion of a `VideoConferenceWebApp` with an is_capturing of
  // false.
  if (vc_app) {
    auto& state = vc_app->state();
    bool prev_is_capturing_video = state.is_capturing_camera;
    vc_app->SetCapturingStatus(VideoConferenceMediaType::kCamera,
                               is_capturing_video);

    // Remove `vc_app` from client if it belongs to an extension which has
    // stopped capturing.
    if (vc_app->IsInactiveExtension()) {
      vc_app->WebContentsDestroyed();
      return;
    }

    media_usage_update_callback_.Run();

    // This will be an AnchoredNudge, which is only visible if the tray is
    // visible; so we have to call this after media_usage_update_callback_.
    if (camera_system_disabled_ && !prev_is_capturing_video &&
        is_capturing_video) {
      device_used_while_disabled_callback_.Run(
          crosapi::mojom::VideoConferenceMediaDevice::kCamera,
          contents->GetTitle());
    }
  }
}

void VideoConferenceMediaListener::OnIsCapturingAudioChanged(
    content::WebContents* contents,
    bool is_capturing_audio) {
  VideoConferenceWebApp* vc_app =
      GetOrCreateVcWebApp(contents, is_capturing_audio);

  // It is normal for `vc_app` to be a nullptr, e.g. when this method is called
  // upon the deletion of a `VideoConferenceWebApp` with an is_capturing of
  // false.
  if (vc_app) {
    auto& state = vc_app->state();
    bool prev_is_capturing_audio = state.is_capturing_microphone;
    vc_app->SetCapturingStatus(VideoConferenceMediaType::kMicrophone,
                               is_capturing_audio);

    // Remove `vc_app` from client if it belongs to an extension which has
    // stopped capturing.
    if (vc_app->IsInactiveExtension()) {
      vc_app->WebContentsDestroyed();
      return;
    }

    media_usage_update_callback_.Run();

    // This will be an AnchoredNudge, which is only visible if the tray is
    // visible; so we have to call this after media_usage_update_callback_.
    if (microphone_system_disabled_ && !prev_is_capturing_audio &&
        is_capturing_audio) {
      device_used_while_disabled_callback_.Run(
          crosapi::mojom::VideoConferenceMediaDevice::kMicrophone,
          contents->GetTitle());
    }
  }
}

void VideoConferenceMediaListener::OnIsCapturingWindowChanged(
    content::WebContents* contents,
    bool is_capturing_window) {
  // We don't distinguish between window and display capture and put them
  // together into 'screen capturing'.
  OnIsCapturingScreenChanged(contents, is_capturing_window);
}

void VideoConferenceMediaListener::OnIsCapturingDisplayChanged(
    content::WebContents* contents,
    bool is_capturing_display) {
  // We don't distinguish between window and display capture and put them
  // together into 'screen capturing'.
  OnIsCapturingScreenChanged(contents, is_capturing_display);
}

void VideoConferenceMediaListener::OnIsCapturingScreenChanged(
    content::WebContents* contents,
    bool is_capturing_screen) {
  VideoConferenceWebApp* vc_app =
      GetOrCreateVcWebApp(contents, is_capturing_screen);

  // It is normal for `vc_app` to be a nullptr, e.g. when this method is called
  // upon the deletion of a `VideoConferenceWebApp` with an is_capturing of
  // false.
  if (vc_app) {
    vc_app->SetCapturingStatus(VideoConferenceMediaType::kScreen,
                               is_capturing_screen);

    // Remove `vc_app` from client if it belongs to an extension which has
    // stopped capturing.
    if (vc_app->IsInactiveExtension()) {
      vc_app->WebContentsDestroyed();
      return;
    }

    media_usage_update_callback_.Run();
  }
}

VideoConferenceWebApp* VideoConferenceMediaListener::GetOrCreateVcWebApp(
    content::WebContents* contents,
    bool is_capturing) {
  auto* vc_app =
      content::WebContentsUserData<VideoConferenceWebApp>::FromWebContents(
          contents);

  // Create and add a |VideoConferenceWebApp| for this webcontents on the
  // client if it doesn't exist.
  if (!vc_app) {
    // A new VcWebApp should only be created the first time an app starts
    // capturing. For example, we do not want to create a new VcWebApp if an old
    // one is closed and that causes an OnIsCapturingXChanged to trigger with a
    // capturing value of false.
    if (!is_capturing) {
      return nullptr;
    }

    if (ShouldSkipId(contents->GetURL().host())) {
      return nullptr;
    }

    vc_app = create_vc_web_app_callback_.Run(contents);
  }

  return vc_app;
}

}  // namespace video_conference