[camera_windows] Allow retrying after initialization failure (#6119)

diff --git a/packages/camera/camera_windows/CHANGELOG.md b/packages/camera/camera_windows/CHANGELOG.md
index 6883b55..a1e2a07 100644
--- a/packages/camera/camera_windows/CHANGELOG.md
+++ b/packages/camera/camera_windows/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.1.0+4
+
+* Allows retrying camera initialization after error.
+
 ## 0.1.0+3
 
 * Updates the README to better explain how to use the unendorsed package.
diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml
index 055313b..e9fe021 100644
--- a/packages/camera/camera_windows/pubspec.yaml
+++ b/packages/camera/camera_windows/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin for getting information about and controlling the camera on Windows.
 repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_windows
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
-version: 0.1.0+3
+version: 0.1.0+4
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp
index c21f8ab..617f98f 100644
--- a/packages/camera/camera_windows/windows/camera.cpp
+++ b/packages/camera/camera_windows/windows/camera.cpp
@@ -28,17 +28,17 @@
                              "Plugin disposed before request was handled");
 }
 
-void CameraImpl::InitCamera(flutter::TextureRegistrar* texture_registrar,
+bool CameraImpl::InitCamera(flutter::TextureRegistrar* texture_registrar,
                             flutter::BinaryMessenger* messenger,
                             bool record_audio,
                             ResolutionPreset resolution_preset) {
   auto capture_controller_factory =
       std::make_unique<CaptureControllerFactoryImpl>();
-  InitCamera(std::move(capture_controller_factory), texture_registrar,
-             messenger, record_audio, resolution_preset);
+  return InitCamera(std::move(capture_controller_factory), texture_registrar,
+                    messenger, record_audio, resolution_preset);
 }
 
-void CameraImpl::InitCamera(
+bool CameraImpl::InitCamera(
     std::unique_ptr<CaptureControllerFactory> capture_controller_factory,
     flutter::TextureRegistrar* texture_registrar,
     flutter::BinaryMessenger* messenger, bool record_audio,
@@ -47,8 +47,8 @@
   messenger_ = messenger;
   capture_controller_ =
       capture_controller_factory->CreateCaptureController(this);
-  capture_controller_->InitCaptureDevice(texture_registrar, device_id_,
-                                         record_audio, resolution_preset);
+  return capture_controller_->InitCaptureDevice(
+      texture_registrar, device_id_, record_audio, resolution_preset);
 }
 
 bool CameraImpl::AddPendingResult(
diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h
index 6996231..429f41a 100644
--- a/packages/camera/camera_windows/windows/camera.h
+++ b/packages/camera/camera_windows/windows/camera.h
@@ -63,7 +63,9 @@
   virtual camera_windows::CaptureController* GetCaptureController() = 0;
 
   // Initializes this camera and its associated capture controller.
-  virtual void InitCamera(flutter::TextureRegistrar* texture_registrar,
+  //
+  // Returns false if initialization fails.
+  virtual bool InitCamera(flutter::TextureRegistrar* texture_registrar,
                           flutter::BinaryMessenger* messenger,
                           bool record_audio,
                           ResolutionPreset resolution_preset) = 0;
@@ -116,7 +118,7 @@
   camera_windows::CaptureController* GetCaptureController() override {
     return capture_controller_.get();
   }
-  void InitCamera(flutter::TextureRegistrar* texture_registrar,
+  bool InitCamera(flutter::TextureRegistrar* texture_registrar,
                   flutter::BinaryMessenger* messenger, bool record_audio,
                   ResolutionPreset resolution_preset) override;
 
@@ -124,7 +126,9 @@
   //
   // This is a convenience method called by |InitCamera| but also used in
   // tests.
-  void InitCamera(
+  //
+  // Returns false if initialization fails.
+  bool InitCamera(
       std::unique_ptr<CaptureControllerFactory> capture_controller_factory,
       flutter::TextureRegistrar* texture_registrar,
       flutter::BinaryMessenger* messenger, bool record_audio,
diff --git a/packages/camera/camera_windows/windows/camera_plugin.cpp b/packages/camera/camera_windows/windows/camera_plugin.cpp
index 3b795e0..5503d17 100644
--- a/packages/camera/camera_windows/windows/camera_plugin.cpp
+++ b/packages/camera/camera_windows/windows/camera_plugin.cpp
@@ -398,9 +398,11 @@
       resolution_preset = ResolutionPreset::kAuto;
     }
 
-    camera->InitCamera(texture_registrar_, messenger_, *record_audio,
-                       resolution_preset);
-    cameras_.push_back(std::move(camera));
+    bool initialized = camera->InitCamera(texture_registrar_, messenger_,
+                                          *record_audio, resolution_preset);
+    if (initialized) {
+      cameras_.push_back(std::move(camera));
+    }
   }
 }
 
diff --git a/packages/camera/camera_windows/windows/capture_controller.cpp b/packages/camera/camera_windows/windows/capture_controller.cpp
index 084b036..6c89060 100644
--- a/packages/camera/camera_windows/windows/capture_controller.cpp
+++ b/packages/camera/camera_windows/windows/capture_controller.cpp
@@ -288,17 +288,19 @@
   texture_handler_ = nullptr;
 }
 
-void CaptureControllerImpl::InitCaptureDevice(
+bool CaptureControllerImpl::InitCaptureDevice(
     flutter::TextureRegistrar* texture_registrar, const std::string& device_id,
     bool record_audio, ResolutionPreset resolution_preset) {
   assert(capture_controller_listener_);
 
   if (IsInitialized()) {
-    return capture_controller_listener_->OnCreateCaptureEngineFailed(
+    capture_controller_listener_->OnCreateCaptureEngineFailed(
         "Capture device already initialized");
+    return false;
   } else if (capture_engine_state_ == CaptureEngineState::kInitializing) {
-    return capture_controller_listener_->OnCreateCaptureEngineFailed(
+    capture_controller_listener_->OnCreateCaptureEngineFailed(
         "Capture device already initializing");
+    return false;
   }
 
   capture_engine_state_ = CaptureEngineState::kInitializing;
@@ -315,7 +317,7 @@
       capture_controller_listener_->OnCreateCaptureEngineFailed(
           "Failed to create camera");
       ResetCaptureController();
-      return;
+      return false;
     }
 
     media_foundation_started_ = true;
@@ -326,8 +328,10 @@
     capture_controller_listener_->OnCreateCaptureEngineFailed(
         "Failed to create camera");
     ResetCaptureController();
-    return;
+    return false;
   }
+
+  return true;
 }
 
 void CaptureControllerImpl::TakePicture(const std::string& file_path) {
diff --git a/packages/camera/camera_windows/windows/capture_controller.h b/packages/camera/camera_windows/windows/capture_controller.h
index 34e3781..0b7ab66 100644
--- a/packages/camera/camera_windows/windows/capture_controller.h
+++ b/packages/camera/camera_windows/windows/capture_controller.h
@@ -75,6 +75,9 @@
 
   // Initializes the capture controller with the specified device id.
   //
+  // Returns false if the capture controller could not be initialized
+  // or is already initialized.
+  //
   // texture_registrar: Pointer to Flutter TextureRegistrar instance. Used to
   //                    register texture for capture preview.
   // device_id:         A string that holds information of camera device id to
@@ -82,7 +85,7 @@
   // record_audio:      A boolean value telling if audio should be captured on
   //                    video recording.
   // resolution_preset: Maximum capture resolution height.
-  virtual void InitCaptureDevice(TextureRegistrar* texture_registrar,
+  virtual bool InitCaptureDevice(TextureRegistrar* texture_registrar,
                                  const std::string& device_id,
                                  bool record_audio,
                                  ResolutionPreset resolution_preset) = 0;
@@ -132,7 +135,7 @@
   CaptureControllerImpl& operator=(const CaptureControllerImpl&) = delete;
 
   // CaptureController
-  void InitCaptureDevice(TextureRegistrar* texture_registrar,
+  bool InitCaptureDevice(TextureRegistrar* texture_registrar,
                          const std::string& device_id, bool record_audio,
                          ResolutionPreset resolution_preset) override;
   uint32_t GetPreviewWidth() const override { return preview_frame_width_; }
diff --git a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp
index 309268a..9cab069 100644
--- a/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp
+++ b/packages/camera/camera_windows/windows/test/camera_plugin_test.cpp
@@ -30,6 +30,41 @@
 using ::testing::Pointee;
 using ::testing::Return;
 
+void MockInitCamera(MockCamera* camera, bool success) {
+  EXPECT_CALL(*camera,
+              HasPendingResultByType(Eq(PendingResultType::kCreateCamera)))
+      .Times(1)
+      .WillOnce(Return(false));
+
+  EXPECT_CALL(*camera,
+              AddPendingResult(Eq(PendingResultType::kCreateCamera), _))
+      .Times(1)
+      .WillOnce([camera](PendingResultType type,
+                         std::unique_ptr<MethodResult<>> result) {
+        camera->pending_result_ = std::move(result);
+        return true;
+      });
+
+  EXPECT_CALL(*camera, HasDeviceId(Eq(camera->device_id_)))
+      .WillRepeatedly(Return(true));
+
+  EXPECT_CALL(*camera, InitCamera)
+      .Times(1)
+      .WillOnce([camera, success](flutter::TextureRegistrar* texture_registrar,
+                                  flutter::BinaryMessenger* messenger,
+                                  bool record_audio,
+                                  ResolutionPreset resolution_preset) {
+        assert(camera->pending_result_);
+        if (success) {
+          camera->pending_result_->Success(EncodableValue(1));
+          return true;
+        } else {
+          camera->pending_result_->Error("camera_error", "InitCamera failed.");
+          return false;
+        }
+      });
+}
+
 TEST(CameraPlugin, AvailableCamerasHandlerSuccessIfNoCameras) {
   std::unique_ptr<MockTextureRegistrar> texture_registrar_ =
       std::make_unique<MockTextureRegistrar>();
@@ -99,28 +134,7 @@
   std::unique_ptr<MockCamera> camera =
       std::make_unique<MockCamera>(MOCK_DEVICE_ID);
 
-  EXPECT_CALL(*camera,
-              HasPendingResultByType(Eq(PendingResultType::kCreateCamera)))
-      .Times(1)
-      .WillOnce(Return(false));
-
-  EXPECT_CALL(*camera,
-              AddPendingResult(Eq(PendingResultType::kCreateCamera), _))
-      .Times(1)
-      .WillOnce([cam = camera.get()](PendingResultType type,
-                                     std::unique_ptr<MethodResult<>> result) {
-        cam->pending_result_ = std::move(result);
-        return true;
-      });
-  EXPECT_CALL(*camera, InitCamera)
-      .Times(1)
-      .WillOnce([cam = camera.get()](
-                    flutter::TextureRegistrar* texture_registrar,
-                    flutter::BinaryMessenger* messenger, bool record_audio,
-                    ResolutionPreset resolution_preset) {
-        assert(cam->pending_result_);
-        return cam->pending_result_->Success(EncodableValue(1));
-      });
+  MockInitCamera(camera.get(), true);
 
   // Move mocked camera to the factory to be passed
   // for plugin with CreateCamera function.
@@ -185,34 +199,7 @@
   std::unique_ptr<MockCamera> camera =
       std::make_unique<MockCamera>(MOCK_DEVICE_ID);
 
-  EXPECT_CALL(*camera,
-              HasPendingResultByType(Eq(PendingResultType::kCreateCamera)))
-      .Times(1)
-      .WillOnce(Return(false));
-
-  EXPECT_CALL(*camera,
-              AddPendingResult(Eq(PendingResultType::kCreateCamera), _))
-      .Times(1)
-      .WillOnce([cam = camera.get()](PendingResultType type,
-                                     std::unique_ptr<MethodResult<>> result) {
-        cam->pending_result_ = std::move(result);
-        return true;
-      });
-  EXPECT_CALL(*camera, InitCamera)
-      .Times(1)
-      .WillOnce([cam = camera.get()](
-                    flutter::TextureRegistrar* texture_registrar,
-                    flutter::BinaryMessenger* messenger, bool record_audio,
-                    ResolutionPreset resolution_preset) {
-        assert(cam->pending_result_);
-        return cam->pending_result_->Success(EncodableValue(1));
-      });
-
-  EXPECT_CALL(*camera, HasDeviceId(Eq(MOCK_DEVICE_ID)))
-      .Times(1)
-      .WillOnce([cam = camera.get()](std::string& device_id) {
-        return cam->device_id_ == device_id;
-      });
+  MockInitCamera(camera.get(), true);
 
   // Move mocked camera to the factory to be passed
   // for plugin with CreateCamera function.
@@ -246,6 +233,64 @@
       std::move(second_create_result));
 }
 
+TEST(CameraPlugin, CreateHandlerAllowsRetry) {
+  std::unique_ptr<MockMethodResult> first_create_result =
+      std::make_unique<MockMethodResult>();
+  std::unique_ptr<MockMethodResult> second_create_result =
+      std::make_unique<MockMethodResult>();
+  std::unique_ptr<MockTextureRegistrar> texture_registrar_ =
+      std::make_unique<MockTextureRegistrar>();
+  std::unique_ptr<MockBinaryMessenger> messenger_ =
+      std::make_unique<MockBinaryMessenger>();
+  std::unique_ptr<MockCameraFactory> camera_factory_ =
+      std::make_unique<MockCameraFactory>();
+
+  // The camera will fail initialization once and then succeed.
+  EXPECT_CALL(*camera_factory_, CreateCamera(MOCK_DEVICE_ID))
+      .Times(2)
+      .WillOnce([](const std::string& device_id) {
+        std::unique_ptr<MockCamera> first_camera =
+            std::make_unique<MockCamera>(MOCK_DEVICE_ID);
+
+        MockInitCamera(first_camera.get(), false);
+
+        return first_camera;
+      })
+      .WillOnce([](const std::string& device_id) {
+        std::unique_ptr<MockCamera> second_camera =
+            std::make_unique<MockCamera>(MOCK_DEVICE_ID);
+
+        MockInitCamera(second_camera.get(), true);
+
+        return second_camera;
+      });
+
+  EXPECT_CALL(*first_create_result, ErrorInternal).Times(1);
+  EXPECT_CALL(*first_create_result, SuccessInternal).Times(0);
+
+  CameraPlugin plugin(texture_registrar_.get(), messenger_.get(),
+                      std::move(camera_factory_));
+  EncodableMap args = {
+      {EncodableValue("cameraName"), EncodableValue(MOCK_CAMERA_NAME)},
+      {EncodableValue("resolutionPreset"), EncodableValue(nullptr)},
+      {EncodableValue("enableAudio"), EncodableValue(true)},
+  };
+
+  plugin.HandleMethodCall(
+      flutter::MethodCall("create",
+                          std::make_unique<EncodableValue>(EncodableMap(args))),
+      std::move(first_create_result));
+
+  EXPECT_CALL(*second_create_result, ErrorInternal).Times(0);
+  EXPECT_CALL(*second_create_result,
+              SuccessInternal(Pointee(EncodableValue(1))));
+
+  plugin.HandleMethodCall(
+      flutter::MethodCall("create",
+                          std::make_unique<EncodableValue>(EncodableMap(args))),
+      std::move(second_create_result));
+}
+
 TEST(CameraPlugin, InitializeHandlerCallStartPreview) {
   int64_t mock_camera_id = 1234;
 
diff --git a/packages/camera/camera_windows/windows/test/camera_test.cpp b/packages/camera/camera_windows/windows/test/camera_test.cpp
index 899c1fd..97e3ce1 100644
--- a/packages/camera/camera_windows/windows/test/camera_test.cpp
+++ b/packages/camera/camera_windows/windows/test/camera_test.cpp
@@ -23,6 +23,7 @@
 using ::testing::Eq;
 using ::testing::NiceMock;
 using ::testing::Pointee;
+using ::testing::Return;
 
 namespace test {
 
@@ -34,17 +35,57 @@
 
   EXPECT_CALL(*capture_controller_factory, CreateCaptureController)
       .Times(1)
-      .WillOnce(
-          []() { return std::make_unique<NiceMock<MockCaptureController>>(); });
+      .WillOnce([]() {
+        std::unique_ptr<NiceMock<MockCaptureController>> capture_controller =
+            std::make_unique<NiceMock<MockCaptureController>>();
+
+        EXPECT_CALL(*capture_controller, InitCaptureDevice)
+            .Times(1)
+            .WillOnce(Return(true));
+
+        return capture_controller;
+      });
 
   EXPECT_TRUE(camera->GetCaptureController() == nullptr);
 
   // Init camera with mock capture controller factory
-  camera->InitCamera(std::move(capture_controller_factory),
-                     std::make_unique<MockTextureRegistrar>().get(),
-                     std::make_unique<MockBinaryMessenger>().get(), false,
-                     ResolutionPreset::kAuto);
+  bool result =
+      camera->InitCamera(std::move(capture_controller_factory),
+                         std::make_unique<MockTextureRegistrar>().get(),
+                         std::make_unique<MockBinaryMessenger>().get(), false,
+                         ResolutionPreset::kAuto);
+  EXPECT_TRUE(result);
+  EXPECT_TRUE(camera->GetCaptureController() != nullptr);
+}
 
+TEST(Camera, InitCameraReportsFailure) {
+  std::unique_ptr<CameraImpl> camera =
+      std::make_unique<CameraImpl>(MOCK_DEVICE_ID);
+  std::unique_ptr<MockCaptureControllerFactory> capture_controller_factory =
+      std::make_unique<MockCaptureControllerFactory>();
+
+  EXPECT_CALL(*capture_controller_factory, CreateCaptureController)
+      .Times(1)
+      .WillOnce([]() {
+        std::unique_ptr<NiceMock<MockCaptureController>> capture_controller =
+            std::make_unique<NiceMock<MockCaptureController>>();
+
+        EXPECT_CALL(*capture_controller, InitCaptureDevice)
+            .Times(1)
+            .WillOnce(Return(false));
+
+        return capture_controller;
+      });
+
+  EXPECT_TRUE(camera->GetCaptureController() == nullptr);
+
+  // Init camera with mock capture controller factory
+  bool result =
+      camera->InitCamera(std::move(capture_controller_factory),
+                         std::make_unique<MockTextureRegistrar>().get(),
+                         std::make_unique<MockBinaryMessenger>().get(), false,
+                         ResolutionPreset::kAuto);
+  EXPECT_FALSE(result);
   EXPECT_TRUE(camera->GetCaptureController() != nullptr);
 }
 
diff --git a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp
index 7520af7..083f823 100644
--- a/packages/camera/camera_windows/windows/test/capture_controller_test.cpp
+++ b/packages/camera/camera_windows/windows/test/capture_controller_test.cpp
@@ -59,8 +59,10 @@
       .Times(1);
   EXPECT_CALL(*engine, Initialize).Times(1);
 
-  capture_controller->InitCaptureDevice(texture_registrar, MOCK_DEVICE_ID, true,
-                                        ResolutionPreset::kAuto);
+  bool result = capture_controller->InitCaptureDevice(
+      texture_registrar, MOCK_DEVICE_ID, true, ResolutionPreset::kAuto);
+
+  EXPECT_TRUE(result);
 
   // MockCaptureEngine::Initialize is called
   EXPECT_TRUE(engine->initialized_);
@@ -214,6 +216,78 @@
   engine = nullptr;
 }
 
+TEST(CaptureController, InitCaptureEngineCanOnlyBeCalledOnce) {
+  ComPtr<MockCaptureEngine> engine = new MockCaptureEngine();
+  std::unique_ptr<MockCamera> camera =
+      std::make_unique<MockCamera>(MOCK_DEVICE_ID);
+  std::unique_ptr<CaptureControllerImpl> capture_controller =
+      std::make_unique<CaptureControllerImpl>(camera.get());
+  std::unique_ptr<MockTextureRegistrar> texture_registrar =
+      std::make_unique<MockTextureRegistrar>();
+
+  uint64_t mock_texture_id = 1234;
+
+  // Init capture controller once with mocks and tests
+  MockInitCaptureController(capture_controller.get(), texture_registrar.get(),
+                            engine.Get(), camera.get(), mock_texture_id);
+
+  // Init capture controller a second time.
+  EXPECT_CALL(*camera, OnCreateCaptureEngineFailed).Times(1);
+
+  bool result = capture_controller->InitCaptureDevice(
+      texture_registrar.get(), MOCK_DEVICE_ID, true, ResolutionPreset::kAuto);
+
+  EXPECT_FALSE(result);
+
+  capture_controller = nullptr;
+  camera = nullptr;
+  texture_registrar = nullptr;
+  engine = nullptr;
+}
+
+TEST(CaptureController, InitCaptureEngineReportsFailure) {
+  ComPtr<MockCaptureEngine> engine = new MockCaptureEngine();
+  std::unique_ptr<MockCamera> camera =
+      std::make_unique<MockCamera>(MOCK_DEVICE_ID);
+  std::unique_ptr<CaptureControllerImpl> capture_controller =
+      std::make_unique<CaptureControllerImpl>(camera.get());
+  std::unique_ptr<MockTextureRegistrar> texture_registrar =
+      std::make_unique<MockTextureRegistrar>();
+
+  ComPtr<MockMediaSource> video_source = new MockMediaSource();
+  ComPtr<MockMediaSource> audio_source = new MockMediaSource();
+
+  capture_controller->SetCaptureEngine(
+      reinterpret_cast<IMFCaptureEngine*>(engine.Get()));
+  capture_controller->SetVideoSource(
+      reinterpret_cast<IMFMediaSource*>(video_source.Get()));
+  capture_controller->SetAudioSource(
+      reinterpret_cast<IMFMediaSource*>(audio_source.Get()));
+
+  EXPECT_CALL(*texture_registrar, RegisterTexture).Times(0);
+  EXPECT_CALL(*texture_registrar, UnregisterTexture).Times(0);
+  EXPECT_CALL(*camera, OnCreateCaptureEngineSucceeded).Times(0);
+
+  EXPECT_CALL(*engine.Get(), Initialize)
+      .Times(1)
+      .WillOnce(Return(E_ACCESSDENIED));
+
+  EXPECT_CALL(*camera,
+              OnCreateCaptureEngineFailed(Eq("Failed to create camera")))
+      .Times(1);
+
+  bool result = capture_controller->InitCaptureDevice(
+      texture_registrar.get(), MOCK_DEVICE_ID, true, ResolutionPreset::kAuto);
+
+  EXPECT_FALSE(result);
+  EXPECT_FALSE(engine->initialized_);
+
+  capture_controller = nullptr;
+  camera = nullptr;
+  texture_registrar = nullptr;
+  engine = nullptr;
+}
+
 TEST(CaptureController, StartPreviewStartsProcessingSamples) {
   ComPtr<MockCaptureEngine> engine = new MockCaptureEngine();
   std::unique_ptr<MockCamera> camera =
diff --git a/packages/camera/camera_windows/windows/test/mocks.h b/packages/camera/camera_windows/windows/test/mocks.h
index 0781989..53101f5 100644
--- a/packages/camera/camera_windows/windows/test/mocks.h
+++ b/packages/camera/camera_windows/windows/test/mocks.h
@@ -182,7 +182,7 @@
   MOCK_METHOD(camera_windows::CaptureController*, GetCaptureController, (),
               (override));
 
-  MOCK_METHOD(void, InitCamera,
+  MOCK_METHOD(bool, InitCamera,
               (flutter::TextureRegistrar * texture_registrar,
                flutter::BinaryMessenger* messenger, bool record_audio,
                ResolutionPreset resolution_preset),
@@ -212,7 +212,7 @@
  public:
   ~MockCaptureController() = default;
 
-  MOCK_METHOD(void, InitCaptureDevice,
+  MOCK_METHOD(bool, InitCaptureDevice,
               (flutter::TextureRegistrar * texture_registrar,
                const std::string& device_id, bool record_audio,
                ResolutionPreset resolution_preset),