267 lines
9.0 KiB
C++
267 lines
9.0 KiB
C++
/*
|
|
* Copyright (C) 2021 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "host/libs/confui/host_server.h"
|
|
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <tuple>
|
|
|
|
#include "common/libs/confui/confui.h"
|
|
#include "common/libs/fs/shared_buf.h"
|
|
#include "host/libs/config/cuttlefish_config.h"
|
|
#include "host/libs/confui/host_utils.h"
|
|
#include "host/libs/confui/secure_input.h"
|
|
|
|
namespace cuttlefish {
|
|
namespace confui {
|
|
namespace {
|
|
|
|
template <typename Derived, typename Base>
|
|
std::unique_ptr<Derived> DowncastTo(std::unique_ptr<Base>&& base) {
|
|
Base* tmp = base.release();
|
|
Derived* derived = static_cast<Derived*>(tmp);
|
|
return std::unique_ptr<Derived>(derived);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/**
|
|
* null if not user/touch, or wrap it and ConfUiSecure{Selection,Touch}Message
|
|
*
|
|
* ConfUiMessage must NOT ConfUiSecure{Selection,Touch}Message types
|
|
*/
|
|
static std::unique_ptr<ConfUiMessage> WrapWithSecureFlag(
|
|
std::unique_ptr<ConfUiMessage>&& base_msg, const bool secure) {
|
|
switch (base_msg->GetType()) {
|
|
case ConfUiCmd::kUserInputEvent: {
|
|
auto as_selection =
|
|
DowncastTo<ConfUiUserSelectionMessage>(std::move(base_msg));
|
|
return ToSecureSelectionMessage(std::move(as_selection), secure);
|
|
}
|
|
case ConfUiCmd::kUserTouchEvent: {
|
|
auto as_touch = DowncastTo<ConfUiUserTouchMessage>(std::move(base_msg));
|
|
return ToSecureTouchMessage(std::move(as_touch), secure);
|
|
}
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
HostServer::HostServer(HostModeCtrl& host_mode_ctrl,
|
|
ConfUiRenderer& host_renderer,
|
|
const PipeConnectionPair& fd_pair)
|
|
: display_num_(0),
|
|
host_renderer_{host_renderer},
|
|
host_mode_ctrl_(host_mode_ctrl),
|
|
from_guest_fifo_fd_(fd_pair.from_guest_),
|
|
to_guest_fifo_fd_(fd_pair.to_guest_) {
|
|
const size_t max_elements = 20;
|
|
auto ignore_new =
|
|
[](ThreadSafeQueue<std::unique_ptr<ConfUiMessage>>::QueueImpl*) {
|
|
// no op, so the queue is still full, and the new item will be discarded
|
|
return;
|
|
};
|
|
hal_cmd_q_id_ = input_multiplexer_.RegisterQueue(
|
|
HostServer::Multiplexer::CreateQueue(max_elements, ignore_new));
|
|
user_input_evt_q_id_ = input_multiplexer_.RegisterQueue(
|
|
HostServer::Multiplexer::CreateQueue(max_elements, ignore_new));
|
|
}
|
|
|
|
bool HostServer::IsVirtioConsoleOpen() const {
|
|
return from_guest_fifo_fd_->IsOpen() && to_guest_fifo_fd_->IsOpen();
|
|
}
|
|
|
|
bool HostServer::CheckVirtioConsole() {
|
|
if (IsVirtioConsoleOpen()) return true;
|
|
ConfUiLog(FATAL) << "Virtio console is not open";
|
|
return false;
|
|
}
|
|
|
|
void HostServer::Start() {
|
|
if (!CheckVirtioConsole()) {
|
|
return;
|
|
}
|
|
auto hal_cmd_fetching = [this]() { this->HalCmdFetcherLoop(); };
|
|
auto main = [this]() { this->MainLoop(); };
|
|
hal_input_fetcher_thread_ =
|
|
thread::RunThread("HalInputLoop", hal_cmd_fetching);
|
|
main_loop_thread_ = thread::RunThread("MainLoop", main);
|
|
ConfUiLog(DEBUG) << "host service started.";
|
|
return;
|
|
}
|
|
|
|
void HostServer::HalCmdFetcherLoop() {
|
|
while (true) {
|
|
if (!CheckVirtioConsole()) {
|
|
return;
|
|
}
|
|
auto msg = RecvConfUiMsg(from_guest_fifo_fd_);
|
|
if (!msg) {
|
|
ConfUiLog(ERROR) << "Error in RecvConfUiMsg from HAL";
|
|
// TODO(kwstephenkim): error handling
|
|
// either file is not open, or ill-formatted message
|
|
continue;
|
|
}
|
|
/*
|
|
* In case of Vts test, the msg could be a user input. For now, we do not
|
|
* enforce the input grace period for Vts. However, if ever we do, here is
|
|
* where the time point check should happen. Once it is enqueued, it is not
|
|
* always guaranteed to be picked up reasonably soon.
|
|
*/
|
|
constexpr bool is_secure = false;
|
|
auto to_override_if_user_input =
|
|
WrapWithSecureFlag(std::move(msg), is_secure);
|
|
if (to_override_if_user_input) {
|
|
msg = std::move(to_override_if_user_input);
|
|
}
|
|
input_multiplexer_.Push(hal_cmd_q_id_, std::move(msg));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send inputs generated not by auto-tester but by the human users
|
|
*
|
|
* Send such inputs into the command queue consumed by the state machine
|
|
* in the main loop/current session.
|
|
*/
|
|
void HostServer::SendUserSelection(std::unique_ptr<ConfUiMessage>& input) {
|
|
if (!curr_session_ ||
|
|
curr_session_->GetState() != MainLoopState::kInSession ||
|
|
!curr_session_->IsReadyForUserInput()) {
|
|
// ignore
|
|
return;
|
|
}
|
|
constexpr bool is_secure = true;
|
|
auto secure_input = WrapWithSecureFlag(std::move(input), is_secure);
|
|
input_multiplexer_.Push(user_input_evt_q_id_, std::move(secure_input));
|
|
}
|
|
|
|
void HostServer::TouchEvent(const int x, const int y, const bool is_down) {
|
|
if (!is_down || !curr_session_) {
|
|
return;
|
|
}
|
|
std::unique_ptr<ConfUiMessage> input =
|
|
std::make_unique<ConfUiUserTouchMessage>(GetCurrentSessionId(), x, y);
|
|
SendUserSelection(input);
|
|
}
|
|
|
|
void HostServer::UserAbortEvent() {
|
|
if (!curr_session_) {
|
|
return;
|
|
}
|
|
std::unique_ptr<ConfUiMessage> input =
|
|
std::make_unique<ConfUiUserSelectionMessage>(GetCurrentSessionId(),
|
|
UserResponse::kUserAbort);
|
|
SendUserSelection(input);
|
|
}
|
|
|
|
// read the comments in the header file
|
|
[[noreturn]] void HostServer::MainLoop() {
|
|
while (true) {
|
|
// this gets one input from either queue:
|
|
// from HAL or from all webrtc clients
|
|
// if no input, sleep until there is
|
|
auto input_ptr = input_multiplexer_.Pop();
|
|
auto& input = *input_ptr;
|
|
const auto session_id = input.GetSessionId();
|
|
const auto cmd = input.GetType();
|
|
const std::string cmd_str(ToString(cmd));
|
|
|
|
// take input for the Finite States Machine below
|
|
std::string src = input.IsUserInput() ? "input" : "hal";
|
|
ConfUiLog(VERBOSE) << "In Session " << GetCurrentSessionId() << ", "
|
|
<< "in state " << GetCurrentState() << ", "
|
|
<< "received input from " << src << " cmd =" << cmd_str
|
|
<< " going to session " << session_id;
|
|
|
|
if (!curr_session_) {
|
|
if (cmd != ConfUiCmd::kStart) {
|
|
ConfUiLog(VERBOSE) << ToString(cmd) << " to " << session_id
|
|
<< " is ignored as there is no session to receive";
|
|
continue;
|
|
}
|
|
// the session is created as kInit
|
|
curr_session_ = CreateSession(input.GetSessionId());
|
|
}
|
|
if (cmd == ConfUiCmd::kUserTouchEvent) {
|
|
ConfUiSecureUserTouchMessage& touch_event =
|
|
static_cast<ConfUiSecureUserTouchMessage&>(input);
|
|
auto [x, y] = touch_event.GetLocation();
|
|
const bool is_confirm = curr_session_->IsConfirm(x, y);
|
|
const bool is_cancel = curr_session_->IsCancel(x, y);
|
|
ConfUiLog(INFO) << "Touch at [" << x << ", " << y << "] was "
|
|
<< (is_cancel ? "CANCEL"
|
|
: (is_confirm ? "CONFIRM" : "INVALID"));
|
|
if (!is_confirm && !is_cancel) {
|
|
// ignore, take the next input
|
|
continue;
|
|
}
|
|
decltype(input_ptr) tmp_input_ptr =
|
|
std::make_unique<ConfUiUserSelectionMessage>(
|
|
GetCurrentSessionId(),
|
|
(is_confirm ? UserResponse::kConfirm : UserResponse::kCancel));
|
|
input_ptr =
|
|
WrapWithSecureFlag(std::move(tmp_input_ptr), touch_event.IsSecure());
|
|
}
|
|
Transition(input_ptr);
|
|
|
|
// finalize
|
|
if (curr_session_ &&
|
|
curr_session_->GetState() == MainLoopState::kAwaitCleanup) {
|
|
curr_session_->CleanUp();
|
|
curr_session_ = nullptr;
|
|
}
|
|
} // end of the infinite while loop
|
|
}
|
|
|
|
std::shared_ptr<Session> HostServer::CreateSession(const std::string& name) {
|
|
return std::make_shared<Session>(name, display_num_, host_renderer_,
|
|
host_mode_ctrl_);
|
|
}
|
|
|
|
static bool IsUserAbort(ConfUiMessage& msg) {
|
|
if (msg.GetType() != ConfUiCmd::kUserInputEvent) {
|
|
return false;
|
|
}
|
|
ConfUiUserSelectionMessage& selection =
|
|
static_cast<ConfUiUserSelectionMessage&>(msg);
|
|
return (selection.GetResponse() == UserResponse::kUserAbort);
|
|
}
|
|
|
|
void HostServer::Transition(std::unique_ptr<ConfUiMessage>& input_ptr) {
|
|
auto& input = *input_ptr;
|
|
const auto session_id = input.GetSessionId();
|
|
const auto cmd = input.GetType();
|
|
const std::string cmd_str(ToString(cmd));
|
|
FsmInput fsm_input = ToFsmInput(input);
|
|
ConfUiLog(VERBOSE) << "Handling " << ToString(cmd);
|
|
if (IsUserAbort(input)) {
|
|
curr_session_->UserAbort(to_guest_fifo_fd_);
|
|
return;
|
|
}
|
|
|
|
if (cmd == ConfUiCmd::kAbort) {
|
|
curr_session_->Abort();
|
|
return;
|
|
}
|
|
curr_session_->Transition(to_guest_fifo_fd_, fsm_input, input);
|
|
}
|
|
|
|
} // end of namespace confui
|
|
} // end of namespace cuttlefish
|