blob: a74fda0b65debc9425e406f0cdd48db6d3738011 [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 <cstdio>
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/ui/policy/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <gtest/gtest.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/component/cpp/startup_context.h>
#include <lib/fdio/spawn.h>
#include <lib/fit/function.h>
#include <lib/gtest/real_loop_fixture.h>
#include <lib/ui/base_view/cpp/base_view.h>
#include <lib/ui/input/cpp/formatting.h>
#include <lib/ui/scenic/cpp/session.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <zircon/status.h>
#include <memory>
#include <string>
#include <vector>
#include "src/lib/fxl/command_line.h"
#include "src/lib/fxl/log_settings_command_line.h"
#include "src/lib/fxl/logging.h"
namespace {
using fuchsia::ui::input::InputEvent;
using fuchsia::ui::input::MediaButtonsEvent;
using Phase = fuchsia::ui::input::PointerEventPhase;
// Shared context for all tests in this process.
// Set it up once, never delete it.
component::StartupContext* g_context = nullptr;
// Max timeout in failure cases.
// Set this as low as you can that still works across all test platforms.
constexpr zx::duration kTimeout = zx::min(5);
// This implements the MediaButtonsListener class. Its purpose is to attach
// to the presentation and test that MediaButton Events are actually sent
// out to the Listeners.
class ButtonsListenerImpl : public fuchsia::ui::policy::MediaButtonsListener {
public:
ButtonsListenerImpl(
fidl::InterfaceRequest<fuchsia::ui::policy::MediaButtonsListener>
listener_request)
: listener_binding_(this, std::move(listener_request)) {}
~ButtonsListenerImpl() = default;
int EventsSeen() { return events_seen_; }
void SetOnTerminateCallback(
fit::function<void(const std::vector<MediaButtonsEvent>&)> on_terminate,
int num_events_to_terminate) {
on_terminate_ = std::move(on_terminate);
num_events_to_terminate_ = num_events_to_terminate;
}
private:
// |MediaButtonsListener|
void OnMediaButtonsEvent(
fuchsia::ui::input::MediaButtonsEvent event) override {
// Store inputs for checking later.
observed_.push_back(std::move(event));
events_seen_++;
if (events_seen_ >= num_events_to_terminate_) {
on_terminate_(observed_);
}
}
int events_seen_ = 0;
int num_events_to_terminate_ = 0;
fidl::Binding<fuchsia::ui::policy::MediaButtonsListener> listener_binding_;
fit::function<void(const std::vector<MediaButtonsEvent>&)> on_terminate_;
std::vector<MediaButtonsEvent> observed_;
};
// A very small Scenic client. Puts up a fuchsia-colored rectangle.
class MinimalClientView : public scenic::BaseView {
public:
MinimalClientView(scenic::ViewContext context, async_dispatcher_t* dispatcher)
: scenic::BaseView(std::move(context), "MinimalClientView"),
dispatcher_(dispatcher) {
FXL_CHECK(dispatcher_);
}
void CreateScene(uint32_t width_in_px, uint32_t height_in_px) {
float width = static_cast<float>(width_in_px);
float height = static_cast<float>(height_in_px);
scenic::ShapeNode background(session());
scenic::Material material(session());
material.SetColor(255, 0, 255, 255); // Fuchsia
background.SetMaterial(material);
scenic::Rectangle rectangle(session(), width, height);
background.SetShape(rectangle);
background.SetTranslation(width / 2, height / 2, -10.f);
root_node().AddChild(background);
}
void Update(uint64_t present_time) {
session()->Present(
present_time, [this](fuchsia::images::PresentationInfo info) {
Update(info.presentation_time + info.presentation_interval);
});
}
private:
// |scenic::SessionListener|
void OnScenicError(std::string error) override { FXL_LOG(FATAL) << error; }
async_dispatcher_t* dispatcher_ = nullptr;
};
class MediaButtonsListenerTest : public gtest::RealLoopFixture {
protected:
// Mildly complex ctor, but we don't throw and we don't call virtual methods.
MediaButtonsListenerTest() {
// This fixture constructor may run multiple times, but we want the context
// to be set up just once per process.
if (g_context == nullptr) {
g_context = component::StartupContext::CreateFromStartupInfo().release();
}
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
// Connect to Scenic, create a View.
scenic_ =
g_context->ConnectToEnvironmentService<fuchsia::ui::scenic::Scenic>();
scenic_.set_error_handler([](zx_status_t status) {
FXL_LOG(FATAL) << "Lost connection to Scenic: "
<< zx_status_get_string(status);
});
scenic::ViewContext view_context = {
.session_and_listener_request =
scenic::CreateScenicSessionPtrAndListenerRequest(scenic_.get()),
.view_token = std::move(view_token),
.incoming_services = nullptr,
.outgoing_services = nullptr,
.startup_context = g_context,
};
view_ = std::make_unique<MinimalClientView>(std::move(view_context),
dispatcher());
// Connect to RootPresenter, create a ViewHolder.
root_presenter_ =
g_context
->ConnectToEnvironmentService<fuchsia::ui::policy::Presenter>();
root_presenter_.set_error_handler([](zx_status_t status) {
FXL_LOG(FATAL) << "Lost connection to RootPresenter: "
<< zx_status_get_string(status);
});
fidl::InterfaceHandle<fuchsia::ui::policy::Presentation> presentation;
root_presenter_->PresentView(std::move(view_holder_token),
presentation.NewRequest());
// Connect to the MediaButtons listener.
fidl::InterfacePtr<fuchsia::ui::policy::Presentation> presentation_ptr;
fidl::InterfaceHandle<fuchsia::ui::policy::MediaButtonsListener>
listener_handle;
button_listener_impl_ =
std::make_unique<ButtonsListenerImpl>(listener_handle.NewRequest());
presentation_ptr = presentation.Bind();
presentation_ptr->RegisterMediaButtonsListener(std::move(listener_handle));
// When display is available, create content and drive input to touchscreen.
scenic_->GetDisplayInfo([this](fuchsia::ui::gfx::DisplayInfo display_info) {
display_width_ = display_info.width_in_px;
display_height_ = display_info.height_in_px;
FXL_CHECK(display_width_ > 0 && display_height_ > 0)
<< "Display size unsuitable for this test: (" << display_width_
<< ", " << display_height_ << ").";
view_->CreateScene(display_width_, display_height_);
view_->Update(zx_clock_get_monotonic());
inject_input_(); // Display up, content ready. Send in input.
test_was_run_ = true; // Actually did work for this test.
});
// Post a "just in case" quit task, if the test hangs.
async::PostDelayedTask(
dispatcher(),
[] {
FXL_LOG(FATAL)
<< "\n\n>> Test did not complete in time, terminating. <<\n\n";
},
kTimeout);
}
~MediaButtonsListenerTest() override {
FXL_CHECK(test_was_run_) << "Oops, didn't actually do anything.";
}
void InjectInput(std::vector<const char*> args) {
// Start with process name, end with nullptr.
args.insert(args.begin(), "input");
args.push_back(nullptr);
// Start the /bin/input process.
zx_handle_t proc;
zx_status_t status = fdio_spawn(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL,
"/bin/input", args.data(), &proc);
FXL_CHECK(status == ZX_OK)
<< "fdio_spawn: " << zx_status_get_string(status);
// Wait for termination.
status = zx_object_wait_one(proc, ZX_PROCESS_TERMINATED,
(zx::clock::get_monotonic() + kTimeout).get(),
nullptr);
FXL_CHECK(status == ZX_OK)
<< "zx_object_wait_one: " << zx_status_get_string(status);
// Check termination status.
zx_info_process_t info;
status = zx_object_get_info(proc, ZX_INFO_PROCESS, &info, sizeof(info),
nullptr, nullptr);
FXL_CHECK(status == ZX_OK)
<< "zx_object_get_info: " << zx_status_get_string(status);
FXL_CHECK(info.return_code == 0)
<< "info.return_code: " << info.return_code;
}
void SetInjectInputCallback(fit::function<void()> inject_input) {
inject_input_ = std::move(inject_input);
}
void SetOnTerminateCallback(
fit::function<void(const std::vector<MediaButtonsEvent>&)> on_terminate,
int num_events_to_terminate) {
button_listener_impl_->SetOnTerminateCallback(std::move(on_terminate),
num_events_to_terminate);
}
std::unique_ptr<ButtonsListenerImpl> button_listener_impl_;
fuchsia::ui::policy::PresenterPtr root_presenter_;
fuchsia::ui::scenic::ScenicPtr scenic_;
std::unique_ptr<MinimalClientView> view_;
uint32_t display_width_ = 0;
uint32_t display_height_ = 0;
fit::function<void()> inject_input_;
bool test_was_run_ = false;
};
TEST_F(MediaButtonsListenerTest, MediaButtons) {
// Set up inputs. Fires when display and content are available.
SetInjectInputCallback([this] {
InjectInput({"media_button", "1", "1", "1", "1", nullptr});
});
// Set up expectations. Terminate when we see 1 message.
SetOnTerminateCallback(
[this](const std::vector<MediaButtonsEvent>& observed) {
EXPECT_EQ(observed.size(), 1U);
QuitLoop();
// Today, we can't quietly break the View/ViewHolder connection.
},
1);
RunLoop(); // Go!
}
} // namespace
// NOTE: We link in FXL's gtest_main to enable proper logging.