blob: ea45166cb78038ca455f78d2f96fc49887156c48 [file] [log] [blame]
// 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_watcher_impl.h"
#include <fuchsia/camera2/hal/cpp/fidl.h>
#include <fuchsia/camera3/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/fit/function.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <algorithm>
#include <limits>
DeviceWatcherImpl::DeviceWatcherImpl() : loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {}
DeviceWatcherImpl::~DeviceWatcherImpl() { loop_.Shutdown(); }
fit::result<std::unique_ptr<DeviceWatcherImpl>, zx_status_t> DeviceWatcherImpl::Create(
fuchsia::sys::LauncherHandle launcher) {
auto server = std::make_unique<DeviceWatcherImpl>();
ZX_ASSERT(server->launcher_.Bind(std::move(launcher), server->loop_.dispatcher()) == ZX_OK);
zx_status_t status = server->loop_.StartThread("DeviceWatcherImpl");
if (status != ZX_OK) {
FX_PLOGS(ERROR, status);
return fit::error(status);
}
return fit::ok(std::move(server));
}
fit::result<PersistentDeviceId, zx_status_t> DeviceWatcherImpl::AddDevice(
fuchsia::hardware::camera::DeviceHandle camera) {
FX_LOGS(DEBUG) << "AddDevice(...)";
fuchsia::hardware::camera::DeviceSyncPtr dev;
dev.Bind(std::move(camera));
fuchsia::camera2::hal::ControllerPtr ctrl;
ZX_ASSERT(dev->GetChannel2(ctrl.NewRequest(loop_.dispatcher())) == ZX_OK);
// Try to get the device info, waiting for either the info to be returned, or for an error.
zx::event event;
ZX_ASSERT(zx::event::create(0, &event) == ZX_OK);
constexpr auto kErrorSignal = ZX_USER_SIGNAL_0;
constexpr auto kInfoSignal = ZX_USER_SIGNAL_1;
zx_status_t status_return;
ctrl.set_error_handler([&](zx_status_t status) {
FX_PLOGS(WARNING, status);
status_return = status;
event.signal(0, kErrorSignal);
});
fuchsia::camera2::DeviceInfo info_return;
async::PostTask(loop_.dispatcher(), [&] {
ctrl->GetDeviceInfo([&](fuchsia::camera2::DeviceInfo info) {
info_return = std::move(info);
event.signal(0, kInfoSignal);
});
});
zx_signals_t signaled{};
zx_status_t status = event.wait_one(kErrorSignal | kInfoSignal, zx::time::infinite(), &signaled);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status);
return fit::error(status);
}
if (signaled & kErrorSignal) {
FX_PLOGS(WARNING, status_return);
return fit::error(status_return);
}
ZX_ASSERT(signaled & kInfoSignal);
if (!info_return.has_vendor_id() || !info_return.has_product_id()) {
FX_LOGS(INFO) << "Controller missing vendor or product ID.";
return fit::error(ZX_ERR_NOT_SUPPORTED);
}
// TODO(fxbug.dev/43565): This generates the same ID for multiple instances of the same device. It
// should be made unique by incorporating a truly unique value such as the bus ID.
constexpr uint32_t kVendorShift = 16;
PersistentDeviceId persistent_id =
(static_cast<uint64_t>(info_return.vendor_id()) << kVendorShift) | info_return.product_id();
// Close the controller handle and launch the instance.
ctrl = nullptr;
auto result = DeviceInstance::Create(launcher_, dev.Unbind(), [this, persistent_id]() {
async::PostTask(loop_.dispatcher(), [this, persistent_id]() { devices_.erase(persistent_id); });
});
if (result.is_error()) {
FX_PLOGS(ERROR, result.error()) << "Failed to launch device instance.";
return fit::error(result.error());
}
auto instance = result.take_value();
async::PostTask(
loop_.dispatcher(), [this, persistent_id, instance = std::move(instance)]() mutable {
devices_[persistent_id] = {.id = device_id_next_, .instance = std::move(instance)};
FX_LOGS(DEBUG) << "Added device " << persistent_id << " as device ID " << device_id_next_;
++device_id_next_;
});
return fit::ok(persistent_id);
}
void DeviceWatcherImpl::UpdateClients() {
if (!initial_update_received_) {
initial_update_received_ = true;
while (!requests_.empty()) {
OnNewRequest(std::move(requests_.front()));
requests_.pop();
}
}
async::PostTask(loop_.dispatcher(), [this]() {
for (auto& client : clients_) {
client.second->UpdateDevices(devices_);
}
});
}
fidl::InterfaceRequestHandler<fuchsia::camera3::DeviceWatcher> DeviceWatcherImpl::GetHandler() {
return fit::bind_member(this, &DeviceWatcherImpl::OnNewRequest);
}
void DeviceWatcherImpl::OnNewRequest(
fidl::InterfaceRequest<fuchsia::camera3::DeviceWatcher> request) {
if (!initial_update_received_) {
requests_.push(std::move(request));
return;
}
auto result = Client::Create(*this, client_id_next_, std::move(request), loop_.dispatcher());
if (result.is_error()) {
FX_PLOGS(ERROR, result.error());
return;
}
auto client = result.take_value();
async::PostTask(loop_.dispatcher(),
[this, client = std::move(client), id = client_id_next_]() mutable {
clients_[id] = std::move(client);
});
FX_LOGS(DEBUG) << "DeviceWatcher client " << client_id_next_ << " connected.";
++client_id_next_;
}
DeviceWatcherImpl::Client::Client(DeviceWatcherImpl& watcher) : watcher_(watcher), binding_(this) {}
fit::result<std::unique_ptr<DeviceWatcherImpl::Client>, zx_status_t>
DeviceWatcherImpl::Client::Create(DeviceWatcherImpl& watcher, ClientId id,
fidl::InterfaceRequest<fuchsia::camera3::DeviceWatcher> request,
async_dispatcher_t* dispatcher) {
auto client = std::make_unique<DeviceWatcherImpl::Client>(watcher);
client->id_ = id;
client->UpdateDevices(watcher.devices_);
zx_status_t status = client->binding_.Bind(request.TakeChannel(), dispatcher);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status);
return fit::error(status);
}
client->binding_.set_error_handler([client = client.get()](zx_status_t status) {
FX_PLOGS(DEBUG, status) << "DeviceWatcher client " << client->id_ << " disconnected.";
status = async::PostTask(client->watcher_.loop_.dispatcher(),
[client] { client->watcher_.clients_.erase(client->id_); });
ZX_ASSERT(status == ZX_OK);
});
return fit::ok(std::move(client));
}
void DeviceWatcherImpl::Client::UpdateDevices(const DevicesMap& devices) {
last_known_ids_.clear();
for (const auto& device : devices) {
last_known_ids_.insert(device.second.id);
}
CheckDevicesChanged();
}
DeviceWatcherImpl::Client::operator bool() { return binding_.is_bound(); }
// Constructs the set of events that should be returned to the client, or null if the response
// should not be sent.
static std::optional<std::vector<fuchsia::camera3::WatchDevicesEvent>> BuildEvents(
const std::set<TransientDeviceId>& last_known,
const std::optional<std::set<TransientDeviceId>>& last_sent) {
std::vector<fuchsia::camera3::WatchDevicesEvent> events;
// If never sent, just populate with Added events for the known IDs.
if (!last_sent.has_value()) {
for (auto id : last_known) {
events.push_back(fuchsia::camera3::WatchDevicesEvent::WithAdded(std::move(id)));
}
return events;
}
// Otherwise, build a full event list.
std::set<TransientDeviceId> existing;
std::set<TransientDeviceId> added;
std::set<TransientDeviceId> removed;
// Exisitng = Known && Sent
std::set_intersection(last_known.begin(), last_known.end(), last_sent.value().begin(),
last_sent.value().end(), std::inserter(existing, existing.begin()));
// Added = Known - Sent
std::set_difference(last_known.begin(), last_known.end(), last_sent.value().begin(),
last_sent.value().end(), std::inserter(added, added.begin()));
// Removed = Sent - Known
std::set_difference(last_sent.value().begin(), last_sent.value().end(), last_known.begin(),
last_known.end(), std::inserter(removed, removed.begin()));
if (added.empty() && removed.empty()) {
return std::nullopt;
}
for (auto id : existing) {
events.push_back(fuchsia::camera3::WatchDevicesEvent::WithExisting(std::move(id)));
}
for (auto id : added) {
events.push_back(fuchsia::camera3::WatchDevicesEvent::WithAdded(std::move(id)));
}
for (auto id : removed) {
events.push_back(fuchsia::camera3::WatchDevicesEvent::WithRemoved(std::move(id)));
}
return events;
};
void DeviceWatcherImpl::Client::CheckDevicesChanged() {
if (!callback_) {
return;
}
auto events = BuildEvents(last_known_ids_, last_sent_ids_);
if (!events.has_value()) {
return;
}
callback_(std::move(events.value()));
callback_ = nullptr;
last_sent_ids_ = last_known_ids_;
}
void DeviceWatcherImpl::Client::WatchDevices(WatchDevicesCallback callback) {
if (callback_) {
FX_LOGS(INFO) << "Client called WatchDevices while a previous call was still pending.";
binding_.Close(ZX_ERR_BAD_STATE);
return;
}
callback_ = std::move(callback);
CheckDevicesChanged();
}
void DeviceWatcherImpl::Client::ConnectToDevice(
TransientDeviceId id, fidl::InterfaceRequest<fuchsia::camera3::Device> request) {
if (!last_sent_ids_.has_value()) {
FX_LOGS(INFO) << "Clients must watch for devices prior to attempting a connection.";
request.Close(ZX_ERR_BAD_STATE);
return;
}
const auto& devices = watcher_.devices_;
auto device = std::find_if(devices.begin(), devices.end(),
[=](const auto& it) { return it.second.id == id; });
if (device == devices.end()) {
request.Close(ZX_ERR_NOT_FOUND);
return;
}
device->second.instance->OnCameraRequested(std::move(request));
}