blob: c14e0ed1657a54143b4d723405dfd7003635bce2 [file] [log] [blame]
/*
* 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 <chrono>
#include <functional>
#include <optional>
#include <tuple>
#include "common/libs/confui/confui.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/confui/host_utils.h"
namespace cuttlefish {
namespace confui {
static auto CuttlefishConfigDefaultInstance() {
auto config = cuttlefish::CuttlefishConfig::Get();
CHECK(config) << "Config must not be null";
return config->ForDefaultInstance();
}
static std::string HalGuestSocketPath() {
return CuttlefishConfigDefaultInstance().confui_hal_guest_socket_path();
}
HostServer& HostServer::Get(
HostModeCtrl& host_mode_ctrl,
cuttlefish::ScreenConnectorFrameRenderer& screen_connector) {
static HostServer host_server{host_mode_ctrl, screen_connector};
return host_server;
}
HostServer::HostServer(
cuttlefish::HostModeCtrl& host_mode_ctrl,
cuttlefish::ScreenConnectorFrameRenderer& screen_connector)
: display_num_(0),
host_mode_ctrl_(host_mode_ctrl),
screen_connector_{screen_connector},
renderer_(display_num_),
hal_socket_path_(HalGuestSocketPath()),
input_multiplexer_{/* max n_elems */ 20, /* n_Qs */ 2} {
hal_cmd_q_id_ = input_multiplexer_.GetNewQueueId(); // return 0
user_input_evt_q_id_ = input_multiplexer_.GetNewQueueId(); // return 1
}
void HostServer::Start() {
guest_hal_socket_ = cuttlefish::SharedFD::SocketLocalServer(
hal_socket_path_, false, SOCK_STREAM, 0666);
if (!guest_hal_socket_->IsOpen()) {
ConfUiLog(FATAL) << "Confirmation UI host service mandates a server socket"
<< "to which the guest HAL to connect.";
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) << "configured internal socket based input.";
return;
}
void HostServer::HalCmdFetcherLoop() {
hal_cli_socket_ = EstablishHalConnection();
if (!hal_cli_socket_->IsOpen()) {
ConfUiLog(FATAL)
<< "Confirmation UI host service mandates connection with HAL.";
return;
}
while (true) {
auto opted_msg = RecvConfUiMsg(hal_cli_socket_);
if (!opted_msg) {
ConfUiLog(ERROR) << "Error in RecvConfUiMsg from HAL";
continue;
}
auto input = std::move(opted_msg.value());
input_multiplexer_.Push(hal_cmd_q_id_, std::move(input));
}
}
bool HostServer::SendUserSelection(UserResponse::type selection) {
if (!curr_session_) {
ConfUiLog(FATAL) << "Current session must not be null";
return false;
}
if (curr_session_->GetState() != MainLoopState::kInSession) {
// ignore
return true;
}
std::lock_guard<std::mutex> lock(input_socket_mtx_);
if (selection != UserResponse::kConfirm &&
selection != UserResponse::kCancel) {
ConfUiLog(FATAL) << selection << " must be either" << UserResponse::kConfirm
<< "or" << UserResponse::kCancel;
return false; // not reaching here
}
ConfUiMessage input{GetCurrentSessionId(),
ToString(ConfUiCmd::kUserInputEvent), selection};
input_multiplexer_.Push(user_input_evt_q_id_, std::move(input));
return true;
}
void HostServer::PressConfirmButton(const bool is_down) {
if (!is_down) {
return;
}
// shared by N vnc/webRTC clients
SendUserSelection(UserResponse::kConfirm);
}
void HostServer::PressCancelButton(const bool is_down) {
if (!is_down) {
return;
}
// shared by N vnc/webRTC clients
SendUserSelection(UserResponse::kCancel);
}
bool HostServer::IsConfUiActive() {
if (!curr_session_) {
return false;
}
return curr_session_->IsConfUiActive();
}
SharedFD HostServer::EstablishHalConnection() {
ConfUiLog(DEBUG) << "Waiting hal accepting";
auto new_cli = SharedFD::Accept(*guest_hal_socket_);
ConfUiLog(DEBUG) << "hal client accepted";
return new_cli;
}
std::unique_ptr<Session> HostServer::ComputeCurrentSession(
const std::string& session_id) {
if (curr_session_ && (GetCurrentSessionId() != session_id)) {
ConfUiLog(FATAL) << curr_session_->GetId() << " is active and in the"
<< GetCurrentState() << "but HAL sends command to"
<< session_id;
}
if (curr_session_) {
return std::move(curr_session_);
}
// pick up a new session, or create one
auto result = GetSession(session_id);
if (result) {
return std::move(result);
}
auto raw_ptr = new Session(session_id, display_num_, renderer_,
host_mode_ctrl_, screen_connector_);
result = std::unique_ptr<Session>(raw_ptr);
// note that the new session is directly going to curr_session_
// when it is suspended, it will be moved to session_map_
return std::move(result);
}
// 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/vnc clients
// if no input, sleep until there is
const auto input = input_multiplexer_.Pop();
const auto& [session_id, cmd_str, additional_info] = input;
// take input for the Finite States Machine below
const ConfUiCmd cmd = ToCmd(cmd_str);
const bool is_user_input = (cmd == ConfUiCmd::kUserInputEvent);
std::string src = is_user_input ? "input" : "hal";
ConfUiLog(DEBUG) << "In Session" << GetCurrentSessionId() << ","
<< "in state" << GetCurrentState() << ","
<< "received input from" << src << "cmd =" << cmd_str
<< "and additional_info =" << additional_info
<< "going to session" << session_id;
FsmInput fsm_input = ToFsmInput(input);
if (is_user_input && !curr_session_) {
// discard the input, there's no session to take it yet
// actually, no confirmation UI screen is available
ConfUiLog(DEBUG) << "Took user input but no active session is available.";
continue;
}
/**
* if the curr_session_ is null, create one
* if the curr_session_ is not null but the session id doesn't match,
* something is wrong. Confirmation UI doesn't allow preemption by
* another confirmation UI session back to back. When it's preempted,
* HAL must send "kSuspend"
*
*/
curr_session_ = ComputeCurrentSession(session_id);
ConfUiLog(DEBUG) << "Host service picked up "
<< (curr_session_ ? curr_session_->GetId()
: "null session");
ConfUiLog(DEBUG) << "The state of current session is "
<< (curr_session_ ? ToString(curr_session_->GetState())
: "null session");
if (is_user_input) {
curr_session_->Transition(is_user_input, hal_cli_socket_, fsm_input,
additional_info);
} else {
ConfUiCmd cmd = ToCmd(cmd_str);
switch (cmd) {
case ConfUiCmd::kSuspend:
curr_session_->Suspend(hal_cli_socket_);
break;
case ConfUiCmd::kRestore:
curr_session_->Restore(hal_cli_socket_);
break;
case ConfUiCmd::kAbort:
curr_session_->Abort(hal_cli_socket_);
break;
default:
curr_session_->Transition(is_user_input, hal_cli_socket_, fsm_input,
additional_info);
break;
}
}
// check the session if it is inactive (e.g. init, suspended)
// and if it is done (transitioned to init from any other state)
if (curr_session_->IsSuspended()) {
session_map_[GetCurrentSessionId()] = std::move(curr_session_);
curr_session_ = nullptr;
continue;
}
if (curr_session_->GetState() == MainLoopState::kAwaitCleanup) {
curr_session_->CleanUp();
curr_session_ = nullptr;
}
// otherwise, continue with keeping the curr_session_
} // end of the infinite while loop
}
} // end of namespace confui
} // end of namespace cuttlefish