Commit 8a0a739b authored by John Rummell's avatar John Rummell Committed by Commit Bot

Monitor network changes if pre-provisioning of MediaDrm origin IDs fails

When unable to pre-provision origin IDs, it may be due to the device
not being able to connect to a provisioning server. Updating the code
to watch for network events. When connected to a network, try again
to pre-provision origin IDs.

BUG=917527
TEST=new unit_tests pass

Change-Id: Ia41bbedf543556e59ecd03bef51d989c6c0f7210
Reviewed-on: https://chromium-review.googlesource.com/c/1475005
Commit-Queue: John Rummell <jrummell@chromium.org>
Reviewed-by: default avatarJosh Karlin <jkarlin@chromium.org>
Reviewed-by: default avatarAdam Langley <agl@chromium.org>
Reviewed-by: default avatarXiaohan Wang <xhwang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#635828}
parent 3cced99b
......@@ -8,7 +8,6 @@
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/task/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
......@@ -20,9 +19,11 @@
#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/android/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"
......@@ -268,6 +269,44 @@ class MediaDrmProvisionHelper {
} // 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.
MediaDrmOriginIdManager* const parent_;
int number_of_attempts_ = 0;
};
// static
void MediaDrmOriginIdManager::RegisterProfilePrefs(
PrefRegistrySimple* registry) {
......@@ -293,8 +332,11 @@ void MediaDrmOriginIdManager::PreProvisionIfNecessary() {
// On devices that need to, check that the user has recently requested
// an origin ID. If not, then skip pre-provisioning on those devices.
DictionaryPrefUpdate update(pref_service_, kMediaDrmOriginIds);
if (!CanPreProvision(update.Get()))
if (!CanPreProvision(update.Get())) {
// Disable any network monitoring, if it exists.
network_observer_.reset();
return;
}
// No need to pre-provision if there are already enough existing
// pre-provisioned origin IDs.
......@@ -348,10 +390,11 @@ void MediaDrmOriginIdManager::StartProvisioning() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_provisioning_);
if (skip_provisioning_for_testing_) {
if (provisioning_result_cb_for_testing_) {
// MediaDrm can't provision an origin ID during unittests, so create a new
// origin ID and pretend it was provisioned or not depending on the setting.
OriginIdProvisioned(provisioning_result_for_testing_,
// origin ID and pretend it was provisioned or not depending on the result
// from |provisioning_result_cb_for_testing_|.
OriginIdProvisioned(provisioning_result_cb_for_testing_.Run(),
base::UnguessableToken::Create());
return;
}
......@@ -372,10 +415,19 @@ void MediaDrmOriginIdManager::OriginIdProvisioned(
DCHECK(is_provisioning_);
if (!success) {
// 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<NetworkObserver>(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).
// TODO(crbug.com/917527): Register for network events.
SetExpirableTokenIfNeeded(pref_service_);
// As this failed, satisfy all pending requests by returning an
......@@ -406,7 +458,9 @@ void MediaDrmOriginIdManager::OriginIdProvisioned(
AddOriginId(update.Get(), origin_id);
// If we already have enough pre-provisioned origin IDs, we're done.
// Stop watching for network change events.
if (CountAvailableOriginIds(update.Get()) >= kMaxPreProvisionedOriginIds) {
network_observer_.reset();
RemoveExpirableToken(update.Get());
is_provisioning_ = false;
return;
......
......@@ -5,7 +5,9 @@
#ifndef CHROME_BROWSER_MEDIA_ANDROID_CDM_MEDIA_DRM_ORIGIN_ID_MANAGER_H_
#define CHROME_BROWSER_MEDIA_ANDROID_CDM_MEDIA_DRM_ORIGIN_ID_MANAGER_H_
#include "base/callback_forward.h"
#include <memory>
#include "base/callback.h"
#include "base/containers/queue.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
......@@ -34,6 +36,7 @@ class MediaDrmOriginIdManager : public KeyedService {
using ProvisionedOriginIdCB = base::OnceCallback<void(
bool success,
const base::Optional<base::UnguessableToken>& origin_id)>;
using ProvisioningResultCB = base::RepeatingCallback<bool()>;
static void RegisterProfilePrefs(PrefRegistrySimple* registry);
......@@ -50,12 +53,13 @@ class MediaDrmOriginIdManager : public KeyedService {
// can handle it.
void GetOriginId(ProvisionedOriginIdCB callback);
void SetProvisioningResultForTesting(bool result) {
skip_provisioning_for_testing_ = true;
provisioning_result_for_testing_ = result;
// 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
......@@ -82,11 +86,14 @@ class MediaDrmOriginIdManager : public KeyedService {
// false otherwise.
bool is_provisioning_ = false;
// When testing don't call MediaDrm to provision the origin ID, just pretend
// it was called and use the value provided so that tests can verify that
// the preference is used correctly.
bool skip_provisioning_for_testing_ = false;
bool provisioning_result_for_testing_ = false;
// 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<NetworkObserver> network_observer_;
THREAD_CHECKER(thread_checker_);
......
......@@ -22,11 +22,15 @@
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "media/base/android/media_drm_bridge.h"
#include "services/network/test/test_network_connection_tracker.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace {
using testing::Return;
// These values must match the values specified for the implementation
// in media_drm_origin_id_manager.cc.
const char kMediaDrmOriginIds[] = "media.media_drm_origin_ids";
......@@ -34,6 +38,7 @@ const char kExpirableToken[] = "expirable_token";
const char kAvailableOriginIds[] = "origin_ids";
constexpr size_t kExpectedPreferenceListSize = 5;
constexpr base::TimeDelta kExpirationDelta = base::TimeDelta::FromHours(24);
constexpr size_t kConnectionAttempts = 5;
using MediaDrmOriginId = base::Optional<base::UnguessableToken>;
......@@ -45,9 +50,13 @@ class MediaDrmOriginIdManagerTest : public testing::Test {
profile_ = std::make_unique<TestingProfile>();
origin_id_manager_ =
MediaDrmOriginIdManagerFactory::GetForProfile(profile_.get());
origin_id_manager_->SetProvisioningResultForTesting(true);
origin_id_manager_->SetProvisioningResultCBForTesting(
base::BindRepeating(&MediaDrmOriginIdManagerTest::GetProvisioningResult,
base::Unretained(this)));
}
MOCK_METHOD0(GetProvisioningResult, bool());
// Call MediaDrmOriginIdManager::GetOriginId() synchronously.
MediaDrmOriginId GetOriginId() {
base::RunLoop run_loop;
......@@ -99,10 +108,12 @@ TEST_F(MediaDrmOriginIdManagerTest, Creation) {
}
TEST_F(MediaDrmOriginIdManagerTest, OneOriginId) {
EXPECT_CALL(*this, GetProvisioningResult()).WillRepeatedly(Return(true));
EXPECT_TRUE(GetOriginId());
}
TEST_F(MediaDrmOriginIdManagerTest, TwoOriginIds) {
EXPECT_CALL(*this, GetProvisioningResult()).WillRepeatedly(Return(true));
MediaDrmOriginId origin_id1 = GetOriginId();
MediaDrmOriginId origin_id2 = GetOriginId();
EXPECT_TRUE(origin_id1);
......@@ -116,6 +127,7 @@ TEST_F(MediaDrmOriginIdManagerTest, PreProvision) {
// that don't, the list will be empty. Note that simply finding the preference
// creates an empty one (as FindPreference() only returns NULL if the
// preference is not registered).
EXPECT_CALL(*this, GetProvisioningResult()).WillRepeatedly(Return(true));
PreProvision();
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds;
......@@ -150,6 +162,7 @@ TEST_F(MediaDrmOriginIdManagerTest, PreProvision) {
TEST_F(MediaDrmOriginIdManagerTest, GetOriginIdCreatesList) {
// After fetching an origin ID the code should pre-provision more origins
// and fill up the list.
EXPECT_CALL(*this, GetProvisioningResult()).WillRepeatedly(Return(true));
GetOriginId();
test_browser_thread_bundle_.RunUntilIdle();
......@@ -170,6 +183,7 @@ TEST_F(MediaDrmOriginIdManagerTest, OriginIdNotInList) {
// After fetching one origin ID MediaDrmOriginIdManager will create the list
// of pre-provisioned origin IDs (asynchronously). It doesn't matter if the
// device supports per-application provisioning or not.
EXPECT_CALL(*this, GetProvisioningResult()).WillRepeatedly(Return(true));
MediaDrmOriginId origin_id = GetOriginId();
test_browser_thread_bundle_.RunUntilIdle();
......@@ -185,7 +199,7 @@ TEST_F(MediaDrmOriginIdManagerTest, ProvisioningFail) {
// TODO(crbug.com/917527): Currently the code returns an origin ID even if
// provisioning fails. Update this once it returns an empty origin ID when
// pre-provisioning fails.
origin_id_manager_->SetProvisioningResultForTesting(false);
EXPECT_CALL(*this, GetProvisioningResult()).WillOnce(testing::Return(false));
EXPECT_TRUE(GetOriginId());
// After failure the preference should contain |kExpireableToken| only if
......@@ -204,11 +218,11 @@ TEST_F(MediaDrmOriginIdManagerTest, ProvisioningSuccessAfterFail) {
// TODO(crbug.com/917527): Currently the code returns an origin ID even if
// provisioning fails. Update this once it returns an empty origin ID when
// pre-provisioning fails.
origin_id_manager_->SetProvisioningResultForTesting(false);
EXPECT_TRUE(GetOriginId());
origin_id_manager_->SetProvisioningResultForTesting(true);
EXPECT_CALL(*this, GetProvisioningResult())
.WillOnce(Return(false))
.WillRepeatedly(Return(true));
EXPECT_TRUE(GetOriginId());
EXPECT_TRUE(GetOriginId()); // Provisioning will succeed on the second call.
// Let pre-provisioning of other origin IDs finish.
test_browser_thread_bundle_.RunUntilIdle();
......@@ -225,7 +239,9 @@ TEST_F(MediaDrmOriginIdManagerTest, ProvisioningAfterExpiration) {
// provisioning fails. Update this once it returns an empty origin ID when
// pre-provisioning fails.
DVLOG(1) << "Current time: " << base::Time::Now();
origin_id_manager_->SetProvisioningResultForTesting(false);
EXPECT_CALL(*this, GetProvisioningResult())
.WillOnce(Return(false))
.WillRepeatedly(Return(true));
EXPECT_TRUE(GetOriginId());
test_browser_thread_bundle_.RunUntilIdle();
......@@ -246,7 +262,6 @@ TEST_F(MediaDrmOriginIdManagerTest, ProvisioningAfterExpiration) {
test_browser_thread_bundle_.FastForwardBy(kExpirationDelta);
test_browser_thread_bundle_.FastForwardBy(base::TimeDelta::FromMinutes(1));
DVLOG(1) << "Adjusted time: " << base::Time::Now();
origin_id_manager_->SetProvisioningResultForTesting(true);
PreProvision();
test_browser_thread_bundle_.RunUntilIdle();
......@@ -277,3 +292,85 @@ TEST_F(MediaDrmOriginIdManagerTest, Incognito) {
EXPECT_FALSE(
MediaDrmOriginIdManagerFactory::GetForProfile(incognito_profile));
}
TEST_F(MediaDrmOriginIdManagerTest, NetworkChange) {
// Try to pre-provision a bunch of origin IDs. Provisioning will fail, so
// there will not be a bunch of origin IDs created. However, it should be
// watching for a network change.
// TODO(crbug.com/917527): Currently the code returns an origin ID even if
// provisioning fails. Update this once it returns an empty origin ID when
// pre-provisioning fails.
EXPECT_CALL(*this, GetProvisioningResult())
.WillOnce(Return(false))
.WillRepeatedly(Return(true));
EXPECT_TRUE(GetOriginId());
test_browser_thread_bundle_.RunUntilIdle();
// Check that |kAvailableOriginIds| in the preference is empty.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds;
auto* dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
EXPECT_FALSE(dict->FindKey(kAvailableOriginIds));
// Provisioning will now "succeed", so trigger a network change to
// unconnected.
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_NONE);
test_browser_thread_bundle_.RunUntilIdle();
// Check that |kAvailableOriginIds| is still empty.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds << " again";
dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
EXPECT_FALSE(dict->FindKey(kAvailableOriginIds));
// Now trigger a network change to connected.
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
test_browser_thread_bundle_.RunUntilIdle();
// Pre-provisioning should have run and filled up the list.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds << " again";
dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
auto* list = dict->FindKey(kAvailableOriginIds);
EXPECT_EQ(list->GetList().size(), kExpectedPreferenceListSize);
}
TEST_F(MediaDrmOriginIdManagerTest, NetworkChangeFails) {
// Try to pre-provision a bunch of origin IDs. Provisioning will fail the
// first time, so there will not be a bunch of origin IDs created. However, it
// should be watching for a network change, and will try again on the next
// |kConnectionAttempts| connections to a network. GetProvisioningResult()
// should only be called once for the GetOriginId() call +
// |kConnectionAttempts| when a network connection is detected.
// TODO(crbug.com/917527): Currently the code returns an origin ID even if
// provisioning fails. Update this once it returns an empty origin ID when
// pre-provisioning fails.
EXPECT_CALL(*this, GetProvisioningResult())
.Times(kConnectionAttempts + 1)
.WillOnce(Return(false));
EXPECT_TRUE(GetOriginId());
test_browser_thread_bundle_.RunUntilIdle();
// Check that |kAvailableOriginIds| in the preference is empty.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds;
auto* dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
EXPECT_FALSE(dict->FindKey(kAvailableOriginIds));
// Trigger multiple network connections (provisioning still fails). Call more
// than |kConnectionAttempts| to ensure that the network change is ignored
// after several failed attempts.
for (size_t i = 0; i < kConnectionAttempts + 3; ++i) {
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
test_browser_thread_bundle_.RunUntilIdle();
}
// Check that |kAvailableOriginIds| is still empty.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds << " again";
dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
EXPECT_FALSE(dict->FindKey(kAvailableOriginIds));
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment