diff --git a/browser/BUILD.gn b/browser/BUILD.gn index 99b67927bdbb62ce3003e8637ff6e428813f6e95..70faf8daa126c1d52097e22252c491de5c2014e3 100644 --- a/browser/BUILD.gn +++ b/browser/BUILD.gn @@ -4422,6 +4422,20 @@ static_library("browser") { "safe_browsing/generated_safe_browsing_pref.h", ] } + + if (defined(ohos_enable_cdm) && ohos_enable_cdm) { + sources += [ + "media/ohos/cdm/media_drm_origin_id_manager.cc", + "media/ohos/cdm/media_drm_origin_id_manager.h", + "media/ohos/cdm/media_drm_origin_id_manager_factory.cc", + "media/ohos/cdm/media_drm_origin_id_manager_factory.h", + "media/ohos/cdm/media_drm_storage_factory.cc", + "media/ohos/cdm/media_drm_storage_factory.h", + ] + deps += [ + "//components/cdm/browser", + ] + } deps += [ ":browser_themes", ":theme_properties", diff --git a/browser/chrome_content_browser_client_receiver_bindings.cc b/browser/chrome_content_browser_client_receiver_bindings.cc index 8961cc1025a6faf92871083c7ac24558d41d3596..890648eef68e6ffc191cbfdf8f9d72db6486049b 100644 --- a/browser/chrome_content_browser_client_receiver_bindings.cc +++ b/browser/chrome_content_browser_client_receiver_bindings.cc @@ -93,6 +93,11 @@ #include "chrome/browser/media/android/cdm/media_drm_storage_factory.h" #endif +#if BUILDFLAG(ENABLE_MOJO_CDM) && \ + BUILDFLAG(IS_OHOS) && defined(OHOS_ENABLE_CDM) +#include "chrome/browser/media/ohos/cdm/media_drm_storage_factory.h" +#endif + #if BUILDFLAG(ENABLE_SPELLCHECK) #include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h" #include "components/spellcheck/common/spellcheck.mojom.h" @@ -376,6 +381,7 @@ void ChromeContentBrowserClient::ExposeInterfacesToRenderer( void ChromeContentBrowserClient::BindMediaServiceReceiver( content::RenderFrameHost* render_frame_host, mojo::GenericPendingReceiver receiver) { + LOG(INFO) << "[NEU][CDM]" << __func__; #if BUILDFLAG(ENABLE_LIBRARY_CDMS) if (auto r = receiver.As()) { OutputProtectionImpl::Create(render_frame_host, std::move(r)); @@ -390,12 +396,15 @@ void ChromeContentBrowserClient::BindMediaServiceReceiver( } #endif // BUILDFLAG(ENABLE_LIBRARY_CDMS) || BUILDFLAG(IS_WIN) -#if BUILDFLAG(ENABLE_MOJO_CDM) && BUILDFLAG(IS_ANDROID) +#if BUILDFLAG(ENABLE_MOJO_CDM) && (BUILDFLAG(IS_ANDROID) || \ + (BUILDFLAG(IS_OHOS) && defined(OHOS_ENABLE_CDM))) if (auto r = receiver.As()) { + LOG(INFO) << "[NEU][CDM]" << __func__; CreateMediaDrmStorage(render_frame_host, std::move(r)); return; } #endif + LOG(INFO) << "[NEU][CDM]" << __func__; } void ChromeContentBrowserClient::RegisterBrowserInterfaceBindersForFrame( diff --git a/browser/component_updater/media_foundation_widevine_cdm_component_installer.cc b/browser/component_updater/media_foundation_widevine_cdm_component_installer.cc index c58663b587b03cbf827e073e73b773e6a88dd1a4..0ca5484d14edce203a1faebab25d0ba97c3c7665 100644 --- a/browser/component_updater/media_foundation_widevine_cdm_component_installer.cc +++ b/browser/component_updater/media_foundation_widevine_cdm_component_installer.cc @@ -126,8 +126,9 @@ void MediaFoundationWidevineCdmComponentInstallerPolicy::ComponentReady( } else { base::UmaHistogramBoolean("Media.EME.Widevine.HardwareSecure.Pref", true); } - + LOG(INFO) << "[NEU][CDM]" << __func__; content::CdmRegistry::GetInstance()->RegisterCdm(cdm_info); + LOG(INFO) << "[NEU][CDM]" << __func__; } // Called during startup and installation before ComponentReady(). diff --git a/browser/component_updater/widevine_cdm_component_installer.cc b/browser/component_updater/widevine_cdm_component_installer.cc index 28c39cba6d5d1bbef4813c0268e7970f217f6626..dd6ab72c71765f73081e610c0dfc2b5976d1e17f 100644 --- a/browser/component_updater/widevine_cdm_component_installer.cc +++ b/browser/component_updater/widevine_cdm_component_installer.cc @@ -124,7 +124,9 @@ void RegisterWidevineCdmWithChrome(const base::Version& cdm_version, kWidevineKeySystem, content::CdmInfo::Robustness::kSoftwareSecure, std::move(capability), /*supports_sub_key_systems=*/false, kWidevineCdmDisplayName, kWidevineCdmType, cdm_version, cdm_path); + LOG(INFO) << "[NEU][CDM]" << __func__; CdmRegistry::GetInstance()->RegisterCdm(cdm_info); + LOG(INFO) << "[NEU][CDM]" << __func__; } #endif // !BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS) @@ -276,9 +278,11 @@ void WidevineCdmComponentInstallerPolicy::UpdateCdmPath( } void RegisterWidevineCdmComponent(ComponentUpdateService* cus) { + LOG(INFO) << "[NEU][CDM]" << __func__; auto installer = base::MakeRefCounted( std::make_unique()); installer->Register(cus, base::OnceClosure()); + LOG(INFO) << "[NEU][CDM]" << __func__; } } // namespace component_updater diff --git a/browser/media/android/cdm/media_drm_storage_factory.cc b/browser/media/android/cdm/media_drm_storage_factory.cc index 5fe815839b7e1500edf8885ac666083ae130be80..53004cdd75232b9897b78b779c1cc768d65b8141 100644 --- a/browser/media/android/cdm/media_drm_storage_factory.cc +++ b/browser/media/android/cdm/media_drm_storage_factory.cc @@ -72,9 +72,11 @@ void ReportStatusToUmaAndNotifyCaller(OriginIdReadyCB callback, void CreateOriginIdWithMediaDrmOriginIdManager(Profile* profile, OriginIdReadyCB callback) { + LOG(INFO) << "[NEU][CDM]" << __func__; auto* origin_id_manager = MediaDrmOriginIdManagerFactory::GetForProfile(profile); if (!origin_id_manager) { + LOG(INFO) << "[NEU][CDM]" << __func__; ReportResultToUma(GetOriginIdResult::kFailureWithNoFactory); std::move(callback).Run(false, absl::nullopt); return; @@ -85,11 +87,13 @@ void CreateOriginIdWithMediaDrmOriginIdManager(Profile* profile, } void CreateOriginId(OriginIdReadyCB callback) { + LOG(INFO) << "[NEU][CDM]" << __func__; auto origin_id = base::UnguessableToken::Create(); DVLOG(2) << __func__ << ": origin_id = " << origin_id; - + LOG(INFO) << "[NEU][CDM]" << __func__ << ": origin_id = " << origin_id; ReportResultToUma(GetOriginIdResult::kSuccessWithUnprovisionedOriginId); std::move(callback).Run(true, origin_id); + LOG(INFO) << "[NEU][CDM]" << __func__; } void AllowEmptyOriginId(content::RenderFrameHost* render_frame_host, @@ -111,6 +115,7 @@ void AllowEmptyOriginId(content::RenderFrameHost* render_frame_host, void CreateMediaDrmStorage( content::RenderFrameHost* render_frame_host, mojo::PendingReceiver receiver) { + LOG(INFO) << "[NEU][CDM]" << __func__; DVLOG(1) << __func__; DCHECK_CURRENTLY_ON(content::BrowserThread::UI); CHECK(render_frame_host); @@ -132,6 +137,8 @@ void CreateMediaDrmStorage( // Only use MediaDrmOriginIdManager's preprovisioned origin IDs when feature // kMediaDrmPreprovisioning is enabled. + + LOG(INFO) << "[NEU][CDM]" << __func__; auto get_origin_id_cb = base::FeatureList::IsEnabled(media::kMediaDrmPreprovisioning) ? base::BindRepeating(&CreateOriginIdWithMediaDrmOriginIdManager, @@ -140,6 +147,7 @@ void CreateMediaDrmStorage( // The object will be deleted on connection error, or when the frame navigates // away. See DocumentService for details. + LOG(INFO) << "[NEU][CDM]" << __func__; new cdm::MediaDrmStorageImpl( *render_frame_host, pref_service, get_origin_id_cb, base::BindRepeating(&AllowEmptyOriginId, render_frame_host), diff --git a/browser/media/ohos/cdm/media_drm_origin_id_manager.cc b/browser/media/ohos/cdm/media_drm_origin_id_manager.cc new file mode 100644 index 0000000000000000000000000000000000000000..54fdc1a6b55a0fdfbbaec6a764ddb67be4bc1c71 --- /dev/null +++ b/browser/media/ohos/cdm/media_drm_origin_id_manager.cc @@ -0,0 +1,526 @@ +// Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media/ohos/cdm/media_drm_origin_id_manager.h" + +#include +#include + +#include "base/feature_list.h" +#include "base/functional/bind.h" +#include "base/json/values_util.h" +#include "base/logging.h" +#include "base/memory/raw_ptr.h" +#include "base/memory/scoped_refptr.h" +#include "base/metrics/histogram_macros.h" +#include "base/task/bind_post_task.h" +#include "base/task/single_thread_task_runner.h" +#include "base/task/task_traits.h" +#include "base/task/thread_pool.h" +#include "base/time/time.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/net/system_network_context_manager.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "content/public/browser/network_service_instance.h" +#include "content/public/browser/provision_fetcher_factory.h" +#include "media/base/media_switches.h" +#include "media/base/ohos/ohos_media_drm_bridge.h" +#include "media/base/provision_fetcher.h" +#include "services/network/public/cpp/network_connection_tracker.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" +#include "third_party/widevine/cdm/widevine_cdm_common.h" + +// The storage will be managed by PrefService. All data will be stored in a +// dictionary under the key "media.media_drm_origin_ids". The dictionary is +// structured as follows: +// +// { +// "origin_ids": [ $origin_id, ... ] +// "expirable_token": $expiration_time, +// } +// +// If specified, "expirable_token" is stored as a string representing the +// int64_t (base::NumberToString()) form of the number of microseconds since +// Windows epoch (1601-01-01 00:00:00 UTC). It is the latest time that this +// code should attempt to pre-provision more origins on some devices. + +namespace { + +const char kMediaDrmOriginIds[] = "media.media_drm_origin_ids"; +const char kExpirableToken[] = "expirable_token"; +const char kOriginIds[] = "origin_ids"; + +// The maximum number of origin IDs to pre-provision. Chosen to be small to +// minimize provisioning server load. +// TODO(jrummell): Adjust this value if needed after initial launch. +constexpr int kMaxPreProvisionedOriginIds = 2; + +// The maximum number of origin IDs logged to UMA. +constexpr int kUMAMaxPreProvisionedOriginIds = 10; + +// "expirable_token" is only good for 24 hours. +constexpr base::TimeDelta kExpirationDelta = base::Hours(24); + +// Time to wait before attempting pre-provisioning at startup (if enabled). +constexpr base::TimeDelta kStartupDelay = base::Minutes(1); + +// Time to wait before logging number of pre-provisioned origin IDs at startup +constexpr base::TimeDelta kCheckDelay = base::Minutes(5); +static_assert(kCheckDelay > kStartupDelay, + "Must allow time for pre-provisioning to run first"); + +// When unable to get an origin ID, only attempt to pre-provision more if +// pre-provision is called within |kExpirationDelta| of the time of this +// failure. This is not needed on devices that support per-application +// provisioning. +void SetExpirableToken(PrefService* const pref_service) { + DVLOG(3) << __func__; + + ScopedDictPrefUpdate update(pref_service, kMediaDrmOriginIds); + update->Set(kExpirableToken, + base::TimeToValue(base::Time::Now() + kExpirationDelta)); +} + +void RemoveExpirableToken(base::Value::Dict& origin_id_dict) { + DVLOG(3) << __func__; + origin_id_dict.Remove(kExpirableToken); +} + +// On devices that don't support per-application provisioning attempts to +// pre-provision more origin IDs should only happen if an origin ID was +// requested recently and failed. This code checks that the time saved in +// |kExpirableToken| is less than the current time. If |kExpirableToken| doesn't +// exist then this function returns false. On devices that support per +// application provisioning pre-provisioning is always allowed. If +// |kExpirableToken| is expired or corrupt, it will be removed for privacy +// reasons. +bool CanPreProvision(bool is_per_application_provisioning_supported, + base::Value::Dict& origin_id_dict) { + DVLOG(3) << __func__; + + // On devices that support per-application provisioning, this is always true. + if (is_per_application_provisioning_supported) { + return true; + } + + // Device doesn't support per-application provisioning, so check if + // "expirable_token" is still valid. + const base::Value* token_value = origin_id_dict.Find(kExpirableToken); + if (!token_value) { + return false; + } + + absl::optional expiration_time = base::ValueToTime(*token_value); + if (!expiration_time) { + RemoveExpirableToken(origin_id_dict); + return false; + } + + if (base::Time::Now() > *expiration_time) { + DVLOG(3) << __func__ << ": Token exists but has expired"; + RemoveExpirableToken(origin_id_dict); + return false; + } + + return true; +} + +int CountAvailableOriginIds(const base::Value::Dict& origin_id_dict) { + DVLOG(3) << __func__; + + const base::Value::List* origin_ids = origin_id_dict.FindList(kOriginIds); + if (!origin_ids) { + return 0; + } + + DVLOG(3) << "count: " << origin_ids->size(); + return origin_ids->size(); +} + +base::UnguessableToken TakeFirstOriginId(PrefService* const pref_service) { + DVLOG(3) << __func__; + + ScopedDictPrefUpdate update(pref_service, kMediaDrmOriginIds); + + base::Value::List* origin_ids = update->FindList(kOriginIds); + if (!origin_ids) { + return base::UnguessableToken::Null(); + } + + if (origin_ids->empty()) { + return base::UnguessableToken::Null(); + } + + auto first_entry = origin_ids->begin(); + auto result = base::ValueToUnguessableToken(*first_entry); + origin_ids->erase(first_entry); + + return result.value_or(base::UnguessableToken::Null()); +} + +void AddOriginId(base::Value::Dict& origin_id_dict, + const base::UnguessableToken& origin_id) { + DVLOG(3) << __func__; + base::Value::List* origin_ids = origin_id_dict.EnsureList(kOriginIds); + origin_ids->Append(base::UnguessableTokenToValue(origin_id)); +} + +// Helper class that creates a new origin ID and provisions it for both L1 +// (if available) and L3. This class self destructs when provisioning is done +// (successfully or not). +class MediaDrmProvisionHelper { + public: + using ProvisionedOriginIdCB = base::OnceCallback; + + explicit MediaDrmProvisionHelper( + std::unique_ptr + pending_shared_url_loader_factory) { + DVLOG(1) << __func__; + DCHECK(pending_shared_url_loader_factory); + create_fetcher_cb_ = + base::BindRepeating(&content::CreateProvisionFetcher, + network::SharedURLLoaderFactory::Create( + std::move(pending_shared_url_loader_factory))); + } + + void Provision(ProvisionedOriginIdCB callback) {} + + private: + friend class base::RefCounted; + ~MediaDrmProvisionHelper() { DVLOG(1) << __func__; } + + void ProvisionLevel1(bool L3_success) {} + + void ProvisionDone(bool L3_success, bool L1_success) { + DVLOG(1) << __func__ << " origin_id: " << origin_id_.ToString() + << ", L1_success: " << L1_success + << ", L3_success: " << L3_success; + + const bool success = L1_success || L3_success; + LOG_IF(WARNING, !success) << "Failed to provision origin ID"; + std::move(complete_callback_) + .Run(success ? absl::make_optional(origin_id_) : absl::nullopt); + delete this; + } + + media::CreateFetcherCB create_fetcher_cb_; + ProvisionedOriginIdCB complete_callback_; + base::UnguessableToken origin_id_; + scoped_refptr media_drm_bridge_; +}; + +// Provisioning runs on a separate background sequence. This kicks off the +// process, calling |callback| when done. |provisioning_result_cb_for_testing| +// is provided for testing. +void StartProvisioning( + std::unique_ptr + pending_shared_url_loader_factory, + MediaDrmOriginIdManager::ProvisioningResultCB + provisioning_result_cb_for_testing, + MediaDrmProvisionHelper::ProvisionedOriginIdCB callback) { + DVLOG(1) << __func__; + + if (provisioning_result_cb_for_testing) { + // MediaDrm can't provision an origin ID during unittests, so use + // |provisioning_result_cb_for_testing| to generate one (or not, depending + // on the test case). + std::move(callback).Run(provisioning_result_cb_for_testing.Run()); + return; + } + + if (!pending_shared_url_loader_factory) { + std::move(callback).Run(absl::nullopt); + return; + } + + auto* helper = + new MediaDrmProvisionHelper(std::move(pending_shared_url_loader_factory)); + helper->Provision(std::move(callback)); +} + +} // namespace + +// Watch for the device being connected to a network and call +// PreProvisionIfNecessary(). This object is owned by MediaDrmOriginIdManager +// and will be deleted when the manager goes away, so it is safe to keep a +// direct reference to the manager. +class MediaDrmOriginIdManager::NetworkObserver + : public network::NetworkConnectionTracker::NetworkConnectionObserver { + public: + explicit NetworkObserver(MediaDrmOriginIdManager* parent) : parent_(parent) { + content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this); + } + + ~NetworkObserver() override { + content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver( + this); + } + + // Returns true if this NetworkObserver has seen a connection to the network + // more than |kMaxAttemptsAllowed| times. + bool MaxAttemptsExceeded() const { + constexpr int kMaxAttemptsAllowed = 5; + return number_of_attempts_ >= kMaxAttemptsAllowed; + } + + // network::NetworkConnectionTracker::NetworkConnectionObserver + void OnConnectionChanged(network::mojom::ConnectionType type) override { + if (type == network::mojom::ConnectionType::CONNECTION_NONE) { + return; + } + + ++number_of_attempts_; + parent_->PreProvisionIfNecessary(); + } + + private: + // Use of raw pointer is okay as |parent_| owns this object. + const raw_ptr parent_; + int number_of_attempts_ = 0; +}; + +// static +void MediaDrmOriginIdManager::RegisterProfilePrefs( + PrefRegistrySimple* registry) { + registry->RegisterDictionaryPref(kMediaDrmOriginIds); +} + +MediaDrmOriginIdManager::MediaDrmOriginIdManager(PrefService* pref_service) + : pref_service_(pref_service) { + DVLOG(1) << __func__; + DCHECK(pref_service_); + + base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, + base::BindOnce( + &MediaDrmOriginIdManager::RecordCountOfPreprovisionedOriginIds, + weak_factory_.GetWeakPtr()), + kCheckDelay); +} + +MediaDrmOriginIdManager::~MediaDrmOriginIdManager() { + // Reject any pending requests. + while (!pending_provisioned_origin_id_cbs_.empty()) { + std::move(pending_provisioned_origin_id_cbs_.front()) + .Run(GetOriginIdStatus::kFailure, absl::nullopt); + pending_provisioned_origin_id_cbs_.pop(); + } +} + +void MediaDrmOriginIdManager::PreProvisionIfNecessary() { + DVLOG(1) << __func__; + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (is_provisioning_) { + return; + } + + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, + {base::MayBlock(), base::TaskPriority::BEST_EFFORT, + base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, + base::BindOnce( + &media::OHOSMediaDrmBridge::IsPerApplicationProvisioningSupported), + base::BindOnce(&MediaDrmOriginIdManager::ResumePreProvisionIfNecessary, + weak_factory_.GetWeakPtr())); +} + +void MediaDrmOriginIdManager::ResumePreProvisionIfNecessary( + bool is_per_application_provisioning_supported) { + DVLOG(1) << __func__; + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + is_per_application_provisioning_supported_ = + is_per_application_provisioning_supported; + + ScopedDictPrefUpdate update(pref_service_, kMediaDrmOriginIds); + if (!CanPreProvision(is_per_application_provisioning_supported, *update)) { + network_observer_.reset(); + return; + } + + if (CountAvailableOriginIds(*update) >= kMaxPreProvisionedOriginIds) { + network_observer_.reset(); + return; + } + + is_provisioning_ = true; + StartProvisioningAsync(/*run_in_background=*/true); +} + +void MediaDrmOriginIdManager::GetOriginId(ProvisionedOriginIdCB callback) { + DVLOG(1) << __func__; + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + base::UnguessableToken origin_id = TakeFirstOriginId(pref_service_); + if (!is_provisioning_) { + is_provisioning_ = true; + + // If there is an origin ID available then we need to replace it, but this + // can be done in the background. If there are none available, we need one + // now as the user is trying to play protected content. + StartProvisioningAsync(/*run_in_background=*/!origin_id.is_empty()); + } + + // If no pre-provisioned origin ID currently available, so save the callback + // for when provisioning creates one and we're done. + if (!origin_id) { + pending_provisioned_origin_id_cbs_.push(std::move(callback)); + return; + } + + // There is an origin ID available so pass it to the caller. + std::move(callback).Run(GetOriginIdStatus::kSuccessWithPreProvisionedOriginId, + origin_id); +} + +void MediaDrmOriginIdManager::StartProvisioningAsync(bool run_in_background) { + DVLOG(1) << __func__ << " run_in_background: " << run_in_background; + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(is_provisioning_); + + // Run StartProvisioning() later. This is done on a separate thread to avoid + // scroll jank, especially when pre-provisioning is happening (as the origin + // IDs aren't needed for the current page, so it can run at low priority). + // However, if a user needs a provisioned origin ID immediately, then run at + // higher priority. See crbug.com/1366106 for details. + const base::TaskPriority priority = run_in_background + ? base::TaskPriority::BEST_EFFORT + : base::TaskPriority::USER_VISIBLE; + + // Provisioning requires accessing the network to handle the actual + // provisioning request. If access is not currently available, then the + // request will fail and OriginIdProvisioned() will setup a observer + // for when network access is available again. When testing the network + // is not accessible, but prefer to call |provisioning_result_cb_for_testing_| + // from the generated sequence. + std::unique_ptr + pending_shared_url_loader_factory; + auto* network_context_manager = + g_browser_process->system_network_context_manager(); + if (network_context_manager) { + // Fetching the license will run on a different sequence, so clone + // SharedURLLoaderFactory to create an unbound one that can be used + // on any thread/sequence. + pending_shared_url_loader_factory = + network_context_manager->GetSharedURLLoaderFactory()->Clone(); + } + + // Note that MediaDrmBridge requires the use of SingleThreadTaskRunner. + scoped_refptr provisioning_task_runner = + base::ThreadPool::CreateSingleThreadTaskRunner( + {base::MayBlock(), priority}); + provisioning_task_runner->PostTask( + FROM_HERE, + base::BindOnce(&StartProvisioning, + std::move(pending_shared_url_loader_factory), + provisioning_result_cb_for_testing_, + base::BindPostTaskToCurrentDefault(base::BindOnce( + &MediaDrmOriginIdManager::OriginIdProvisioned, + weak_factory_.GetWeakPtr())))); +} + +void MediaDrmOriginIdManager::OriginIdProvisioned( + const MediaDrmOriginId& origin_id) { + DVLOG(1) << __func__ + << " origin_id: " << (origin_id ? origin_id->ToString() : "null"); + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(is_provisioning_); + + if (!origin_id) { + // Unable to provision an origin ID, most likely due to being unable to + // connect to a provisioning server. Set up a NetworkObserver to detect when + // we're connected to a network so that we can try again. If there is + // already a NetworkObserver and provisioning has failed multiple times, + // stop watching for network changes. + if (!network_observer_) { + network_observer_ = std::make_unique(this); + } else if (network_observer_->MaxAttemptsExceeded()) { + network_observer_.reset(); + } + + if (!pending_provisioned_origin_id_cbs_.empty()) { + // This failure results from a user request (as opposed to + // pre-provisioning having been started). + + if (!IsPerApplicationProvisioningSupported()) { + // Token is only required if per application provisioning is not + // supported. + SetExpirableToken(pref_service_); + } + + // As this failed, satisfy all pending requests by returning false. + base::queue pending_requests; + pending_requests.swap(pending_provisioned_origin_id_cbs_); + while (!pending_requests.empty()) { + std::move(pending_requests.front()) + .Run(GetOriginIdStatus::kFailure, absl::nullopt); + pending_requests.pop(); + } + } + + is_provisioning_ = false; + return; + } + + // Success, for at least one level. Pass |origin_id| to the first requestor if + // somebody is waiting for it. Otherwise add it to the list of available + // origin IDs in the preference. + if (!pending_provisioned_origin_id_cbs_.empty()) { + std::move(pending_provisioned_origin_id_cbs_.front()) + .Run(GetOriginIdStatus::kSuccessWithNewlyProvisionedOriginId, + origin_id); + pending_provisioned_origin_id_cbs_.pop(); + } else { + ScopedDictPrefUpdate update(pref_service_, kMediaDrmOriginIds); + AddOriginId(*update, origin_id.value()); + + // If we already have enough pre-provisioned origin IDs, we're done. + // Stop watching for network change events. + if (CountAvailableOriginIds(*update) >= kMaxPreProvisionedOriginIds) { + network_observer_.reset(); + RemoveExpirableToken(*update); + is_provisioning_ = false; + return; + } + } + + // Create another pre-provisioned origin ID asynchronously. If there is + // no pending requestor, then this is simply pre-provisioning another one, + // and can be safely run in the background. If there is a request, run + // at higher priority to quickly satisfy the request. + StartProvisioningAsync( + /*run_in_background=*/pending_provisioned_origin_id_cbs_.empty()); +} + +bool MediaDrmOriginIdManager::IsPerApplicationProvisioningSupported() { + // `is_per_application_provisioning_supported_` should be set in + // PreProvisionIfNecessary(). However, in case it's not (e.g. flag + // kMediaDrmPreprovisioningAtStartup is disabled), determine it now. + if (!is_per_application_provisioning_supported_.has_value()) { + is_per_application_provisioning_supported_ = + media::OHOSMediaDrmBridge::IsPerApplicationProvisioningSupported(); + } + return is_per_application_provisioning_supported_.value(); +} + +void MediaDrmOriginIdManager::RecordCountOfPreprovisionedOriginIds() { + DVLOG(1) << __func__; + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + const auto& pref = pref_service_->GetDict(kMediaDrmOriginIds); + int available_origin_ids = CountAvailableOriginIds(pref); + + if (IsPerApplicationProvisioningSupported()) { + UMA_HISTOGRAM_EXACT_LINEAR( + "Media.EME.MediaDrm.PreprovisionedOriginId.PerAppProvisioningDevice", + available_origin_ids, kUMAMaxPreProvisionedOriginIds); + } else { + UMA_HISTOGRAM_EXACT_LINEAR( + "Media.EME.MediaDrm.PreprovisionedOriginId.NonPerAppProvisioningDevice", + available_origin_ids, kUMAMaxPreProvisionedOriginIds); + } +} diff --git a/browser/media/ohos/cdm/media_drm_origin_id_manager.h b/browser/media/ohos/cdm/media_drm_origin_id_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..68e884b00f6dec77fd11c2262017cd0ee09d8a82 --- /dev/null +++ b/browser/media/ohos/cdm/media_drm_origin_id_manager.h @@ -0,0 +1,126 @@ +// Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_OHOS_CDM_MEDIA_DRM_ORIGIN_ID_MANAGER_H_ +#define CHROME_BROWSER_MEDIA_OHOS_CDM_MEDIA_DRM_ORIGIN_ID_MANAGER_H_ + +#include + +#include "base/containers/queue.h" +#include "base/functional/callback.h" +#include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/unguessable_token.h" +#include "components/keyed_service/core/keyed_service.h" +#include "media/base/media_drm_storage.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +class MediaDrmOriginIdManagerFactory; +class PrefRegistrySimple; +class PrefService; + +// Implements a manager that preprovisions origin IDs used by MediaDrm so that +// an origin ID is available when attempting to play protected content in a +// partially offline environment (e.g. in flight entertainment). One +// MediaDrmOriginIdManager will be created for each PrefService the first time +// it is accessed. These objects need to be persistent so that they can +// pre-provision origin IDs in the background. +// +// These objects will be owned by MediaDrmOriginIdManagerFactory. They support +// KeyedService as the factory connects them to a Profile, and will be +// destroyed when the Profile goes away. +class MediaDrmOriginIdManager : public KeyedService { + public: + enum class GetOriginIdStatus { + kSuccessWithPreProvisionedOriginId = 0, + kSuccessWithNewlyProvisionedOriginId = 1, + kFailure = 2, + }; + + using MediaDrmOriginId = media::MediaDrmStorage::MediaDrmOriginId; + + // |success| is true if an origin ID was obtained and |origin_id| is + // not null, false otherwise. + using ProvisionedOriginIdCB = + base::OnceCallback; + using ProvisioningResultCB = base::RepeatingCallback; + + static void RegisterProfilePrefs(PrefRegistrySimple* registry); + + // Destructor must be public as it's used in std::unique_ptr<>. + ~MediaDrmOriginIdManager() override; + + // Asynchronously preprovision origin IDs if necessary. + void PreProvisionIfNecessary(); + + // Asynchronously returns a preprovisioned origin ID using |callback|, if one + // is available. If none are available, an un-provisioned origin ID is + // returned. + // TODO(crbug.com/917527): Return an empty origin ID once callers + // can handle it. + void GetOriginId(ProvisionedOriginIdCB callback); + + // When testing, use the provided |cb| instead of calling MediaDrm. + void SetProvisioningResultCBForTesting(ProvisioningResultCB cb) { + provisioning_result_cb_for_testing_ = cb; + } + + private: + class NetworkObserver; + friend class MediaDrmOriginIdManagerFactory; + + // MediaDrmOriginIdManager should only be created by + // MediaDrmOriginIdManagerFactory. + explicit MediaDrmOriginIdManager(PrefService* pref_service); + + // Complete the pre-provisioning steps. + void ResumePreProvisionIfNecessary( + bool is_per_application_provisioning_supported); + + // Asynchronously call StartProvisioning() on a sequence using different + // priorities, depending on |run_in_background|. + void StartProvisioningAsync(bool run_in_background); + + // Called when provisioning of |origin_id| is done. The provisioning of + // |origin_id| was successful if |origin_id| is not nullopt. + void OriginIdProvisioned(const MediaDrmOriginId& origin_id); + + // Check if per application provisioning is supported or not. Uses + // `is_per_application_provisioning_supported_`, and if not set, sets it. + bool IsPerApplicationProvisioningSupported(); + + // If called, record the current number of pre-provisioned origin IDs to UMA. + void RecordCountOfPreprovisionedOriginIds(); + + const raw_ptr pref_service_; + + // Callback to be used when the next origin ID is provisioned. + base::queue pending_provisioned_origin_id_cbs_; + + // True if this class is currently pre-provisioning origin IDs, + // false otherwise. + bool is_provisioning_ = false; + + // True if per-application provisioning is supported. If nullopt, then + // support has not yet been determined. + absl::optional is_per_application_provisioning_supported_; + + // When testing don't call MediaDrm to provision the origin ID, just call + // this CB and use the value returned to indicate if provisioning succeeded or + // failed so that tests can verify that the preference is used correctly. + ProvisioningResultCB provisioning_result_cb_for_testing_; + + // When set, watch for network changes and call PreProvisionIfNecessary() + // when connected to a network. + std::unique_ptr network_observer_; + + THREAD_CHECKER(thread_checker_); + + // NOTE: Weak pointers must be invalidated before all other member variables. + base::WeakPtrFactory weak_factory_{this}; +}; + +#endif // CHROME_BROWSER_MEDIA_OHOS_CDM_MEDIA_DRM_ORIGIN_ID_MANAGER_H_ diff --git a/browser/media/ohos/cdm/media_drm_origin_id_manager_factory.cc b/browser/media/ohos/cdm/media_drm_origin_id_manager_factory.cc new file mode 100644 index 0000000000000000000000000000000000000000..fb70b8b4a42628cb1d1854f755e55524471dea08 --- /dev/null +++ b/browser/media/ohos/cdm/media_drm_origin_id_manager_factory.cc @@ -0,0 +1,46 @@ +// Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media/ohos/cdm/media_drm_origin_id_manager_factory.h" + +#include + +#include "base/feature_list.h" +#include "base/memory/ptr_util.h" +#include "chrome/browser/media/ohos/cdm/media_drm_origin_id_manager.h" +#include "chrome/browser/profiles/profile.h" +#include "media/base/media_switches.h" + +// static +MediaDrmOriginIdManager* MediaDrmOriginIdManagerFactory::GetForProfile( + Profile* profile) { + return static_cast( + GetInstance()->GetServiceForBrowserContext(profile, true)); +} + +// static +MediaDrmOriginIdManagerFactory* MediaDrmOriginIdManagerFactory::GetInstance() { + return base::Singleton::get(); +} + +MediaDrmOriginIdManagerFactory::MediaDrmOriginIdManagerFactory() + : ProfileKeyedServiceFactory( + "MediaDrmOriginIdManager", + ProfileSelections::Builder() + .WithRegular(ProfileSelection::kOriginalOnly) + .WithGuest(ProfileSelection::kOriginalOnly) + .Build()) {} + +MediaDrmOriginIdManagerFactory::~MediaDrmOriginIdManagerFactory() = default; + +KeyedService* MediaDrmOriginIdManagerFactory::BuildServiceInstanceFor( + content::BrowserContext* context) const { + Profile* profile = Profile::FromBrowserContext(context); + return new MediaDrmOriginIdManager(profile->GetPrefs()); +} + +bool MediaDrmOriginIdManagerFactory::ServiceIsCreatedWithBrowserContext() + const { + return false; +} diff --git a/browser/media/ohos/cdm/media_drm_origin_id_manager_factory.h b/browser/media/ohos/cdm/media_drm_origin_id_manager_factory.h new file mode 100644 index 0000000000000000000000000000000000000000..d10d19d0b92b001d52b7a898506ee752cc4b34bc --- /dev/null +++ b/browser/media/ohos/cdm/media_drm_origin_id_manager_factory.h @@ -0,0 +1,33 @@ +// Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_OHOS_CDM_MEDIA_DRM_ORIGIN_ID_MANAGER_FACTORY_H_ +#define CHROME_BROWSER_MEDIA_OHOS_CDM_MEDIA_DRM_ORIGIN_ID_MANAGER_FACTORY_H_ + +#include "base/memory/singleton.h" +#include "chrome/browser/profiles/profile_keyed_service_factory.h" + +class MediaDrmOriginIdManager; +class Profile; + +class MediaDrmOriginIdManagerFactory : public ProfileKeyedServiceFactory { + public: + static MediaDrmOriginIdManager* GetForProfile(Profile* profile); + + static MediaDrmOriginIdManagerFactory* GetInstance(); + + private: + friend struct base::DefaultSingletonTraits; + + MediaDrmOriginIdManagerFactory(); + + ~MediaDrmOriginIdManagerFactory() override; + + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const override; + + bool ServiceIsCreatedWithBrowserContext() const override; +}; + +#endif // CHROME_BROWSER_MEDIA_OHOS_CDM_MEDIA_DRM_ORIGIN_ID_MANAGER_FACTORY_H_ diff --git a/browser/media/ohos/cdm/media_drm_storage_factory.cc b/browser/media/ohos/cdm/media_drm_storage_factory.cc new file mode 100644 index 0000000000000000000000000000000000000000..4a75db6e3cc497148596b71cf35e36921e22da46 --- /dev/null +++ b/browser/media/ohos/cdm/media_drm_storage_factory.cc @@ -0,0 +1,101 @@ +// Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media/ohos/cdm/media_drm_storage_factory.h" + +#include + +#include "base/functional/bind.h" +#include "base/functional/callback.h" +#include "base/logging.h" +#include "base/metrics/histogram_functions.h" +#include "chrome/browser/media/ohos/cdm/media_drm_origin_id_manager.h" +#include "chrome/browser/media/ohos/cdm/media_drm_origin_id_manager_factory.h" +#include "chrome/browser/profiles/profile.h" +#include "components/cdm/browser/media_drm_storage_impl.h" +#include "components/prefs/pref_service.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_frame_host.h" +#include "media/base/media_switches.h" +#include "media/base/ohos/ohos_media_drm_bridge.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace { + +using MediaDrmOriginId = media::MediaDrmStorage::MediaDrmOriginId; +using GetOriginIdStatus = MediaDrmOriginIdManager::GetOriginIdStatus; +using OriginIdReadyCB = + base::OnceCallback; + +// These values are reported to UMA. Entries should not be renumbered and +// numeric values should never be reused. +enum class GetOriginIdResult { + kSuccessWithPreProvisionedOriginId = 0, + kSuccessWithNewlyProvisionedOriginId = 1, + kSuccessWithUnprovisionedOriginId = 2, + kFailureOnPerAppProvisioningDevice = 3, + kFailureOnNonPerAppProvisioningDevice = 4, + kFailureWithNoFactory = 5, + kMaxValue = kFailureWithNoFactory, +}; + +// Update UMA with |result|. +void ReportResultToUma(GetOriginIdResult result) { + base::UmaHistogramEnumeration("Media.EME.MediaDrm.GetOriginIdResult", result); +} + +void CreateOriginId(OriginIdReadyCB callback) { + LOG(INFO) << "[NEU][CDM]" << __func__; + auto origin_id = base::UnguessableToken::Create(); + DVLOG(2) << __func__ << ": origin_id = " << origin_id; + LOG(INFO) << "[NEU][CDM]" << __func__ << ": origin_id = " << origin_id; + ReportResultToUma(GetOriginIdResult::kSuccessWithUnprovisionedOriginId); + std::move(callback).Run(true, origin_id); + LOG(INFO) << "[NEU][CDM]" << __func__; +} + +void AllowEmptyOriginId(content::RenderFrameHost* render_frame_host, + base::OnceCallback callback) { + if (media::OHOSMediaDrmBridge::IsPerApplicationProvisioningSupported()) { + std::move(callback).Run(false); + return; + } + std::move(callback).Run(false); +} + +} // namespace + +void CreateMediaDrmStorage( + content::RenderFrameHost* render_frame_host, + mojo::PendingReceiver receiver) { + LOG(INFO) << "[NEU][CDM]" << __func__; + DVLOG(1) << __func__; + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + CHECK(render_frame_host); + + content::BrowserContext* browser_context = + render_frame_host->GetBrowserContext(); + DCHECK(browser_context) << "BrowserContext not available."; + + Profile* profile = Profile::FromBrowserContext(browser_context); + DCHECK(profile) << "Profile not available."; + + PrefService* pref_service = profile->GetPrefs(); + DCHECK(pref_service) << "PrefService not available."; + + if (render_frame_host->GetLastCommittedOrigin().opaque()) { + DVLOG(1) << __func__ << ": Unique origin."; + return; + } + + LOG(INFO) << "[NEU][CDM]" << __func__; + auto get_origin_id_cb = base::BindRepeating(&CreateOriginId); + + LOG(INFO) << "[NEU][CDM]" << __func__; + new cdm::MediaDrmStorageImpl( + *render_frame_host, pref_service, get_origin_id_cb, + base::BindRepeating(&AllowEmptyOriginId, render_frame_host), + std::move(receiver)); +} diff --git a/browser/media/ohos/cdm/media_drm_storage_factory.h b/browser/media/ohos/cdm/media_drm_storage_factory.h new file mode 100644 index 0000000000000000000000000000000000000000..3143374ffa24c9bbc07350a5c03af25274d8a859 --- /dev/null +++ b/browser/media/ohos/cdm/media_drm_storage_factory.h @@ -0,0 +1,19 @@ +// Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_OHOS_CDM_MEDIA_DRM_STORAGE_FACTORY_H_ +#define CHROME_BROWSER_MEDIA_OHOS_CDM_MEDIA_DRM_STORAGE_FACTORY_H_ + +#include "media/mojo/mojom/media_drm_storage.mojom.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" + +namespace content { +class RenderFrameHost; +} + +void CreateMediaDrmStorage( + content::RenderFrameHost* render_frame_host, + mojo::PendingReceiver receiver); + +#endif // CHROME_BROWSER_MEDIA_OHOS_CDM_MEDIA_DRM_STORAGE_FACTORY_H_ diff --git a/common/chrome_content_client.cc b/common/chrome_content_client.cc index db08f6151229efb10def32f81aedf930f2ff44ed..510ac57ec4c806facc54bf7b99e6cc164b627cfa 100644 --- a/common/chrome_content_client.cc +++ b/common/chrome_content_client.cc @@ -186,6 +186,7 @@ void ChromeContentClient::AddPlugins( void ChromeContentClient::AddContentDecryptionModules( std::vector* cdms, std::vector* cdm_host_file_paths) { + LOG(INFO) << "[NEU][CDM]" << __func__; if (cdms) RegisterCdmInfo(cdms); diff --git a/common/media/cdm_registration.cc b/common/media/cdm_registration.cc index 9de0d9960a74484843d05f8740cda5b2f0713b85..2aa84f881435fc70a625a54eddc6f7a91592c71c 100644 --- a/common/media/cdm_registration.cc +++ b/common/media/cdm_registration.cc @@ -150,7 +150,7 @@ content::CdmInfo* GetComponentUpdatedWidevine() { void AddSoftwareSecureWidevine(std::vector* cdms) { DVLOG(1) << __func__; - + LOG(INFO) << "[NEU][CDM]" << __func__; #if BUILDFLAG(IS_ANDROID) // On Android Widevine is done by MediaDrm, and should be supported on all // devices. Register Widevine without any capabilities so that it will be @@ -159,7 +159,12 @@ void AddSoftwareSecureWidevine(std::vector* cdms) { kWidevineKeySystem, Robustness::kSoftwareSecure, absl::nullopt, /*supports_sub_key_systems=*/false, kWidevineCdmDisplayName, kWidevineCdmType, base::Version(), base::FilePath()); - +#elif BUILDFLAG(IS_OHOS) + LOG(INFO) << "[NEU][CDM]" << __func__; + cdms->emplace_back( + kWidevineKeySystem, Robustness::kSoftwareSecure, absl::nullopt, + /*supports_sub_key_systems=*/false, kWidevineCdmDisplayName, + kWidevineCdmType, base::Version(), base::FilePath()); #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) #if defined(WIDEVINE_CDM_MIN_GLIBC_VERSION) base::Version glibc_version(gnu_get_libc_version()); @@ -382,6 +387,7 @@ void RegisterCdmInfo(std::vector* cdms) { DVLOG(1) << __func__; DCHECK(cdms); DCHECK(cdms->empty()); + LOG(INFO) << "[NEU][CDM]" << __func__; #if BUILDFLAG(ENABLE_WIDEVINE) AddWidevine(cdms); @@ -398,6 +404,6 @@ void RegisterCdmInfo(std::vector* cdms) { #if BUILDFLAG(IS_ANDROID) AddOtherAndroidKeySystems(cdms); #endif // BUILDFLAG(IS_ANDROID) - + LOG(INFO) << "[NEU][CDM]" << __func__; DVLOG(3) << __func__ << " done with " << cdms->size() << " cdms"; } diff --git a/renderer/chrome_content_renderer_client.cc b/renderer/chrome_content_renderer_client.cc index c8192b21dc14f02f6124fcff5e99c51ed5fb303b..1cffd9e9694ed518e9314bfd183340b6edbeae4b 100644 --- a/renderer/chrome_content_renderer_client.cc +++ b/renderer/chrome_content_renderer_client.cc @@ -1562,11 +1562,15 @@ ChromeContentRendererClient::CreateWebSocketHandshakeThrottleProvider() { void ChromeContentRendererClient::GetSupportedKeySystems( media::GetSupportedKeySystemsCB cb) { + LOG(INFO) << "[NEU][CDM]" << __func__; #if BUILDFLAG(ENABLE_LIBRARY_CDMS) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID) + LOG(INFO) << "[NEU][CDM]" << __func__; GetChromeKeySystems(std::move(cb)); #else + LOG(INFO) << "[NEU][CDM]" << __func__; std::move(cb).Run({}); #endif + LOG(INFO) << "[NEU][CDM]" << __func__; } bool ChromeContentRendererClient::ShouldReportDetailedMessageForSource( diff --git a/renderer/media/chrome_key_systems.cc b/renderer/media/chrome_key_systems.cc index 18965729b36744359c893ca614366e9607a0a2e3..9246dacbdbe93bce14fe745f357f60dc2cfc594d 100644 --- a/renderer/media/chrome_key_systems.cc +++ b/renderer/media/chrome_key_systems.cc @@ -8,6 +8,8 @@ #include "components/cdm/renderer/key_system_support_update.h" void GetChromeKeySystems(media::GetSupportedKeySystemsCB cb) { + LOG(INFO) << "[NEU][CDM]" << __func__; cdm::GetSupportedKeySystemsUpdates( !ChromeRenderThreadObserver::is_incognito_process(), std::move(cb)); + LOG(INFO) << "[NEU][CDM]" << __func__; }