/* * 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/session.h" #include #include "common/libs/utils/contains.h" #include "host/libs/confui/secure_input.h" namespace cuttlefish { namespace confui { Session::Session(const std::string& session_name, const std::uint32_t display_num, ConfUiRenderer& host_renderer, HostModeCtrl& host_mode_ctrl, const std::string& locale) : session_id_{session_name}, display_num_{display_num}, renderer_{host_renderer}, host_mode_ctrl_{host_mode_ctrl}, locale_{locale}, state_{MainLoopState::kInit}, saved_state_{MainLoopState::kInit} {} /** return grace period + alpha * * grace period is the gap between user seeing the dialog * and the UI starts to take the user inputs * Grace period should be at least 1s. * Session requests the Renderer to render the dialog, * but it might not be immediate. So, add alpha to 1s */ static const std::chrono::milliseconds GetGracePeriod() { using std::literals::chrono_literals::operator""ms; return 1000ms + 100ms; } bool Session::IsReadyForUserInput() const { using std::literals::chrono_literals::operator""ms; if (!start_time_) { return false; } const auto right_now = Clock::now(); return (right_now - *start_time_) >= GetGracePeriod(); } bool Session::RenderDialog() { auto result = renderer_.RenderDialog(display_num_, prompt_text_, locale_, ui_options_); if (!result.ok()) { LOG(ERROR) << result.error().FormatForEnv(); return false; } return true; } MainLoopState Session::Transition(SharedFD& hal_cli, const FsmInput fsm_input, const ConfUiMessage& conf_ui_message) { bool should_keep_running = false; bool already_terminated = false; switch (state_) { case MainLoopState::kInit: { should_keep_running = HandleInit(hal_cli, fsm_input, conf_ui_message); } break; case MainLoopState::kInSession: { should_keep_running = HandleInSession(hal_cli, fsm_input, conf_ui_message); } break; case MainLoopState::kWaitStop: { if (IsUserInput(fsm_input)) { ConfUiLog(VERBOSE) << "User input ignored " << ToString(fsm_input) << " : " << ToString(conf_ui_message) << " at the state " << ToString(state_); } should_keep_running = HandleWaitStop(hal_cli, fsm_input); } break; case MainLoopState::kTerminated: { already_terminated = true; } break; default: ConfUiLog(FATAL) << "Must not be in the state of " << ToString(state_); break; } if (!should_keep_running && !already_terminated) { ScheduleToTerminate(); } return state_; }; void Session::CleanUp() { if (state_ != MainLoopState::kAwaitCleanup) { ConfUiLog(FATAL) << "Clean up a session only when in kAwaitCleanup"; } state_ = MainLoopState::kTerminated; // common action done when the state is back to init state host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kAndroidMode); } void Session::ScheduleToTerminate() { state_ = MainLoopState::kAwaitCleanup; saved_state_ = MainLoopState::kInvalid; } bool Session::ReportErrorToHal(SharedFD hal_cli, const std::string& msg) { ScheduleToTerminate(); if (!SendAck(hal_cli, session_id_, false, msg)) { ConfUiLog(ERROR) << "I/O error in sending ack to report rendering failure"; return false; } return true; } void Session::Abort() { ConfUiLog(VERBOSE) << "Abort is called"; ScheduleToTerminate(); return; } void Session::UserAbort(SharedFD hal_cli) { ConfUiLog(VERBOSE) << "it is a user abort input."; SendAbortCmd(hal_cli, GetId()); Abort(); ScheduleToTerminate(); } bool Session::HandleInit(SharedFD hal_cli, const FsmInput fsm_input, const ConfUiMessage& conf_ui_message) { if (IsUserInput(fsm_input)) { // ignore user input state_ = MainLoopState::kInit; return true; } ConfUiLog(VERBOSE) << ToString(fsm_input) << "is handled in HandleInit"; if (fsm_input != FsmInput::kHalStart) { ConfUiLog(ERROR) << "invalid cmd for Init State:" << ToString(fsm_input); // ReportErrorToHal returns true if error report was successful // However, anyway we abort this session on the host ReportErrorToHal(hal_cli, HostError::kSystemError); return false; } // Start Session ConfUiLog(VERBOSE) << "Sending ack to hal_cli: " << Enum2Base(ConfUiCmd::kCliAck); host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kConfUI_Mode); auto start_cmd_msg = static_cast(conf_ui_message); prompt_text_ = start_cmd_msg.GetPromptText(); locale_ = start_cmd_msg.GetLocale(); extra_data_ = start_cmd_msg.GetExtraData(); ui_options_ = start_cmd_msg.GetUiOpts(); // cbor_ can be correctly created after the session received kStart cmd // at runtime cbor_ = std::make_unique(prompt_text_, extra_data_); if (cbor_->IsMessageTooLong()) { ConfUiLog(ERROR) << "The prompt text and extra_data are too long to be " << "properly encoded."; ReportErrorToHal(hal_cli, HostError::kMessageTooLongError); return false; } if (cbor_->IsMalformedUtf8()) { ConfUiLog(ERROR) << "The prompt text appears to have incorrect UTF8 format"; ReportErrorToHal(hal_cli, HostError::kIncorrectUTF8); return false; } if (!cbor_->IsOk()) { ConfUiLog(ERROR) << "Unknown Error in cbor implementation"; ReportErrorToHal(hal_cli, HostError::kSystemError); return false; } if (!RenderDialog()) { // the confirmation UI is driven by a user app, not running from the start // automatically so that means webRTC should have been set up ConfUiLog(ERROR) << "Dialog is not rendered. However, it should." << "No webRTC can't initiate any confirmation UI."; ReportErrorToHal(hal_cli, HostError::kUIError); return false; } start_time_ = std::make_unique(Clock::now()); if (!SendAck(hal_cli, session_id_, true, "started")) { ConfUiLog(ERROR) << "Ack to kStart failed in I/O"; return false; } state_ = MainLoopState::kInSession; return true; } bool Session::HandleInSession(SharedFD hal_cli, const FsmInput fsm_input, const ConfUiMessage& conf_ui_msg) { auto invalid_input_handler = [&, this]() { ReportErrorToHal(hal_cli, HostError::kSystemError); ConfUiLog(ERROR) << "cmd " << ToString(fsm_input) << " should not be handled in HandleInSession"; }; if (!IsUserInput(fsm_input)) { invalid_input_handler(); return false; } const auto& user_input_msg = static_cast(conf_ui_msg); const auto response = user_input_msg.GetResponse(); if (response == UserResponse::kUnknown || response == UserResponse::kUserAbort) { invalid_input_handler(); return false; } const bool is_secure_input = user_input_msg.IsSecure(); ConfUiLog(VERBOSE) << "In HandleInSession, session " << session_id_ << " is sending the user input " << ToString(fsm_input); bool is_success = false; if (response == UserResponse::kCancel) { // no need to sign is_success = SendResponse(hal_cli, session_id_, UserResponse::kCancel, std::vector{}, std::vector{}); } else { message_ = std::move(cbor_->GetMessage()); auto message_opt = (is_secure_input ? Sign(message_) : TestSign(message_)); if (!message_opt) { ReportErrorToHal(hal_cli, HostError::kSystemError); return false; } signed_confirmation_ = message_opt.value(); is_success = SendResponse(hal_cli, session_id_, UserResponse::kConfirm, signed_confirmation_, message_); } if (!is_success) { ConfUiLog(ERROR) << "I/O error in sending user response to HAL"; return false; } state_ = MainLoopState::kWaitStop; return true; } bool Session::HandleWaitStop(SharedFD hal_cli, const FsmInput fsm_input) { if (IsUserInput(fsm_input)) { // ignore user input state_ = MainLoopState::kWaitStop; return true; } if (fsm_input == FsmInput::kHalStop) { ConfUiLog(VERBOSE) << "Handling Abort in kWaitStop."; ScheduleToTerminate(); return true; } ReportErrorToHal(hal_cli, HostError::kSystemError); ConfUiLog(FATAL) << "In WaitStop, received wrong HAL command " << ToString(fsm_input); return false; } } // end of namespace confui } // end of namespace cuttlefish