blob: 84077fccd648c68a7786ddaee6dd81cd5705b673 [file] [log] [blame]
// Copyright 2019 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/developer/feedback_agent/data_provider.h"
#include <fuchsia/feedback/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <lib/fit/promise.h>
#include <lib/syslog/cpp/logger.h>
#include <lib/zx/time.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include "src/developer/feedback_agent/annotations.h"
#include "src/developer/feedback_agent/attachments.h"
#include "src/developer/feedback_agent/config.h"
#include "src/developer/feedback_agent/image_conversion.h"
namespace fuchsia {
namespace feedback {
namespace {
const char kDefaultConfigPath[] = "/pkg/data/default_config.json";
// Timeout for a single asynchronous attachment, e.g., syslog collection.
const zx::duration kAttachmentTimeout = zx::sec(10);
// Timeout for requesting the screenshot from Scenic.
const zx::duration kScreenshotTimeout = zx::sec(10);
} // namespace
std::unique_ptr<DataProviderImpl> DataProviderImpl::TryCreate(
async_dispatcher_t* dispatcher, std::shared_ptr<::sys::ServiceDirectory> services) {
Config config;
const zx_status_t parse_status = ParseConfig(kDefaultConfigPath, &config);
if (parse_status != ZX_OK) {
FX_PLOGS(ERROR, parse_status) << "Failed to read default config file at " << kDefaultConfigPath;
FX_LOGS(FATAL) << "Failed to set up data provider";
return nullptr;
}
return std::make_unique<DataProviderImpl>(dispatcher, std::move(services), config);
}
DataProviderImpl::DataProviderImpl(async_dispatcher_t* dispatcher,
std::shared_ptr<::sys::ServiceDirectory> services,
const Config& config)
: dispatcher_(dispatcher), executor_(dispatcher), services_(services), config_(config) {}
void DataProviderImpl::GetData(GetDataCallback callback) {
auto annotations = fit::join_promise_vector(GetAnnotations(config_.annotation_allowlist))
.and_then([](std::vector<fit::result<Annotation>>& annotations)
-> fit::result<std::vector<Annotation>> {
std::vector<Annotation> ok_annotations;
for (auto& annotation : annotations) {
if (annotation.is_ok()) {
ok_annotations.emplace_back(annotation.take_value());
}
}
if (ok_annotations.empty()) {
return fit::error();
}
return fit::ok(ok_annotations);
});
auto attachments =
fit::join_promise_vector(
GetAttachments(dispatcher_, services_, config_.attachment_allowlist, kAttachmentTimeout))
.and_then([](std::vector<fit::result<Attachment>>& attachments)
-> fit::result<std::vector<Attachment>> {
std::vector<Attachment> ok_attachments;
for (auto& attachment : attachments) {
if (attachment.is_ok()) {
ok_attachments.emplace_back(attachment.take_value());
}
}
if (ok_attachments.empty()) {
return fit::error();
}
return fit::ok(std::move(ok_attachments));
});
auto promise =
fit::join_promises(std::move(annotations), std::move(attachments))
.and_then(
[callback = std::move(callback)](
std::tuple<fit::result<std::vector<Annotation>>,
fit::result<std::vector<Attachment>>>& annotations_and_attachments) {
DataProvider_GetData_Response response;
auto& annotations = std::get<0>(annotations_and_attachments);
if (annotations.is_ok()) {
response.data.set_annotations(annotations.take_value());
} else {
FX_LOGS(WARNING) << "Failed to retrieve any annotations";
}
auto& attachments = std::get<1>(annotations_and_attachments);
if (attachments.is_ok()) {
response.data.set_attachments(attachments.take_value());
} else {
FX_LOGS(WARNING) << "Failed to retrieve any attachments";
}
DataProvider_GetData_Result result;
result.set_response(std::move(response));
callback(std::move(result));
})
.or_else([callback = std::move(callback)] {
DataProvider_GetData_Result result;
result.set_err(ZX_ERR_INTERNAL);
callback(std::move(result));
});
executor_.schedule_task(std::move(promise));
}
void DataProviderImpl::GetScreenshot(ImageEncoding encoding, GetScreenshotCallback callback) {
const uint64_t id = next_scenic_id_++;
scenics_[id] = std::make_unique<Scenic>(dispatcher_, services_);
auto promise =
scenics_[id]
->TakeScreenshot(kScreenshotTimeout)
.and_then([encoding](fuchsia::ui::scenic::ScreenshotData& raw_screenshot)
-> fit::result<Screenshot> {
Screenshot screenshot;
screenshot.dimensions_in_px.height = raw_screenshot.info.height;
screenshot.dimensions_in_px.width = raw_screenshot.info.width;
switch (encoding) {
case ImageEncoding::PNG:
if (!RawToPng(raw_screenshot.data, raw_screenshot.info.height,
raw_screenshot.info.width, raw_screenshot.info.stride,
raw_screenshot.info.pixel_format, &screenshot.image)) {
FX_LOGS(ERROR) << "Failed to convert raw screenshot to PNG";
return fit::error();
}
break;
}
return fit::ok(std::move(screenshot));
})
.then([this, id, callback = std::move(callback)](fit::result<Screenshot>& result) {
if (scenics_.erase(id) == 0) {
FX_LOGS(ERROR) << "No fuchsia.ui.scenic.Scenic connection to close with id " << id;
}
if (!result.is_ok()) {
callback(/*screenshot=*/nullptr);
} else {
callback(std::make_unique<Screenshot>(result.take_value()));
}
});
executor_.schedule_task(std::move(promise));
}
} // namespace feedback
} // namespace fuchsia