chromium/media/base/android/media_drm_bridge_unittest.cc

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

#include "media/base/android/media_drm_bridge.h"

#include <memory>

#include "base/android/build_info.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "media/base/media_switches.h"
#include "media/base/mock_filters.h"
#include "media/base/provision_fetcher.h"
#include "media/cdm/clear_key_cdm_common.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest-spi.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/widevine/cdm/widevine_cdm_common.h"

using ::testing::_;
using ::testing::ByMove;
using ::testing::Return;
using ::testing::StrictMock;

namespace media {

#define EXPECT_TRUE_IF_KEY_SYSTEM_AVAILABLE(a, b)                   \
  do {                                                              \
    if (!MediaDrmBridge::IsKeySystemSupported(b)) {                 \
      VLOG(0) << "Key System " << b << " not supported on device."; \
      EXPECT_FALSE(a);                                              \
    } else {                                                        \
      EXPECT_TRUE(a);                                               \
    }                                                               \
  } while (0)

const char kAudioMp4[] = "audio/mp4";
const char kVideoMp4[] = "video/mp4";
const char kAudioWebM[] = "audio/webm";
const char kVideoWebM[] = "video/webm";
const char kInvalidKeySystem[] = "invalid.keysystem";
const MediaDrmBridge::SecurityLevel kDefault =
    MediaDrmBridge::SECURITY_LEVEL_DEFAULT;
const MediaDrmBridge::SecurityLevel kL1 = MediaDrmBridge::SECURITY_LEVEL_1;
const MediaDrmBridge::SecurityLevel kL3 = MediaDrmBridge::SECURITY_LEVEL_3;
const char kTestOrigin[] = "http://www.example.com";
const char kEmptyOrigin[] = "";

// Helper functions to avoid typing "MediaDrmBridge::" in tests.

static bool IsKeySystemSupportedWithType(
    const std::string& key_system,
    const std::string& container_mime_type) {
  return MediaDrmBridge::IsKeySystemSupportedWithType(key_system,
                                                      container_mime_type);
}

namespace {

// This class is simply a wrapper that passes on calls to Retrieve() to another
// implementation that is provided to the constructor. This is created as
// MediaDrmBridge::CreateWithoutSessionSupport() requires the creation of a new
// ProvisionFetcher each time it needs to retrieve a license from the license
// server.
class ProvisionFetcherWrapper : public ProvisionFetcher {
 public:
  explicit ProvisionFetcherWrapper(ProvisionFetcher* provision_fetcher)
      : provision_fetcher_(provision_fetcher) {}

  // ProvisionFetcher implementation.
  void Retrieve(const GURL& default_url,
                const std::string& request_data,
                ResponseCB response_cb) override {
    provision_fetcher_->Retrieve(default_url, request_data,
                                 std::move(response_cb));
  }

 private:
  raw_ptr<ProvisionFetcher> provision_fetcher_;
};

}  // namespace

class MediaDrmBridgeTest : public ProvisionFetcher, public testing::Test {
 public:
  MediaDrmBridgeTest() {}

  void CreateWithoutSessionSupport(
      const std::string& key_system,
      const std::string& origin_id,
      MediaDrmBridge::SecurityLevel security_level) {
    media_drm_bridge_ = MediaDrmBridge::CreateWithoutSessionSupport(
        key_system, origin_id, security_level, "Test",
        base::BindRepeating(&MediaDrmBridgeTest::CreateProvisionFetcher,
                            base::Unretained(this)));
  }

  void CreateWithoutSessionSupportWithNullCallback(
      const std::string& key_system,
      const std::string& origin_id,
      MediaDrmBridge::SecurityLevel security_level) {
    media_drm_bridge_ = MediaDrmBridge::CreateWithoutSessionSupport(
        key_system, origin_id, security_level, "Test", base::NullCallback());
  }

  // ProvisionFetcher implementation. Done as a mock method so we can properly
  // check if |media_drm_bridge_| invokes it or not.
  MOCK_METHOD(void,
              Retrieve,
              (const GURL& default_url,
               const std::string& request_data,
               ResponseCB response_cb));

  void Provision() {
    media_drm_bridge_->Provision(base::BindOnce(
        &MediaDrmBridgeTest::ProvisioningDone, base::Unretained(this)));
  }

  void Unprovision() { media_drm_bridge_->Unprovision(); }

  // MediaDrmBridge::Provision() requires a callback that is called when
  // provisioning completes and indicates if it succeeds or not.
  MOCK_METHOD(void, ProvisioningDone, (bool));

  // Called whenever Provision() is run to create a ProvisionFetcher (which if
  // needed will be `this`).
  MOCK_METHOD(std::unique_ptr<ProvisionFetcher>, CreateProvisionFetcher, ());

 protected:
  scoped_refptr<MediaDrmBridge> media_drm_bridge_;

  base::test::ScopedFeatureList scoped_feature_list_;

 private:
  base::test::TaskEnvironment task_environment_;
};

TEST_F(MediaDrmBridgeTest, IsKeySystemSupported_Widevine) {
  // TODO(xhwang): Enable when b/13564917 is fixed.
  // EXPECT_TRUE_IF_AVAILABLE(
  //     IsKeySystemSupportedWithType(kWidevineKeySystem, kAudioMp4));
  EXPECT_TRUE_IF_KEY_SYSTEM_AVAILABLE(
      IsKeySystemSupportedWithType(kWidevineKeySystem, kVideoMp4),
      kWidevineKeySystem);

  EXPECT_TRUE_IF_KEY_SYSTEM_AVAILABLE(
      IsKeySystemSupportedWithType(kWidevineKeySystem, kAudioWebM),
      kWidevineKeySystem);
  EXPECT_TRUE_IF_KEY_SYSTEM_AVAILABLE(
      IsKeySystemSupportedWithType(kWidevineKeySystem, kVideoWebM),
      kWidevineKeySystem);

  EXPECT_FALSE(IsKeySystemSupportedWithType(kWidevineKeySystem, "unknown"));
  EXPECT_FALSE(IsKeySystemSupportedWithType(kWidevineKeySystem, "video/avi"));
  EXPECT_FALSE(IsKeySystemSupportedWithType(kWidevineKeySystem, "audio/mp3"));
}

TEST_F(MediaDrmBridgeTest, IsKeySystemSupported_ExternalClearKey) {
  // Testing that 'kExternalClearKeyForTesting' is disabled by default.
  EXPECT_FALSE(
      base::FeatureList::IsEnabled(media::kExternalClearKeyForTesting));
  scoped_feature_list_.InitWithFeatures({media::kExternalClearKeyForTesting},
                                        {});
  EXPECT_TRUE(base::FeatureList::IsEnabled(media::kExternalClearKeyForTesting));
  EXPECT_TRUE_IF_KEY_SYSTEM_AVAILABLE(
      IsKeySystemSupportedWithType(kExternalClearKeyKeySystem, kVideoMp4),
      kExternalClearKeyKeySystem);
}

// Invalid key system is NOT supported regardless whether MediaDrm is available.
TEST_F(MediaDrmBridgeTest, IsKeySystemSupported_InvalidKeySystem) {
  EXPECT_FALSE(MediaDrmBridge::IsKeySystemSupported(kInvalidKeySystem));
  EXPECT_FALSE(IsKeySystemSupportedWithType(kInvalidKeySystem, kAudioMp4));
  EXPECT_FALSE(IsKeySystemSupportedWithType(kInvalidKeySystem, kVideoMp4));
  EXPECT_FALSE(IsKeySystemSupportedWithType(kInvalidKeySystem, kAudioWebM));
  EXPECT_FALSE(IsKeySystemSupportedWithType(kInvalidKeySystem, kVideoWebM));
  EXPECT_FALSE(IsKeySystemSupportedWithType(kInvalidKeySystem, "unknown"));
  EXPECT_FALSE(IsKeySystemSupportedWithType(kInvalidKeySystem, "video/avi"));
  EXPECT_FALSE(IsKeySystemSupportedWithType(kInvalidKeySystem, "audio/mp3"));
}

TEST_F(MediaDrmBridgeTest, CreateWithoutSessionSupport_Widevine) {
  CreateWithoutSessionSupport(kWidevineKeySystem, kTestOrigin, kDefault);
  EXPECT_TRUE_IF_KEY_SYSTEM_AVAILABLE(media_drm_bridge_, kWidevineKeySystem);
}

TEST_F(MediaDrmBridgeTest, CreateWithoutSessionSupport_ExternalClearKey) {
  CreateWithoutSessionSupport(kExternalClearKeyKeySystem, kTestOrigin,
                              kDefault);
  EXPECT_FALSE(media_drm_bridge_);
  scoped_feature_list_.InitWithFeatures({media::kExternalClearKeyForTesting},
                                        {});
  CreateWithoutSessionSupport(kExternalClearKeyKeySystem, kTestOrigin,
                              kDefault);
  EXPECT_TRUE_IF_KEY_SYSTEM_AVAILABLE(media_drm_bridge_,
                                      kExternalClearKeyKeySystem);
}

// Invalid key system is NOT supported regardless whether MediaDrm is available.
TEST_F(MediaDrmBridgeTest, CreateWithoutSessionSupport_InvalidKeySystem) {
  CreateWithoutSessionSupport(kInvalidKeySystem, kTestOrigin, kDefault);
  EXPECT_FALSE(media_drm_bridge_);
}

TEST_F(MediaDrmBridgeTest, CreateWithSecurityLevel_Widevine) {
  // We test "L3" fully. But for "L1" we don't check the result as it depends on
  // whether the test device supports "L1".
  CreateWithoutSessionSupport(kWidevineKeySystem, kTestOrigin, kL3);
  EXPECT_TRUE_IF_KEY_SYSTEM_AVAILABLE(media_drm_bridge_, kWidevineKeySystem);

  CreateWithoutSessionSupport(kWidevineKeySystem, kTestOrigin, kL1);
}

TEST_F(MediaDrmBridgeTest, CreateWithSecurityLevel_ExternalClearKey) {
  scoped_feature_list_.InitWithFeatures({media::kExternalClearKeyForTesting},
                                        {});

  // ExternalClearKey only initialized with 'kDefault' security level.
  CreateWithoutSessionSupport(kExternalClearKeyKeySystem, kTestOrigin,
                              kDefault);
  EXPECT_TRUE_IF_KEY_SYSTEM_AVAILABLE(media_drm_bridge_,
                                      kExternalClearKeyKeySystem);
}

TEST_F(MediaDrmBridgeTest, Provision_Widevine) {
  // Only test this if Widevine is supported. Otherwise
  // CreateWithoutSessionSupport() will return null and it can't be tested.
  if (!MediaDrmBridge::IsKeySystemSupported(kWidevineKeySystem)) {
    GTEST_SKIP() << "Widevine not supported on device.";
  }

  // Calling Provision() later should trigger a provisioning request. As we
  // can't pass the request to a license server,
  // MockProvisionFetcher::Retrieve() simply drops the request and never
  // responds. As a result, there should be a call to Retrieve() but not to
  // ProvisioningDone() (CB passed to Provision()) as the provisioning never
  // completes.
  EXPECT_CALL(*this, CreateProvisionFetcher())
      .WillOnce(
          Return(ByMove(std::make_unique<ProvisionFetcherWrapper>(this))));
  EXPECT_CALL(*this, Retrieve(_, _, _));
  EXPECT_CALL(*this, ProvisioningDone(_)).Times(0);

  // Create MediaDrmBridge. We only test "L3" as "L1" depends on whether the
  // test device supports it or not.
  CreateWithoutSessionSupport(kWidevineKeySystem, kTestOrigin, kL3);
  EXPECT_TRUE(media_drm_bridge_);
  Provision();

  // Provisioning is executed asynchronously.
  base::RunLoop().RunUntilIdle();
}

TEST_F(MediaDrmBridgeTest, Provision_Widevine_NoOrigin) {
  // Only test this if Widevine is supported. Otherwise
  // CreateWithoutSessionSupport() will return null and it can't be tested.
  if (!MediaDrmBridge::IsKeySystemSupported(kWidevineKeySystem)) {
    GTEST_SKIP() << "Widevine not supported on device.";
  }

  // Calling Provision() later should fail as the origin is not provided (or
  // origin isolated storage is not available). No provisioning request should
  // be attempted.
  EXPECT_CALL(*this, CreateProvisionFetcher()).Times(0);
  EXPECT_CALL(*this, Retrieve(_, _, _)).Times(0);
  EXPECT_CALL(*this, ProvisioningDone(false));

  // Create MediaDrmBridge. We only test "L3" as "L1" depends on whether the
  // test device supports it or not.
  CreateWithoutSessionSupport(kWidevineKeySystem, kEmptyOrigin, kL3);
  EXPECT_TRUE(media_drm_bridge_);
  Provision();

  // Provisioning is executed asynchronously.
  base::RunLoop().RunUntilIdle();
}

TEST_F(MediaDrmBridgeTest, Unprovision_Widevine) {
  // Only test this if Widevine is supported. Otherwise
  // CreateWithoutSessionSupport() will return null and it can't be tested.
  if (!MediaDrmBridge::IsKeySystemSupported(kWidevineKeySystem)) {
    GTEST_SKIP() << "Widevine not supported on device.";
  }

  // Ensure Unprovision doesn't try to provision.
  EXPECT_CALL(*this, CreateProvisionFetcher()).Times(0);

  // Create MediaDrmBridge. We only test "L3" as "L1" depends on whether the
  // test device supports it or not.
  CreateWithoutSessionSupport(kWidevineKeySystem, kTestOrigin, kL3);
  EXPECT_TRUE(media_drm_bridge_);
  Unprovision();
}

TEST_F(MediaDrmBridgeTest, GetStatusForPolicy_FeatureFlagDisabled) {
  // Only test this if Widevine is supported. Otherwise
  // CreateWithoutSessionSupport() will return null and it can't be
  // tested.
  if (!MediaDrmBridge::IsKeySystemSupported(kWidevineKeySystem)) {
    GTEST_SKIP() << "Widevine not supported on device.";
  }

  scoped_feature_list_.InitWithFeatureState(media::kMediaDrmGetStatusForPolicy,
                                            false);

  CreateWithoutSessionSupport(kWidevineKeySystem, kTestOrigin, kL3);
  EXPECT_TRUE(media_drm_bridge_);

  CdmKeyInformation::KeyStatus key_status;
  CdmPromise::Exception exception;

  media_drm_bridge_->GetStatusForPolicy(
      HdcpVersion::kHdcpVersionNone,
      std::make_unique<MockCdmKeyStatusPromise>(
          /*expect_success=*/false, &key_status, &exception));

  EXPECT_EQ(exception, CdmPromise::Exception::NOT_SUPPORTED_ERROR);
}

TEST_F(MediaDrmBridgeTest, GetStatusForPolicy_ExternalClearKey) {
  scoped_feature_list_.InitWithFeatures({media::kExternalClearKeyForTesting},
                                        {});

  // TODO(b/263310318): Remove test skip when clear key is fixed and we call
  // into MediaDrm for Android ClearKey instead of using AesDecryptor.
  if (!MediaDrmBridge::IsKeySystemSupported(kExternalClearKeyKeySystem)) {
    GTEST_SKIP() << "ClearKey not supported on device.";
  }

  // ExternalClearKey only initialized with 'kDefault' security level.
  CreateWithoutSessionSupport(kExternalClearKeyKeySystem, kTestOrigin,
                              kDefault);
  EXPECT_TRUE_IF_KEY_SYSTEM_AVAILABLE(media_drm_bridge_,
                                      kExternalClearKeyKeySystem);

  CdmKeyInformation::KeyStatus key_status;

  media_drm_bridge_->GetStatusForPolicy(
      HdcpVersion::kHdcpVersionNone, std::make_unique<MockCdmKeyStatusPromise>(
                                         /*expect_success=*/true, &key_status));
  EXPECT_EQ(CdmKeyInformation::KeyStatus::USABLE, key_status);

  media_drm_bridge_->GetStatusForPolicy(
      HdcpVersion::kHdcpVersion1_0, std::make_unique<MockCdmKeyStatusPromise>(
                                        /*expect_success=*/true, &key_status));
  EXPECT_EQ(CdmKeyInformation::KeyStatus::OUTPUT_RESTRICTED, key_status);

  media_drm_bridge_->GetStatusForPolicy(
      HdcpVersion::kHdcpVersion2_3, std::make_unique<MockCdmKeyStatusPromise>(
                                        /*expect_success=*/true, &key_status));
  EXPECT_EQ(CdmKeyInformation::KeyStatus::OUTPUT_RESTRICTED, key_status);
}

TEST_F(MediaDrmBridgeTest, GetStatusForPolicyL3_Widevine) {
  // Only test this if Widevine is supported. Otherwise
  // CreateWithoutSessionSupport() will return null and it can't be
  // tested.
  if (!MediaDrmBridge::IsKeySystemSupported(kWidevineKeySystem)) {
    GTEST_SKIP() << "Widevine not supported on device.";
  }

  CreateWithoutSessionSupport(kWidevineKeySystem, kTestOrigin, kL3);
  EXPECT_TRUE(media_drm_bridge_);

  CdmKeyInformation::KeyStatus key_status;

  media_drm_bridge_->GetStatusForPolicy(
      HdcpVersion::kHdcpVersionNone, std::make_unique<MockCdmKeyStatusPromise>(
                                         /*expect_success=*/true, &key_status));
  EXPECT_EQ(CdmKeyInformation::KeyStatus::USABLE, key_status);

  media_drm_bridge_->GetStatusForPolicy(
      HdcpVersion::kHdcpVersion1_0, std::make_unique<MockCdmKeyStatusPromise>(
                                        /*expect_success=*/true, &key_status));
  EXPECT_EQ(CdmKeyInformation::KeyStatus::OUTPUT_RESTRICTED, key_status);

  media_drm_bridge_->GetStatusForPolicy(
      HdcpVersion::kHdcpVersion2_3, std::make_unique<MockCdmKeyStatusPromise>(
                                        /*expect_success=*/true, &key_status));
  EXPECT_EQ(CdmKeyInformation::KeyStatus::OUTPUT_RESTRICTED, key_status);
}

}  // namespace media