D3D11CdmProxy should be reinitializable after hardware content protection teardown

- Clean up after hardware content protection teardown notification so that
  another call to Initialize() would succeed.
- Once hardware content protection teardown is notified to the client, it should
  not notify more until it is reinitialized.
- Notify clients of hardware reset on power resume instead of power sleep.

Bug: 924794
Change-Id: Ia42768f86e012da59c07b3974cd0f1faf207f263
Reviewed-on: https://chromium-review.googlesource.com/c/1450648
Commit-Queue: Rintaro Kuroiwa <rkuroiwa@chromium.org>
Reviewed-by: Xiaohan Wang <xhwang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#629659}
diff --git a/media/gpu/windows/d3d11_cdm_proxy.cc b/media/gpu/windows/d3d11_cdm_proxy.cc
index b188fc5..930da80 100644
--- a/media/gpu/windows/d3d11_cdm_proxy.cc
+++ b/media/gpu/windows/d3d11_cdm_proxy.cc
@@ -163,7 +163,7 @@
   // Return true on success.
   bool RegisterHardwareContentProtectionTeardown(ComPtr<ID3D11Device> device);
 
-  // Regiesters for power events, specifically power suspend event.
+  // Regiesters for power events, specifically power resume event.
   // Returns true on success.
   bool RegisterPowerEvents();
 
@@ -172,7 +172,7 @@
 
   // base::PowerObserver implementation. Other power events are not relevant to
   // this class.
-  void OnSuspend() override;
+  void OnResume() override;
 
   // Stops watching for events. Good for clean up.
   void StopWatching();
@@ -543,6 +543,26 @@
 void D3D11CdmProxy::NotifyHardwareContentProtectionTeardown() {
   cdm_context_->OnHardwareReset();
   client_->NotifyHardwareReset();
+  Reset();
+}
+
+void D3D11CdmProxy::Reset() {
+  client_ = nullptr;
+  initialized_ = false;
+  crypto_session_map_.clear();
+  device_.Reset();
+  device_context_.Reset();
+  video_device_.Reset();
+  video_device1_.Reset();
+  video_context_.Reset();
+  video_context1_.Reset();
+  // Note that this deregisters hardware reset event watcher. It shouldn't
+  // notify the clients until this is reinitialized. Also the client is set to
+  // null in this method.
+  hardware_event_watcher_ = nullptr;
+  crypto_session_map_.clear();
+  private_input_size_ = 0;
+  private_output_size_ = 0;
 }
 
 D3D11CdmProxy::HardwareEventWatcher::~HardwareEventWatcher() {
@@ -627,7 +647,7 @@
   teardown_callback_.Run();
 }
 
-void D3D11CdmProxy::HardwareEventWatcher::OnSuspend() {
+void D3D11CdmProxy::HardwareEventWatcher::OnResume() {
   teardown_callback_.Run();
 }
 
diff --git a/media/gpu/windows/d3d11_cdm_proxy.h b/media/gpu/windows/d3d11_cdm_proxy.h
index 1fb77103f..8b62d2f 100644
--- a/media/gpu/windows/d3d11_cdm_proxy.h
+++ b/media/gpu/windows/d3d11_cdm_proxy.h
@@ -75,6 +75,9 @@
 
   void NotifyHardwareContentProtectionTeardown();
 
+  // Reset the state of this instance to be reinitializable.
+  void Reset();
+
   const GUID crypto_type_;
   const CdmProxy::Protocol protocol_;
   const FunctionIdMap function_id_map_;
@@ -90,6 +93,8 @@
   // Counter for assigning IDs to crypto sessions.
   uint32_t next_crypto_session_id_ = 1;
 
+  // Everything from here until weak ptr factory (which must be at the end)
+  // should be reset in Reset().
   Client* client_ = nullptr;
   bool initialized_ = false;
 
diff --git a/media/gpu/windows/d3d11_cdm_proxy_unittest.cc b/media/gpu/windows/d3d11_cdm_proxy_unittest.cc
index ff24d6b..cee91d5 100644
--- a/media/gpu/windows/d3d11_cdm_proxy_unittest.cc
+++ b/media/gpu/windows/d3d11_cdm_proxy_unittest.cc
@@ -24,6 +24,7 @@
 using ::testing::_;
 using ::testing::AllOf;
 using ::testing::AtLeast;
+using ::testing::AtMost;
 using ::testing::DoAll;
 using ::testing::Invoke;
 using ::testing::InvokeWithoutArgs;
@@ -51,8 +52,13 @@
 
 class MockPowerMonitorSource : public base::PowerMonitorSource {
  public:
-  // Use this method to send a power suspend event.
-  void Suspend() { ProcessPowerEvent(SUSPEND_EVENT); }
+  // Use this method to send a power resume event.
+  void Resume() {
+    // Due to how ProcessPowerEvent() works, it has to be suspended first to
+    // resume.
+    ProcessPowerEvent(SUSPEND_EVENT);
+    ProcessPowerEvent(RESUME_EVENT);
+  }
 
   MOCK_METHOD0(Shutdown, void());
   MOCK_METHOD0(IsOnBatteryPowerImpl, bool());
@@ -92,7 +98,7 @@
     function_id_map[kTestFunction] = kTestFunctionId;
 
     // Use NiceMock because we don't care about base::PowerMonitorSource events
-    // other than calling Suspend() directly.
+    // other than calling Resume() directly.
     auto mock_power_monitor_source =
         std::make_unique<NiceMock<MockPowerMonitorSource>>();
     mock_power_monitor_source_ = mock_power_monitor_source.get();
@@ -189,8 +195,8 @@
                              Return(S_OK)));
   }
 
-  // Helper method to do Initialize(). Only useful if the test doesn't require
-  // access to the mocks later.
+  // Helper method to do Initialize(). The returned mock objects are accessible
+  // thru member variables.
   void Initialize(CdmProxy::Client* client, CdmProxy::InitializeCB callback) {
     EXPECT_CALL(create_device_mock_,
                 Create(_, D3D_DRIVER_TYPE_HARDWARE, _, _, _, _, _, _, _, _));
@@ -235,10 +241,33 @@
     Mock::VerifyAndClearExpectations(video_context1_mock_.Get());
   }
 
+  // Test case where the proxy is initialized and then hardware content
+  // protection teardown is notified.
+  void HardwareContentProtectionTeardown() {
+    base::RunLoop run_loop;
+
+    EXPECT_CALL(callback_mock_,
+                InitializeCallback(CdmProxy::Status::kOk, _, _));
+    ASSERT_NO_FATAL_FAILURE(Initialize(
+        &client_, base::BindOnce(&CallbackMock::InitializeCallback,
+                                 base::Unretained(&callback_mock_))));
+
+    EXPECT_CALL(client_, NotifyHardwareReset());
+
+    base::MockCallback<CdmContext::EventCB> event_cb;
+    auto callback_registration =
+        proxy_->GetCdmContext()->RegisterEventCB(event_cb.Get());
+    EXPECT_CALL(event_cb, Run(CdmContext::Event::kHardwareContextLost))
+        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+
+    SetEvent(teardown_event_);
+    run_loop.Run();
+  }
+
   MockProxyClient client_;
   std::unique_ptr<D3D11CdmProxy> proxy_;
   std::unique_ptr<base::PowerMonitor> power_monitor_;
-  // Owned by power_monitor_. Use this to simulate a power-suspend.
+  // Owned by power_monitor_. Use this to simulate a power-resume.
   MockPowerMonitorSource* mock_power_monitor_source_;
 
   D3D11CreateDeviceMock create_device_mock_;
@@ -291,22 +320,16 @@
 // Hardware content protection teardown is notified to the proxy.
 // Verify that the client is notified.
 TEST_F(D3D11CdmProxyTest, HardwareContentProtectionTeardown) {
-  base::RunLoop run_loop;
+  EXPECT_NO_FATAL_FAILURE(HardwareContentProtectionTeardown());
+}
 
-  EXPECT_CALL(client_, NotifyHardwareReset());
-
-  base::MockCallback<CdmContext::EventCB> event_cb;
-  auto callback_registration =
-      proxy_->GetCdmContext()->RegisterEventCB(event_cb.Get());
-  EXPECT_CALL(event_cb, Run(CdmContext::Event::kHardwareContextLost))
-      .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-
+// Verify that initialization after hardware content protection teardown works..
+TEST_F(D3D11CdmProxyTest, HardwareContentProtectionTeardownThenInitialize) {
+  ASSERT_NO_FATAL_FAILURE(HardwareContentProtectionTeardown());
   EXPECT_CALL(callback_mock_, InitializeCallback(CdmProxy::Status::kOk, _, _));
   ASSERT_NO_FATAL_FAILURE(
       Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
                                           base::Unretained(&callback_mock_))));
-  SetEvent(teardown_event_);
-  run_loop.Run();
 }
 
 // Verify that failing to register to hardware content protection teardown
@@ -326,18 +349,41 @@
 }
 
 // Verify that the client is notified on power suspend.
-TEST_F(D3D11CdmProxyTest, PowerSuspend) {
+TEST_F(D3D11CdmProxyTest, PowerResume) {
   base::RunLoop run_loop;
 
-  EXPECT_CALL(client_, NotifyHardwareReset()).WillOnce(Invoke([&run_loop]() {
-    run_loop.Quit();
-  }));
-
   EXPECT_CALL(callback_mock_, InitializeCallback(CdmProxy::Status::kOk, _, _));
   ASSERT_NO_FATAL_FAILURE(
       Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
                                           base::Unretained(&callback_mock_))));
-  mock_power_monitor_source_->Suspend();
+
+  EXPECT_CALL(client_, NotifyHardwareReset()).WillOnce(Invoke([&run_loop]() {
+    run_loop.Quit();
+  }));
+
+  mock_power_monitor_source_->Resume();
+  run_loop.Run();
+}
+
+// IRL power resume is notified and then hardware content protection teardown
+// is notified. Make sure that the two notifications don't signal the clients
+// more than once (without being reinitialized in between the notifications).
+// Note that this test uses QuitWhenIdle(). If both notifications are processed
+// this test will run forever.
+TEST_F(D3D11CdmProxyTest, PowerResumeAndHardwareContentProtectionTeardown) {
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(callback_mock_, InitializeCallback(CdmProxy::Status::kOk, _, _));
+  ASSERT_NO_FATAL_FAILURE(
+      Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
+                                          base::Unretained(&callback_mock_))));
+
+  EXPECT_CALL(client_, NotifyHardwareReset())
+      .Times(1)
+      .WillOnce(Invoke([&run_loop]() { run_loop.QuitWhenIdle(); }));
+
+  mock_power_monitor_source_->Resume();
+  SetEvent(teardown_event_);
   run_loop.Run();
 }
 
@@ -744,14 +790,6 @@
 TEST_F(D3D11CdmProxyTest, ClearKeysAfterHardwareContentProtectionTeardown) {
   base::RunLoop run_loop;
 
-  EXPECT_CALL(client_, NotifyHardwareReset()).WillOnce(Invoke([&run_loop]() {
-    run_loop.Quit();
-  }));
-
-  base::WeakPtr<CdmContext> context = proxy_->GetCdmContext();
-  ASSERT_TRUE(context);
-  CdmProxyContext* proxy_context = context->GetCdmProxyContext();
-
   uint32_t crypto_session_id_from_initialize = 0;
   EXPECT_CALL(callback_mock_,
               InitializeCallback(CdmProxy::Status::kOk, kTestProtocol, _))
@@ -775,9 +813,17 @@
                  base::BindOnce(&CallbackMock::SetKeyCallback,
                                 base::Unretained(&callback_mock_)));
 
+  EXPECT_CALL(client_, NotifyHardwareReset()).WillOnce(Invoke([&run_loop]() {
+    run_loop.Quit();
+  }));
+
   SetEvent(teardown_event_);
   run_loop.Run();
 
+  base::WeakPtr<CdmContext> context = proxy_->GetCdmContext();
+  ASSERT_TRUE(context);
+  CdmProxyContext* proxy_context = context->GetCdmProxyContext();
+
   std::string key_id_str(kKeyId.begin(), kKeyId.end());
   auto decrypt_context =
       proxy_context->GetD3D11DecryptContext(kTestKeyType, key_id_str);