blob: fd26b3e3085411aa1ef9b9f782e2597f44753250 [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_data/data_provider.h"
#include <fuchsia/feedback/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.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_data/annotations/types.h"
#include "src/developer/feedback/feedback_data/annotations/utils.h"
#include "src/developer/feedback/feedback_data/attachments/aliases.h"
#include "src/developer/feedback/feedback_data/attachments/screenshot_ptr.h"
#include "src/developer/feedback/feedback_data/attachments/utils.h"
#include "src/developer/feedback/feedback_data/image_conversion.h"
#include "src/lib/fsl/vmo/sized_vmo.h"
#include "src/lib/fsl/vmo/strings.h"
#include "src/lib/fxl/strings/string_printf.h"
#include "src/lib/syslog/cpp/logger.h"
namespace feedback {
namespace {
using fuchsia::feedback::Data;
using fuchsia::feedback::ImageEncoding;
using fuchsia::feedback::Screenshot;
// Timeout for requesting the screenshot from Scenic.
const zx::duration kScreenshotTimeout = zx::sec(10);
} // namespace
DataProvider::DataProvider(async_dispatcher_t* dispatcher,
std::shared_ptr<sys::ServiceDirectory> services, cobalt::Logger* cobalt,
Datastore* datastore)
: dispatcher_(dispatcher),
services_(services),
cobalt_(cobalt),
datastore_(datastore),
executor_(dispatcher_) {}
namespace {
std::vector<fuchsia::feedback::Attachment> ToAttachmentVector(const Attachments& attachments) {
std::vector<fuchsia::feedback::Attachment> vec;
for (const auto& [key, value] : attachments) {
fsl::SizedVmo vmo;
if (!fsl::VmoFromString(value, &vmo)) {
FX_LOGS(ERROR) << fxl::StringPrintf("Failed to convert attachment %s to VMO", key.c_str());
continue;
}
fuchsia::feedback::Attachment attachment;
attachment.key = key;
attachment.value = std::move(vmo).ToTransport();
vec.push_back(std::move(attachment));
}
return vec;
}
} // namespace
void DataProvider::GetData(GetDataCallback callback) {
const uint64_t timer_id = cobalt_->StartTimer();
auto promise =
::fit::join_promises(datastore_->GetAnnotations(), datastore_->GetAttachments())
.and_then([](std::tuple<::fit::result<Annotations>, ::fit::result<Attachments>>&
annotations_and_attachments) {
Data data;
std::vector<fuchsia::feedback::Attachment> attachments;
auto& annotations_or_error = std::get<0>(annotations_and_attachments);
if (annotations_or_error.is_ok()) {
data.set_annotations(ToFeedbackAnnotationVector(annotations_or_error.take_value()));
} else {
FX_LOGS(WARNING) << "Failed to retrieve any annotations";
}
auto& attachments_or_error = std::get<1>(annotations_and_attachments);
if (attachments_or_error.is_ok()) {
attachments = ToAttachmentVector(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()) {
fuchsia::feedback::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),
timer_id](::fit::result<Data, zx_status_t>& result) {
if (result.is_error()) {
cobalt_->LogElapsedTime(cobalt::BugreportGenerationFlow::kFailure, timer_id);
} else {
cobalt_->LogElapsedTime(cobalt::BugreportGenerationFlow::kSuccess, timer_id);
}
callback(std::move(result));
});
executor_.schedule_task(std::move(promise));
}
void DataProvider::GetScreenshot(ImageEncoding encoding, GetScreenshotCallback callback) {
auto promise =
TakeScreenshot(
dispatcher_, services_,
fit::Timeout(kScreenshotTimeout,
[this] { cobalt_->LogOccurrence(cobalt::TimedOutData::kScreenshot); }))
.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([callback = std::move(callback)](::fit::result<Screenshot>& result) {
if (!result.is_ok()) {
callback(/*screenshot=*/nullptr);
} else {
callback(std::make_unique<Screenshot>(result.take_value()));
}
});
executor_.schedule_task(std::move(promise));
}
} // namespace feedback