blob: cbe9afcd81e1fc3ceb3fe83b349918a00c6b22db [file] [log] [blame]
// Copyright 2021 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 <fuchsia/accessibility/semantics/cpp/fidl.h>
#include <fuchsia/buildinfo/cpp/fidl.h>
#include <fuchsia/cobalt/cpp/fidl.h>
#include <fuchsia/component/cpp/fidl.h>
#include <fuchsia/fonts/cpp/fidl.h>
#include <fuchsia/input/virtualkeyboard/cpp/fidl.h>
#include <fuchsia/intl/cpp/fidl.h>
#include <fuchsia/io/cpp/fidl.h>
#include <fuchsia/memorypressure/cpp/fidl.h>
#include <fuchsia/net/interfaces/cpp/fidl.h>
#include <fuchsia/netstack/cpp/fidl.h>
#include <fuchsia/posix/socket/cpp/fidl.h>
#include <fuchsia/scheduler/cpp/fidl.h>
#include <fuchsia/sysmem/cpp/fidl.h>
#include <fuchsia/tracing/provider/cpp/fidl.h>
#include <fuchsia/ui/accessibility/view/cpp/fidl.h>
#include <fuchsia/ui/app/cpp/fidl.h>
#include <fuchsia/ui/focus/cpp/fidl.h>
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/ui/observation/test/cpp/fidl.h>
#include <fuchsia/ui/pointerinjector/cpp/fidl.h>
#include <fuchsia/ui/policy/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <fuchsia/vulkan/loader/cpp/fidl.h>
#include <fuchsia/web/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/sys/component/cpp/testing/realm_builder.h>
#include <lib/sys/component/cpp/testing/realm_builder_types.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/ui/scenic/cpp/resources.h>
#include <lib/ui/scenic/cpp/session.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <lib/zx/clock.h>
#include <lib/zx/time.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <zircon/utc.h>
#include <iostream>
#include <type_traits>
#include <gtest/gtest.h>
#include <src/lib/fostr/fidl/fuchsia/ui/gfx/formatting.h>
#include <test/virtualkeyboard/cpp/fidl.h>
#include "src/lib/testing/loop_fixture/real_loop_fixture.h"
// This test exercises the virtual keyboard visibility interactions between Chromium and Root
// Presenter. It is a multi-component test, and carefully avoids sleeping or polling for component
// coordination.
// - It runs real Root Presenter and Scenic components.
// - It uses a fake display controller; the physical device is unused.
//
// Components involved
// - This test program
// - Root Presenter
// - Scenic
// - WebEngine (built from Chromium)
//
// Setup sequence
// - The test sets up a view hierarchy with two views:
// - Top level scene, owned by Root Presenter.
// - Bottom view, owned by Chromium.
namespace {
using test::virtualkeyboard::InputPositionListener;
using ScenicEvent = fuchsia::ui::scenic::Event;
using GfxEvent = fuchsia::ui::gfx::Event;
// Types imported for the realm_builder library.
using component_testing::ChildRef;
using component_testing::Directory;
using component_testing::LocalComponent;
using component_testing::LocalComponentHandles;
using component_testing::ParentRef;
using component_testing::Protocol;
using component_testing::RealmRoot;
using component_testing::Route;
using RealmBuilder = component_testing::RealmBuilder;
// Alias for Component child name as provided to Realm Builder.
using ChildName = std::string;
// Alias for Component Legacy URL as provided to Realm Builder.
using LegacyUrl = std::string;
// 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);
constexpr auto kRootPresenter = "root_presenter";
constexpr auto kScenicTestRealm = "scenic-test-realm";
constexpr auto kResponseListener = "response_listener";
enum class TapLocation { kTopLeft, kTopRight };
// The type used to measure UTC time. The integer value here does not matter so
// long as it differs from the ZX_CLOCK_MONOTONIC=0 defined by Zircon.
using time_utc = zx::basic_time<1>;
void AddBaseComponents(RealmBuilder* realm_builder) {
realm_builder->AddChild(kRootPresenter, "#meta/root_presenter.cm");
realm_builder->AddChild(kScenicTestRealm, "#meta/scenic_only.cm");
}
void AddBaseRoutes(RealmBuilder* realm_builder) {
// Capabilities routed from test_manager to components in realm.
realm_builder->AddRoute(
Route{.capabilities = {Protocol{fuchsia::logger::LogSink::Name_},
Protocol{fuchsia::vulkan::loader::Loader::Name_},
Protocol{fuchsia::scheduler::ProfileProvider::Name_},
Protocol{fuchsia::sysmem::Allocator::Name_},
Protocol{fuchsia::tracing::provider::Registry::Name_}},
.source = ParentRef(),
.targets = {ChildRef{kScenicTestRealm}}});
realm_builder->AddRoute(
Route{.capabilities = {Protocol{fuchsia::tracing::provider::Registry::Name_},
Protocol{fuchsia::logger::LogSink::Name_}},
.source = ParentRef(),
.targets = {ChildRef{kRootPresenter}}});
// Capabilities routed between siblings in realm.
realm_builder->AddRoute(
Route{.capabilities = {Protocol{fuchsia::ui::scenic::Scenic::Name_},
Protocol{fuchsia::ui::pointerinjector::Registry::Name_},
Protocol{fuchsia::ui::focus::FocusChainListenerRegistry::Name_}},
.source = ChildRef{kScenicTestRealm},
.targets = {ChildRef{kRootPresenter}}});
// Capabilities routed up to test driver (this component).
realm_builder->AddRoute(
Route{.capabilities = {Protocol{fuchsia::input::virtualkeyboard::Manager::Name_},
Protocol{fuchsia::input::virtualkeyboard::ControllerCreator::Name_},
Protocol{fuchsia::ui::input::InputDeviceRegistry::Name_},
Protocol{fuchsia::ui::accessibility::view::Registry::Name_},
Protocol{fuchsia::ui::policy::Presenter::Name_}},
.source = ChildRef{kRootPresenter},
.targets = {ParentRef()}});
realm_builder->AddRoute(
Route{.capabilities = {Protocol{fuchsia::ui::scenic::Scenic::Name_},
Protocol{fuchsia::ui::observation::test::Registry::Name_}},
.source = ChildRef{kScenicTestRealm},
.targets = {ParentRef()}});
}
// Combines all vectors in `vecs` into one.
template <typename T>
std::vector<T> merge(std::initializer_list<std::vector<T>> vecs) {
std::vector<T> result;
for (auto v : vecs) {
result.insert(result.end(), v.begin(), v.end());
}
return result;
}
bool CheckViewExistsInSnapshot(const fuchsia::ui::observation::geometry::ViewTreeSnapshot& snapshot,
zx_koid_t view_ref_koid) {
if (!snapshot.has_views()) {
return false;
}
auto snapshot_count = std::count_if(
snapshot.views().begin(), snapshot.views().end(),
[view_ref_koid](const auto& view) { return view.view_ref_koid() == view_ref_koid; });
return snapshot_count > 0;
}
zx_koid_t ExtractKoid(const zx::object_base& object) {
zx_info_handle_basic_t info{};
if (object.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr) != ZX_OK) {
return ZX_KOID_INVALID; // no info
}
return info.koid;
}
zx_koid_t ExtractKoid(const fuchsia::ui::views::ViewRef& view_ref) {
return ExtractKoid(view_ref.reference);
}
// This component implements the interface for a RealmBuilder
// LocalComponent and the test.virtualkeyboard.InputPositionListener
// protocol.
class InputPositionListenerServer : public InputPositionListener, public LocalComponent {
public:
explicit InputPositionListenerServer(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {}
// |test::virtualkeyboard::InputPositionListener|
void Notify(test::virtualkeyboard::BoundingBox bounding_box) override {
input_position_ = std::move(bounding_box);
}
// |LocalComponent::Start|
void Start(std::unique_ptr<LocalComponentHandles> local_handles) override {
// When this component starts, add a binding to the test.touch.ResponseListener
// protocol to this component's outgoing directory.
FX_CHECK(local_handles->outgoing()->AddPublicService(
fidl::InterfaceRequestHandler<InputPositionListener>([this](auto request) {
bindings_.AddBinding(this, std::move(request), dispatcher_);
})) == ZX_OK);
local_handle_ = std::move(local_handles);
}
const std::optional<test::virtualkeyboard::BoundingBox>& input_position() const {
return input_position_;
}
private:
async_dispatcher_t* dispatcher_ = nullptr;
std::unique_ptr<LocalComponentHandles> local_handle_;
std::optional<test::virtualkeyboard::BoundingBox> input_position_;
fidl::BindingSet<InputPositionListener> bindings_;
};
class VirtualKeyboardBase : public gtest::RealLoopFixture {
protected:
VirtualKeyboardBase()
: realm_builder_(std::make_unique<RealmBuilder>(RealmBuilder::Create())), realm_() {}
~VirtualKeyboardBase() override {
FX_CHECK(injection_count_ > 0) << "injection expected but didn't happen.";
}
void SetUp() override {
// Post a "just in case" quit task, if the test hangs.
async::PostDelayedTask(
dispatcher(),
[] { FX_LOGS(FATAL) << "\n\n>> Test did not complete in time, terminating. <<\n\n"; },
kTimeout);
BuildRealm(this->GetTestComponents(), this->GetTestRoutes(), this->GetTestV2Components());
// Get the display dimensions
scenic_ = realm()->Connect<fuchsia::ui::scenic::Scenic>();
scenic_->GetDisplayInfo([this](fuchsia::ui::gfx::DisplayInfo display_info) {
display_width_ = display_info.width_in_px;
display_height_ = display_info.height_in_px;
FX_LOGS(INFO) << "Got display_width = " << *display_width_
<< " and display_height = " << *display_height_;
});
RunLoopUntil([this] { return display_width_.has_value() && display_height_.has_value(); });
}
// Subclass should implement this method to add components to the test realm
// next to the base ones added.
virtual std::vector<std::pair<ChildName, LegacyUrl>> GetTestComponents() { return {}; }
// Subclass should implement this method to add capability routes to the test
// realm next to the base ones added.
virtual std::vector<Route> GetTestRoutes() { return {}; }
// Subclass should implement this method to add components to the test realm
// next to the base ones added.
virtual std::vector<std::pair<ChildName, std::string>> GetTestV2Components() { return {}; }
// This method does NOT block; it only logs when the client view has rendered.
void WatchClientRenderStatus(zx_koid_t client_view_ref_koid) {
geometry_provider_->Watch([this, client_view_ref_koid](auto response) {
if (response.has_updates() && !response.updates().empty() &&
CheckViewExistsInSnapshot(response.updates().back(), client_view_ref_koid)) {
FX_LOGS(INFO) << "Client view has rendered";
} else {
WatchClientRenderStatus(client_view_ref_koid);
}
});
}
// Launches the test client by connecting to fuchsia.ui.app.ViewProvider protocol.
// This method should only be invoked if this protocol has been exposed from
// the root of the test realm.
void LaunchChromium() {
// Use |fuchsia.ui.observation.test.Registry| to register the view observer endpoint with
// scenic.
realm()->Connect<fuchsia::ui::observation::test::Registry>(observer_registry_ptr_.NewRequest());
observer_registry_ptr_->RegisterGlobalGeometryProvider(geometry_provider_.NewRequest());
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto root_presenter = realm()->Connect<fuchsia::ui::policy::Presenter>();
root_presenter->PresentOrReplaceView(std::move(view_holder_token),
/* presentation */ nullptr);
auto [view_ref_control, view_ref] = scenic::ViewRefPair::New();
const auto view_ref_koid = ExtractKoid(view_ref);
WatchClientRenderStatus(view_ref_koid);
auto view_provider = realm()->Connect<fuchsia::ui::app::ViewProvider>();
view_provider->CreateViewWithViewRef(std::move(view_token.value), std::move(view_ref_control),
std::move(view_ref));
}
// Inject directly into Root Presenter, using fuchsia.ui.input FIDLs.
void InjectInput(int32_t x, int32_t y) {
using fuchsia::ui::input::InputReport;
// Device parameters
auto parameters = std::make_unique<fuchsia::ui::input::TouchscreenDescriptor>();
*parameters = {.x = {.range = {.min = 0, .max = static_cast<int32_t>(display_width())}},
.y = {.range = {.min = 0, .max = static_cast<int32_t>(display_height())}},
.max_finger_id = 10};
FX_LOGS(INFO) << "Registering touchscreen with x touch range = (" << parameters->x.range.min
<< ", " << parameters->x.range.max << ") "
<< "and y touch range = (" << parameters->y.range.min << ", "
<< parameters->y.range.max << ").";
// Register it against Root Presenter.
fuchsia::ui::input::DeviceDescriptor device{.touchscreen = std::move(parameters)};
auto registry = realm()->Connect<fuchsia::ui::input::InputDeviceRegistry>();
fuchsia::ui::input::InputDevicePtr connection;
registry->RegisterDevice(std::move(device), connection.NewRequest());
{
// Inject input report.
auto touch = std::make_unique<fuchsia::ui::input::TouchscreenReport>();
*touch = {.touches = {{.finger_id = 1, .x = x, .y = y}}};
InputReport report{.event_time = static_cast<uint64_t>(zx::clock::get_monotonic().get()),
.touchscreen = std::move(touch)};
connection->DispatchReport(std::move(report));
FX_LOGS(INFO) << "Dispatching touch report at (" << x << "," << y << ")";
}
{
// Inject conclusion (empty) report.
auto touch = std::make_unique<fuchsia::ui::input::TouchscreenReport>();
InputReport report{.event_time = static_cast<uint64_t>(zx::clock::get_monotonic().get()),
.touchscreen = std::move(touch)};
connection->DispatchReport(std::move(report));
}
++injection_count_;
FX_LOGS(INFO) << "*** Tap injected, count: " << injection_count_;
}
fuchsia::sys::ComponentControllerPtr& client_component() { return client_component_; }
sys::ServiceDirectory& child_services() { return *child_services_; }
RealmBuilder* builder() { return realm_builder_.get(); }
RealmRoot* realm() { return realm_.get(); }
InputPositionListenerServer* response_listener() { return response_listener_.get(); }
// Guaranteed to be initialized after SetUp().
uint32_t display_width() const { return *display_width_; }
uint32_t display_height() const { return *display_height_; }
private:
void BuildRealm(const std::vector<std::pair<ChildName, LegacyUrl>>& components,
const std::vector<Route>& routes,
const std::vector<std::pair<ChildName, std::string>>& v2_components) {
// Key part of service setup: have this test component vend the
// |ResponseListener| service in the constructed realm.
response_listener_ = std::make_unique<InputPositionListenerServer>(dispatcher());
builder()->AddLocalChild(kResponseListener, response_listener_.get());
// Add all components shared by each test to the realm.
AddBaseComponents(builder());
// Add components specific for this test case to the realm.
for (const auto& [name, component] : components) {
builder()->AddLegacyChild(name, component);
}
for (const auto& [name, component] : v2_components) {
builder()->AddChild(name, component);
}
// Add the necessary routing for each of the base components added above.
AddBaseRoutes(builder());
// Add the necessary routing for each of the extra components added above.
for (const auto& route : routes) {
builder()->AddRoute(route);
}
// Finally, build the realm using the provided components and routes.
realm_ = std::make_unique<RealmRoot>(builder()->Build());
}
std::unique_ptr<RealmBuilder> realm_builder_;
std::unique_ptr<RealmRoot> realm_;
std::unique_ptr<InputPositionListenerServer> response_listener_;
std::unique_ptr<scenic::Session> session_;
int injection_count_ = 0;
fuchsia::ui::scenic::ScenicPtr scenic_;
std::optional<uint32_t> display_width_;
std::optional<uint32_t> display_height_;
// Test view and child view's ViewHolder.
std::unique_ptr<scenic::ViewHolder> view_holder_;
std::unique_ptr<scenic::View> view_;
fuchsia::ui::observation::test::RegistrySyncPtr observer_registry_ptr_;
fuchsia::ui::observation::geometry::ProviderPtr geometry_provider_;
fuchsia::sys::ComponentControllerPtr client_component_;
std::shared_ptr<sys::ServiceDirectory> child_services_;
};
class WebEngineTest : public VirtualKeyboardBase {
protected:
std::vector<std::pair<ChildName, LegacyUrl>> GetTestComponents() override {
return {
std::make_pair(kWebContextProvider, kWebContextProviderUrl),
};
}
std::vector<std::pair<ChildName, LegacyUrl>> GetTestV2Components() override {
return {
std::make_pair(kBuildInfoProvider, kBuildInfoProviderUrl),
std::make_pair(kFontsProvider, kFontsProviderUrl),
std::make_pair(kIntl, kIntlUrl),
std::make_pair(kMemoryPressureProvider, kMemoryPressureProviderUrl),
std::make_pair(kNetstack, kNetstackUrl),
std::make_pair(kSemanticsManager, kSemanticsManagerUrl),
std::make_pair(kTextManager, kTextManagerUrl),
std::make_pair(kWebVirtualKeyboardClient, kWebVirtualKeyboardUrl),
};
}
std::vector<Route> GetTestRoutes() override {
return merge({
GetWebEngineRoutes(ChildRef{kWebVirtualKeyboardClient}),
{
{
.capabilities = {Protocol{fuchsia::ui::app::ViewProvider::Name_}},
.source = ChildRef{kWebVirtualKeyboardClient},
.targets = {ParentRef()},
},
{
.capabilities = {Protocol{fuchsia::logger::LogSink::Name_}},
.source = ParentRef(),
.targets = {ChildRef{kWebVirtualKeyboardClient}},
},
},
});
}
// Injects an input event, and posts a task to retry after `kTapRetryInterval`.
//
// We post the retry task because the first input event we send to WebEngine may be lost.
// There is no guarantee that, just because the web app has returned the location of the
// input box, that Chromium is actually ready to receive events from Scenic.
void TryInject(int32_t x, int32_t y) {
InjectInput(x, y);
inject_retry_task_.emplace(
[this, x, y](auto dispatcher, auto task, auto status) { TryInject(x, y); });
FX_CHECK(inject_retry_task_->PostDelayed(dispatcher(), kTapRetryInterval) == ZX_OK);
}
void CancelInject() { inject_retry_task_.reset(); }
// Routes needed to setup Chromium client.
static std::vector<Route> GetWebEngineRoutes(ChildRef target) {
return {
{.capabilities = {Protocol{test::virtualkeyboard::InputPositionListener::Name_}},
.source = ChildRef{kResponseListener},
.targets = {target}},
{.capabilities = {Protocol{fuchsia::fonts::Provider::Name_}},
.source = ChildRef{kFontsProvider},
.targets = {target}},
{.capabilities = {Protocol{fuchsia::tracing::provider::Registry::Name_},
Protocol{fuchsia::logger::LogSink::Name_},
Directory{.name = "config-data",
.rights = fuchsia::io::R_STAR_DIR,
.path = "/config/data"}},
.source = ParentRef(),
.targets = {ChildRef{kFontsProvider}}},
{.capabilities = {Protocol{fuchsia::ui::input3::Keyboard::Name_},
Protocol{fuchsia::ui::input::ImeService::Name_}},
.source = ChildRef{kTextManager},
.targets = {target}},
{.capabilities = {Protocol{fuchsia::intl::PropertyProvider::Name_}},
.source = ChildRef{kIntl},
.targets = {target, ChildRef{kSemanticsManager}}},
{.capabilities = {Protocol{fuchsia::input::virtualkeyboard::Manager::Name_},
Protocol{fuchsia::input::virtualkeyboard::ControllerCreator::Name_}},
.source = ChildRef{kRootPresenter},
.targets = {ChildRef{kWebVirtualKeyboardClient}}},
{.capabilities = {Protocol{fuchsia::memorypressure::Provider::Name_}},
.source = ChildRef{kMemoryPressureProvider},
.targets = {target}},
{.capabilities = {Protocol{fuchsia::scheduler::ProfileProvider::Name_},
Protocol{fuchsia::posix::socket::Provider::Name_},
Protocol{fuchsia::netstack::Netstack::Name_},
Protocol{fuchsia::net::interfaces::State::Name_}},
.source = ChildRef{kNetstack},
.targets = {target}},
{.capabilities = {Protocol{fuchsia::accessibility::semantics::SemanticsManager::Name_}},
.source = ChildRef{kSemanticsManager},
.targets = {target}},
{.capabilities = {Protocol{fuchsia::web::ContextProvider::Name_}},
.source = ChildRef{kWebContextProvider},
.targets = {target}},
{.capabilities = {Protocol{fuchsia::ui::scenic::Scenic::Name_},
Protocol{fuchsia::ui::focus::FocusChainListenerRegistry::Name_}},
.source = ChildRef{kScenicTestRealm},
.targets = {ChildRef{kSemanticsManager}}},
{.capabilities = {Protocol{fuchsia::cobalt::LoggerFactory::Name_}},
.source = ChildRef{kScenicTestRealm},
.targets = {ChildRef{kMemoryPressureProvider}}},
{.capabilities = {Protocol{fuchsia::sysmem::Allocator::Name_}},
.source = ParentRef(),
.targets = {ChildRef{kMemoryPressureProvider}, ChildRef{kWebVirtualKeyboardClient}}},
{.capabilities = {Protocol{fuchsia::tracing::provider::Registry::Name_},
Protocol{fuchsia::scheduler::ProfileProvider::Name_}},
.source = ParentRef(),
.targets = {ChildRef{kMemoryPressureProvider}}},
{.capabilities = {Protocol{fuchsia::ui::scenic::Scenic::Name_}},
.source = ChildRef{kScenicTestRealm},
.targets = {target}},
{.capabilities = {Protocol{fuchsia::buildinfo::Provider::Name_}},
.source = ChildRef{kBuildInfoProvider},
.targets = {target, ChildRef{kWebContextProvider}}},
};
}
static constexpr auto kWebVirtualKeyboardClient = "web_virtual_keyboard_client";
static constexpr auto kWebVirtualKeyboardUrl = "#meta/web-virtual-keyboard-client.cm";
private:
static constexpr auto kFontsProvider = "fonts_provider";
static constexpr auto kFontsProviderUrl = "#meta/fonts.cm";
static constexpr auto kTextManager = "text_manager";
static constexpr auto kTextManagerUrl = "#meta/text_manager.cm";
static constexpr auto kIntl = "intl";
static constexpr auto kIntlUrl = "#meta/intl_property_manager.cm";
static constexpr auto kMemoryPressureProvider = "memory_pressure_provider";
static constexpr auto kMemoryPressureProviderUrl = "#meta/memory_monitor.cm";
static constexpr auto kNetstack = "netstack";
static constexpr auto kNetstackUrl = "#meta/netstack.cm";
static constexpr auto kWebContextProvider = "web_context_provider";
static constexpr auto kWebContextProviderUrl =
"fuchsia-pkg://fuchsia.com/web_engine#meta/context_provider.cmx";
static constexpr auto kSemanticsManager = "semantics_manager";
static constexpr auto kSemanticsManagerUrl = "#meta/fake-a11y-manager.cm";
static constexpr auto kBuildInfoProvider = "build_info_provider";
static constexpr auto kBuildInfoProviderUrl = "#meta/fake_build_info.cm";
// The typical latency on devices we've tested is ~60 msec. The retry interval is chosen to be
// a) Long enough that it's unlikely that we send a new tap while a previous tap is still being
// processed. That is, it should be far more likely that a new tap is sent because the first
// tap was lost, than because the system is just running slowly.
// b) Short enough that we don't slow down tryjobs.
//
// The first property is important to avoid skewing the latency metrics that we collect.
// For an explanation of why a tap might be lost, see the documentation for TryInject().
static constexpr auto kTapRetryInterval = zx::sec(1);
std::optional<async::Task> inject_retry_task_;
};
TEST_F(WebEngineTest, ShowAndHideKeyboard) {
LaunchChromium();
client_component().events().OnTerminated = [](int64_t return_code,
fuchsia::sys::TerminationReason reason) {
if (return_code != 0) {
FX_LOGS(FATAL) << "Web appterminated abnormally with return_code=" << return_code
<< ", reason="
<< static_cast<std::underlying_type_t<decltype(reason)>>(reason);
}
};
FX_LOGS(INFO) << "Getting initial keyboard state";
std::optional<bool> is_keyboard_visible;
auto virtualkeyboard_manager = realm()->Connect<fuchsia::input::virtualkeyboard::Manager>();
virtualkeyboard_manager->WatchTypeAndVisibility(
[&is_keyboard_visible](auto text_type, auto is_visible) {
is_keyboard_visible = is_visible;
});
RunLoopUntil([&]() { return is_keyboard_visible.has_value(); });
ASSERT_FALSE(is_keyboard_visible.value());
is_keyboard_visible.reset();
FX_LOGS(INFO) << "Getting input box position";
RunLoopUntil([this]() { return response_listener()->input_position().has_value(); });
FX_LOGS(INFO) << "Tapping _inside_ input box";
auto input_pos = *response_listener()->input_position();
int32_t input_center_x = (input_pos.x0 + input_pos.x1) / 2;
int32_t input_center_y = (input_pos.y0 + input_pos.y1) / 2;
TryInject(input_center_x, input_center_y);
FX_LOGS(INFO) << "Waiting for keyboard to be visible";
virtualkeyboard_manager->WatchTypeAndVisibility(
[&is_keyboard_visible](auto text_type, auto is_visible) {
is_keyboard_visible = is_visible;
});
RunLoopUntil([&]() { return is_keyboard_visible.has_value(); });
ASSERT_TRUE(is_keyboard_visible.value());
CancelInject();
is_keyboard_visible.reset();
FX_LOGS(INFO) << "Tapping _outside_ input box";
TryInject(input_pos.x1 + 1, input_pos.y1 + 1);
FX_LOGS(INFO) << "Waiting for keyboard to be hidden";
virtualkeyboard_manager->WatchTypeAndVisibility(
[&is_keyboard_visible](auto text_type, auto is_visible) {
is_keyboard_visible = is_visible;
});
RunLoopUntil([&]() { return is_keyboard_visible.has_value(); });
ASSERT_FALSE(is_keyboard_visible.value());
CancelInject();
}
} // namespace