text: // Copyright 2020 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "src/camera/bin/device_watcher/device_instance.h"
  
  #include <fuchsia/camera2/hal/cpp/fidl.h>
  #include <lib/sys/service/cpp/service.h>
  #include <lib/syslog/cpp/macros.h>
  #include <zircon/errors.h>
  
  #include "fuchsia/io/cpp/fidl.h"
  #include "lib/fit/function.h"
  #include "lib/sys/cpp/service_directory.h"
  
  // Known path for the camera_device package manifest.
  constexpr auto kCameraDeviceUrl = "fuchsia-pkg://fuchsia.com/camera_device#meta/camera_device.cmx";
  
  // Arbitrary string identifying the camera device protocol. The protocol does not have a fixed name
  // because it is not marked as [Discoverable].
  constexpr auto kCameraPublishedServiceName = "PublishedCameraService";
  
  DeviceInstance::DeviceInstance() : loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {}
  
  fit::result<std::unique_ptr<DeviceInstance>, zx_status_t> DeviceInstance::Create(
      const fuchsia::sys::LauncherPtr& launcher,
      fidl::InterfaceHandle<fuchsia::hardware::camera::Device> camera,
      fit::closure on_component_unavailable) {
    auto instance = std::make_unique<DeviceInstance>();
  
    // Bind the camera channel.
    zx_status_t status = instance->camera_.Bind(std::move(camera), instance->loop_.dispatcher());
    if (status != ZX_OK) {
      FX_PLOGS(ERROR, status);
      return fit::error(status);
    }
  
    // Add the camera controller as an injected service.
    fidl::InterfaceRequestHandler<fuchsia::camera2::hal::Controller> handler =
        fit::bind_member(instance.get(), &DeviceInstance::OnControllerRequested);
    status = instance->injected_services_dir_.AddEntry(
        fuchsia::camera2::hal::Controller::Name_, std::make_unique<vfs::Service>(std::move(handler)));
    if (status != ZX_OK) {
      FX_PLOGS(ERROR, status);
      return fit::error(status);
    }
    auto additional_services = fuchsia::sys::ServiceList::New();
    additional_services->names.push_back(fuchsia::camera2::hal::Controller::Name_);
  
    // Bind the injected services directory to the given channel.
    fidl::InterfaceHandle<fuchsia::io::Directory> injected_services_dir_channel;
    status = instance->injected_services_dir_.Serve(
        fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE,
        injected_services_dir_channel.NewRequest().TakeChannel());
    if (status != ZX_OK) {
      FX_PLOGS(ERROR, status);
      return fit::error(status);
    }
  
    // Create a service directory into which the component will publish services.
    zx::channel directory_request;
    instance->published_services_ = sys::ServiceDirectory::CreateWithRequest(&directory_request);
  
    // Launch the Device component using the injected services channel.
    fuchsia::sys::LaunchInfo launch_info{};
    launch_info.url = kCameraDeviceUrl;
    launch_info.arguments = {kCameraPublishedServiceName};
    launch_info.directory_request = std::move(directory_request);
    launch_info.additional_services = std::move(additional_services);
    launch_info.additional_services->host_directory = injected_services_dir_channel.TakeChannel();
    launcher->CreateComponent(std::move(launch_info), instance->component_controller_.NewRequest(
                                                          instance->loop_.dispatcher()));
  
    // Bind component event handlers.
    instance->component_controller_.events().OnDirectoryReady =
        fit::bind_member(instance.get(), &DeviceInstance::OnServicesReady);
    instance->component_controller_.events().OnTerminated =
        [callback = on_component_unavailable.share()](int64_t return_code,
                                                      fuchsia::sys::TerminationReason reason) {
          FX_LOGS(WARNING) << "Camera Device Component exited with code " << return_code
                           << " and reason " << static_cast<uint32_t>(reason);
          callback();
        };
  
    // Bind error handlers.
    instance->component_controller_.set_error_handler(
        [callback = on_component_unavailable.share()](zx_status_t status) {
          FX_PLOGS(WARNING, status) << "Component controller disconnected.";
          // Invoke the callback here since OnTerminated won't be called.
          callback();
        });
    instance->camera_.set_error_handler([instance = instance.get()](zx_status_t status) {
      FX_PLOGS(WARNING, status) << "Camera device server disconnected.";
      instance->camera_ = nullptr;
    });
  
    // Start the loop.
    ZX_ASSERT(instance->loop_.StartThread("Camera Device Instance") == ZX_OK);
  
    return fit::ok(std::move(instance));
  }
  
  void DeviceInstance::OnCameraRequested(fidl::InterfaceRequest<fuchsia::camera3::Device> request) {
    if (!services_ready_) {
      pending_requests_.push_back(std::move(request));
      return;
    }
    published_services_->Connect(std::move(request), kCameraPublishedServiceName);
  }
  
  void DeviceInstance::OnServicesReady() {
    services_ready_ = true;
    for (auto& request : pending_requests_) {
      OnCameraRequested(std::move(request));
    }
  }
  
  void DeviceInstance::OnControllerRequested(
      fidl::InterfaceRequest<fuchsia::camera2::hal::Controller> request) {
    if (!camera_) {
      request.Close(ZX_ERR_UNAVAILABLE);
      return;
    }
    camera_->GetChannel2(request.TakeChannel());
  }
  