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

#include "content/browser/media/key_system_support_impl.h"

#include <vector>

#include "base/command_line.h"
#include "base/containers/flat_set.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_split.h"
#include "content/public/browser/cdm_registry.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/cdm_info.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "media/base/key_system_names.h"
#include "media/base/key_systems.h"
#include "media/base/media_switches.h"
#include "media/mojo/buildflags.h"
#include "mojo/public/cpp/bindings/strong_binding.h"

namespace content {

namespace {

void SendCdmAvailableUMA(const std::string& key_system, bool available) {
  base::UmaHistogramBoolean("Media.EME." +
                                media::GetKeySystemNameForUMA(key_system) +
                                ".LibraryCdmAvailable",
                            available);
}

template <typename T>
std::vector<T> SetToVector(const base::flat_set<T>& s) {
  return std::vector<T>(s.begin(), s.end());
}

// Returns whether hardware secure codecs are enabled from command line. If
// true, |video_codecs| are filled with codecs specified on command line, which
// could be empty if no codecs are specified. If false, |video_codecs| will not
// be modified.
bool IsHardwareSecureCodecsOverriddenFromCommandLine(
    std::vector<media::VideoCodec>* video_codecs,
    std::vector<media::EncryptionMode>* encryption_schemes) {
  DCHECK(video_codecs->empty());
  DCHECK(encryption_schemes->empty());

  auto* command_line = base::CommandLine::ForCurrentProcess();
  if (!command_line || !command_line->HasSwitch(
                           switches::kOverrideHardwareSecureCodecsForTesting)) {
    return false;
  }

  auto codecs_string = command_line->GetSwitchValueASCII(
      switches::kOverrideHardwareSecureCodecsForTesting);
  const auto supported_codecs = base::SplitStringPiece(
      codecs_string, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);

  for (const auto& codec : supported_codecs) {
    if (codec == "vp8")
      video_codecs->push_back(media::VideoCodec::kCodecVP8);
    else if (codec == "vp9")
      video_codecs->push_back(media::VideoCodec::kCodecVP9);
    else if (codec == "avc1")
      video_codecs->push_back(media::VideoCodec::kCodecH264);
    else
      DVLOG(1) << "Unsupported codec specified on command line: " << codec;
  }

  // Codecs enabled from command line assumes CENC support.
  if (!video_codecs->empty())
    encryption_schemes->push_back(media::EncryptionMode::kCenc);

  return true;
}

void GetHardwareSecureDecryptionCaps(
    const std::string& key_system,
    const base::flat_set<media::CdmProxy::Protocol>& cdm_proxy_protocols,
    std::vector<media::VideoCodec>* video_codecs,
    std::vector<media::EncryptionMode>* encryption_schemes) {
  DCHECK(video_codecs->empty());
  DCHECK(encryption_schemes->empty());

  if (!base::FeatureList::IsEnabled(media::kHardwareSecureDecryption)) {
    DVLOG(1) << "Hardware secure decryption disabled";
    return;
  }

  // Secure codecs override takes precedence over other checks.
  if (IsHardwareSecureCodecsOverriddenFromCommandLine(video_codecs,
                                                      encryption_schemes)) {
    DVLOG(1) << "Hardware secure codecs overridden from command line";
    return;
  }

  if (cdm_proxy_protocols.empty()) {
    DVLOG(1) << "CDM does not support any CdmProxy protocols";
    return;
  }

  // Hardware secure video codecs need hardware video decoder support.
  // TODO(xhwang): Make sure this check is as close as possible to the check
  // in the render process. For example, also check check GPU features like
  // GPU_FEATURE_TYPE_ACCELERATED_VIDEO_DECODE.
  auto* command_line = base::CommandLine::ForCurrentProcess();
  if (command_line &&
      command_line->HasSwitch(switches::kDisableAcceleratedVideoDecode)) {
    DVLOG(1) << "Hardware secure codecs not supported because accelerated "
                "video decode disabled";
    return;
  }

#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
  if (!base::FeatureList::IsEnabled(media::kMojoVideoDecoder)) {
    DVLOG(1) << "Hardware secure codecs not supported because mojo video "
                "decode was disabled at runtime";
    return;
  }
#else
  DVLOG(1) << "Hardware secure codecs not supported because mojo video "
              "decode was disabled at buildtime";
  return;
#endif

  base::flat_set<media::VideoCodec> video_codec_set;
  base::flat_set<media::EncryptionMode> encryption_scheme_set;

  GetContentClient()->browser()->GetHardwareSecureDecryptionCaps(
      key_system, cdm_proxy_protocols, &video_codec_set,
      &encryption_scheme_set);

  *video_codecs = SetToVector(video_codec_set);
  *encryption_schemes = SetToVector(encryption_scheme_set);
}

}  // namespace

// static
void KeySystemSupportImpl::Create(
    media::mojom::KeySystemSupportRequest request) {
  DVLOG(3) << __func__;
  // The created object is bound to (and owned by) |request|.
  mojo::MakeStrongBinding(std::make_unique<KeySystemSupportImpl>(),
                          std::move(request));
}

// static
std::unique_ptr<CdmInfo> KeySystemSupportImpl::GetCdmInfoForKeySystem(
    const std::string& key_system) {
  DVLOG(2) << __func__ << ": key_system = " << key_system;
  for (const auto& cdm : CdmRegistry::GetInstance()->GetAllRegisteredCdms()) {
    if (cdm.supported_key_system == key_system ||
        (cdm.supports_sub_key_systems &&
         media::IsChildKeySystemOf(key_system, cdm.supported_key_system))) {
      return std::make_unique<CdmInfo>(cdm);
    }
  }

  return nullptr;
}

KeySystemSupportImpl::KeySystemSupportImpl() = default;

KeySystemSupportImpl::~KeySystemSupportImpl() = default;

void KeySystemSupportImpl::IsKeySystemSupported(
    const std::string& key_system,
    IsKeySystemSupportedCallback callback) {
  DVLOG(3) << __func__ << ": key_system = " << key_system;

  auto cdm_info = GetCdmInfoForKeySystem(key_system);
  if (!cdm_info) {
    SendCdmAvailableUMA(key_system, false);
    std::move(callback).Run(false, nullptr);
    return;
  }

  SendCdmAvailableUMA(key_system, true);

  // Supported codecs and encryption schemes.
  auto capability = media::mojom::KeySystemCapability::New();
  capability->video_codecs = cdm_info->capability.video_codecs;
  capability->supports_vp9_profile2 =
      cdm_info->capability.supports_vp9_profile2;
  capability->encryption_schemes =
      SetToVector(cdm_info->capability.encryption_schemes);

  GetHardwareSecureDecryptionCaps(key_system,
                                  cdm_info->capability.cdm_proxy_protocols,
                                  &capability->hw_secure_video_codecs,
                                  &capability->hw_secure_encryption_schemes);

  capability->session_types = SetToVector(cdm_info->capability.session_types);

  std::move(callback).Run(true, std::move(capability));
}

}  // namespace content
