blob: 6f3a1c2cbf31084ade73234fcb145efd701d898d [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/feedback_agent/data_provider.h"
#include <fuchsia/feedback/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <lib/async-loop/default.h>
#include <lib/fit/promise.h>
#include <lib/fit/result.h>
#include <lib/zx/time.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <memory>
#include "src/developer/feedback/feedback_agent/annotations.h"
#include "src/developer/feedback/feedback_agent/attachments.h"
#include "src/developer/feedback/feedback_agent/attachments/screenshot_ptr.h"
#include "src/developer/feedback/feedback_agent/config.h"
#include "src/developer/feedback/feedback_agent/image_conversion.h"
#include "src/lib/files/file.h"
#include "src/lib/syslog/cpp/logger.h"
namespace feedback {
namespace {
using fuchsia::feedback::Annotation;
using fuchsia::feedback::Attachment;
using fuchsia::feedback::Data;
using fuchsia::feedback::ImageEncoding;
using fuchsia::feedback::Screenshot;
const char kConfigPath[] = "/pkg/data/config.json";
// Timeout for a single asynchronous piece of data, e.g., syslog collection.
const zx::duration kDataTimeout = zx::sec(30);
// Timeout for requesting the screenshot from Scenic.
const zx::duration kScreenshotTimeout = zx::sec(10);
} // namespace
std::unique_ptr<DataProvider> DataProvider::TryCreate(
async_dispatcher_t* dispatcher, std::shared_ptr<sys::ServiceDirectory> services,
std::function<void()> after_timeout, zx::duration timeout) {
Config config;
if (const zx_status_t status = ParseConfig(kConfigPath, &config); status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to read config file at " << kConfigPath;
FX_LOGS(FATAL) << "Failed to set up data provider";
return nullptr;
}
return std::make_unique<DataProvider>(dispatcher, std::move(services), config, after_timeout,
timeout);
}
DataProvider::DataProvider(async_dispatcher_t* dispatcher,
std::shared_ptr<sys::ServiceDirectory> services, const Config& config,
std::function<void()> after_timeout, zx::duration timeout)
: dispatcher_(dispatcher),
services_(services),
config_(config),
after_timeout_(dispatcher, after_timeout, timeout),
executor_(dispatcher),
cobalt_(std::make_unique<Cobalt>(dispatcher_, services_)),
inspect_loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
inspect_executor_(inspect_loop_.dispatcher()) {
if (const zx_status_t status = inspect_loop_.StartThread("inspect-thread"); status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Unable to start new thread for Inspect data collection";
}
}
void DataProvider::GetData(GetDataCallback callback) {
after_timeout_.Acquire();
auto annotations =
fit::join_promise_vector(GetAnnotations(dispatcher_, services_, config_.annotation_allowlist,
kDataTimeout, cobalt_))
.and_then([](std::vector<fit::result<std::vector<Annotation>>>& annotation_promises)
-> fit::result<std::vector<Annotation>> {
std::vector<Annotation> ok_annotations;
for (auto& promise : annotation_promises) {
if (promise.is_ok()) {
auto annotations = promise.take_value();
for (const auto& annotation : annotations) {
ok_annotations.push_back(std::move(annotation));
}
}
}
if (ok_annotations.empty()) {
return fit::error();
}
return fit::ok(ok_annotations);
});
auto attachments =
fit::join_promise_vector(GetAttachments(dispatcher_, services_, config_.attachment_allowlist,
kDataTimeout, cobalt_, &inspect_executor_))
.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(
[](std::tuple<fit::result<std::vector<Annotation>>,
fit::result<std::vector<Attachment>>>& annotations_and_attachments) {
Data data;
auto& annotations_or_error = std::get<0>(annotations_and_attachments);
if (annotations_or_error.is_ok()) {
data.set_annotations(annotations_or_error.take_value());
} else {
FX_LOGS(WARNING) << "Failed to retrieve any annotations";
}
auto& attachments_or_error = std::get<1>(annotations_and_attachments);
std::vector<Attachment> attachments;
if (attachments_or_error.is_ok()) {
attachments = attachments_or_error.take_value();
} else {
FX_LOGS(WARNING) << "Failed to retrieve any attachments";
}
// We also add the annotations as a single extra attachment.
// This is useful for clients that surface the annotations differentily in the UI
// but still want all the annotations to be easily downloadable in one file.
if (data.has_annotations()) {
AddAnnotationsAsExtraAttachment(data.annotations(), &attachments);
}
// We bundle the attachments into a single attachment.
// This is useful for most clients that want to pass around a single bundle.
if (!attachments.empty()) {
Attachment bundle;
if (BundleAttachments(attachments, &bundle)) {
data.set_attachment_bundle(std::move(bundle));
}
}
return fit::ok(std::move(data));
})
.or_else([]() { return fit::error(ZX_ERR_INTERNAL); })
.then([this, callback = std::move(callback)](fit::result<Data, zx_status_t>& result) {
callback(std::move(result));
after_timeout_.Release();
});
executor_.schedule_task(std::move(promise));
}
void DataProvider::GetScreenshot(ImageEncoding encoding, GetScreenshotCallback callback) {
after_timeout_.Acquire();
auto promise = TakeScreenshot(dispatcher_, services_, kScreenshotTimeout, cobalt_)
.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, callback = std::move(callback)](fit::result<Screenshot>& result) {
if (!result.is_ok()) {
callback(/*screenshot=*/nullptr);
} else {
callback(std::make_unique<Screenshot>(result.take_value()));
}
after_timeout_.Release();
});
executor_.schedule_task(std::move(promise));
}
} // namespace feedback