blob: 292a78db4452d3d01ecc00936cef42ecfaa6584d [file] [log] [blame]
// Copyright 2021 The Chromium OS 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 "minios/screen_controller.h"
#include <utility>
#include <base/callback.h>
#include <base/logging.h>
#include <base/strings/strcat.h>
#include <base/time/time.h>
#include <brillo/message_loops/message_loop.h>
#include "minios/error.h"
#include "minios/recovery_installer.h"
#include "minios/screens/screen_debug_options.h"
#include "minios/screens/screen_download.h"
#include "minios/screens/screen_error.h"
#include "minios/screens/screen_language_dropdown.h"
#include "minios/screens/screen_log.h"
#include "minios/screens/screen_network.h"
#include "minios/screens/screen_permission.h"
#include "minios/screens/screen_welcome.h"
#include "minios/utils.h"
namespace minios {
ScreenController::ScreenController(
std::shared_ptr<DrawInterface> draw_utils,
std::shared_ptr<UpdateEngineProxy> update_engine_proxy,
std::shared_ptr<NetworkManagerInterface> network_manager,
ProcessManagerInterface* process_manager)
: draw_utils_(draw_utils),
update_engine_proxy_(update_engine_proxy),
network_manager_(network_manager),
process_manager_(process_manager),
key_reader_(
KeyReader{/*include_usb=*/true, GetKeyboardLayout(process_manager_)}),
key_states_(kFdsMax, std::vector<bool>(kKeyMax, false)) {}
bool ScreenController::Init() {
if (!draw_utils_ || !draw_utils_->Init()) {
LOG(ERROR) << "Screen drawing utility not available. Cannot continue.";
return false;
}
update_engine_proxy_->Init();
std::vector<int> wait_keys = {KEY_UP, KEY_DOWN, KEY_ENTER, KEY_ESC};
if (draw_utils_->IsDetachable())
wait_keys = {KEY_VOLUMEDOWN, KEY_VOLUMEUP, KEY_POWER, KEY_ESC};
if (!key_reader_.Init(wait_keys)) {
LOG(ERROR) << "Could not initialize key reader. Unable to continue.";
return false;
}
key_reader_.SetDelegate(this);
current_screen_ = CreateScreen(ScreenType::kWelcomeScreen);
current_screen_->Show();
return true;
}
std::unique_ptr<ScreenInterface> ScreenController::CreateScreen(
ScreenType screen_type) {
switch (screen_type) {
case ScreenType::kWelcomeScreen:
return std::make_unique<ScreenWelcome>(draw_utils_, this);
case ScreenType::kNetworkDropDownScreen: {
auto screen = std::make_unique<ScreenNetwork>(
draw_utils_, network_manager_, &key_reader_, this);
if (!seeded_ssid_.empty()) {
screen->SeedCredentials(seeded_ssid_, seeded_passphrase_);
}
return screen;
}
case ScreenType::kLanguageDropDownScreen:
return std::make_unique<ScreenLanguageDropdown>(draw_utils_, this);
case ScreenType::kUserPermissionScreen:
return std::make_unique<ScreenPermission>(draw_utils_, this);
case ScreenType::kStartDownload:
return std::make_unique<ScreenDownload>(
std::make_unique<RecoveryInstaller>(process_manager_),
update_engine_proxy_, draw_utils_,
std::make_unique<MetricsReporter>(process_manager_), this);
case ScreenType::kDownloadError:
case ScreenType::kNetworkError:
case ScreenType::kPasswordError:
case ScreenType::kConnectionError:
case ScreenType::kGeneralError:
return std::make_unique<ScreenError>(screen_type, draw_utils_, this);
case ScreenType::kDebugOptionsScreen:
return std::make_unique<ScreenDebugOptions>(draw_utils_, this);
case ScreenType::kLogScreen:
return std::make_unique<ScreenLog>(draw_utils_, this);
default:
LOG(FATAL) << "Invalid screen.";
return nullptr;
}
}
void ScreenController::OnForward(ScreenInterface* screen) {
switch (screen->GetType()) {
case ScreenType::kWelcomeScreen:
current_screen_ = CreateScreen(ScreenType::kNetworkDropDownScreen);
break;
case ScreenType::kNetworkDropDownScreen:
current_screen_ = CreateScreen(ScreenType::kUserPermissionScreen);
break;
case ScreenType::kUserPermissionScreen:
current_screen_ = CreateScreen(ScreenType::kStartDownload);
break;
case ScreenType::kDownloadError:
case ScreenType::kNetworkError:
case ScreenType::kPasswordError:
case ScreenType::kConnectionError:
case ScreenType::kGeneralError:
// Show debug options and log screen. Save error screen to return to from
// `kDebugOptionsScreen`.
previous_screen_ = std::move(current_screen_);
current_screen_ = CreateScreen(ScreenType::kDebugOptionsScreen);
break;
case ScreenType::kDebugOptionsScreen:
current_screen_ = CreateScreen(ScreenType::kLogScreen);
break;
default:
LOG(FATAL) << "Invalid screen.";
}
current_screen_->Show();
}
void ScreenController::OnBackward(ScreenInterface* screen) {
switch (screen->GetType()) {
case ScreenType::kWelcomeScreen:
case ScreenType::kNetworkDropDownScreen:
case ScreenType::kUserPermissionScreen:
previous_screen_ = nullptr;
current_screen_ = CreateScreen(ScreenType::kWelcomeScreen);
break;
case ScreenType::kPasswordError:
case ScreenType::kNetworkError:
case ScreenType::kConnectionError:
// Return to network dropdown screen.
previous_screen_ = nullptr;
current_screen_ = CreateScreen(ScreenType::kNetworkDropDownScreen);
break;
case ScreenType::kDownloadError:
case ScreenType::kGeneralError:
// Return to beginning of the flow.
previous_screen_ = nullptr;
current_screen_ = CreateScreen(ScreenType::kWelcomeScreen);
break;
case ScreenType::kDebugOptionsScreen:
// Back to original error screen, reset index.
if (previous_screen_ &&
dynamic_cast<ScreenError*>(previous_screen_.get())) {
current_screen_ = std::move(previous_screen_);
current_screen_->Reset();
} else {
// No error screen saved. Go back to beginning.
previous_screen_ = nullptr;
current_screen_ = CreateScreen(ScreenType::kWelcomeScreen);
}
break;
case ScreenType::kLogScreen:
// Back to debug options screen.
current_screen_ = CreateScreen(ScreenType::kDebugOptionsScreen);
break;
case ScreenType::kLanguageDropDownScreen:
if (previous_screen_) {
current_screen_ = std::move(previous_screen_);
} else {
current_screen_ = CreateScreen(ScreenType::kWelcomeScreen);
}
break;
default:
LOG(FATAL) << "Invalid screen.";
}
current_screen_->Show();
}
void ScreenController::OnError(ScreenType error_screen) {
switch (error_screen) {
case ScreenType::kDownloadError:
case ScreenType::kNetworkError:
case ScreenType::kPasswordError:
case ScreenType::kConnectionError:
case ScreenType::kGeneralError:
previous_screen_ = std::move(current_screen_);
current_screen_ = CreateScreen(error_screen);
break;
default:
LOG(WARNING)
<< "Not a valid error screen. Defaulting to general error case.";
previous_screen_ = std::move(current_screen_);
current_screen_ = CreateScreen(ScreenType::kGeneralError);
break;
}
current_screen_->Show();
}
ScreenType ScreenController::GetCurrentScreen() {
return current_screen_->GetType();
}
void ScreenController::SwitchLocale(ScreenInterface* screen) {
previous_screen_ = std::move(current_screen_);
current_screen_ = CreateScreen(ScreenType::kLanguageDropDownScreen);
current_screen_->Show();
}
void ScreenController::UpdateLocale(ScreenInterface* screen,
int selected_locale_index) {
// Change locale and update constants.
CHECK(draw_utils_) << "Screen drawing utility not available.";
if (screen->GetType() != ScreenType::kLanguageDropDownScreen) {
LOG(WARNING) << "Only the language dropdown screen can change the locale.";
return;
}
draw_utils_->LocaleChange(selected_locale_index);
current_screen_ = std::move(previous_screen_);
current_screen_->Reset();
current_screen_->Show();
}
void ScreenController::OnKeyPress(int fd_index,
int key_changed,
bool key_released) {
CHECK(current_screen_) << "Could not send key event to screen.";
// Make sure you have seen a key press for this key before ending on key
// event release.
if (fd_index < 0 || key_changed < 0 || fd_index >= key_states_.size() ||
key_changed >= key_states_[0].size()) {
LOG(ERROR) << "Fd index or key code out of range. Index: " << fd_index
<< ". Key code: " << key_changed;
return;
}
if (key_released && key_states_[fd_index][key_changed]) {
key_states_[fd_index][key_changed] = false;
// Send key event to the currently displayed screen. It will decide what to
// do with it.
current_screen_->OnKeyPress(key_changed);
return;
} else if (!key_released) {
key_states_[fd_index][key_changed] = true;
}
}
void ScreenController::GetState(State* state_out) {
CHECK(current_screen_) << "Could not get State for current screen.";
state_out->CopyFrom(current_screen_->GetState());
}
bool ScreenController::MoveBackward(brillo::ErrorPtr* error) {
CHECK(current_screen_) << "Could not move to previous screen.";
LOG(INFO) << "MoveBackward from screen: " << current_screen_->GetName();
return current_screen_->MoveBackward(error);
}
bool ScreenController::MoveForward(brillo::ErrorPtr* error) {
CHECK(current_screen_) << "Could not move to next screen.";
LOG(INFO) << "MoveForward from screen: " << current_screen_->GetName();
return current_screen_->MoveForward(error);
}
void ScreenController::PressKey(int key_changed) {
CHECK(current_screen_) << "Could not send key event to screen.";
// TODO(hbarnor): Does not support GetPassword screen. Need to look into using
// `KeyReader` for this.
current_screen_->OnKeyPress(key_changed);
}
bool ScreenController::Reset(brillo::ErrorPtr* error) {
if (ResetScreen(error)) {
seeded_ssid_.clear();
seeded_passphrase_.clear();
dbus_recovery_state_.reset();
return true;
}
return false;
}
bool ScreenController::ResetScreen(brillo::ErrorPtr* error) {
// Don't allow reset in the middle of recovering.
if (current_screen_->GetType() == ScreenType::kStartDownload) {
Error::AddTo(error, FROM_HERE, error::kCannotReset,
"Cannot reset whiles recovery in progress.");
return false;
}
previous_screen_ = nullptr;
current_screen_ = CreateScreen(ScreenType::kWelcomeScreen);
current_screen_->Show();
return true;
}
void ScreenController::SeedNetworkCredentials(const std::string& ssid,
const std::string& passphrase) {
seeded_ssid_ = ssid;
seeded_passphrase_ = passphrase;
if (current_screen_->GetType() == ScreenType::kNetworkDropDownScreen) {
ScreenNetwork* screen = dynamic_cast<ScreenNetwork*>(current_screen_.get());
if (screen)
screen->SeedCredentials(seeded_ssid_, seeded_passphrase_);
}
}
void ScreenController::StartRecovery(const std::string& ssid,
const std::string& passphrase) {
if (dbus_recovery_state_.has_value()) {
LOG(ERROR) << "Recovery already in progress.;";
return;
}
dbus_recovery_state_ = State::IDLE;
brillo::ErrorPtr error;
LOG(INFO) << "Starting Dbus triggered recovery flow.;";
// Always start from welcome screen.
bool result = ResetScreen(&error);
if (result) {
SeedNetworkCredentials(ssid, passphrase);
// Move forward from welcome screen.
brillo::MessageLoop::current()->PostTask(
FROM_HERE,
base::BindOnce(base::IgnoreResult(&ScreenController::MoveForward),
base::Unretained(this), nullptr));
}
if (!result) {
LOG(ERROR) << "StartRecovery failed. Reason: " << error->GetMessage();
if (dbus_recovery_state_.value() != State::ERROR)
OnError(ScreenType::kGeneralError);
}
}
void ScreenController::SetStateReporter(
StateReporterInterface* state_reporter) {
state_reporter_ = state_reporter;
}
void ScreenController::OnStateChanged(State state) {
if (state_reporter_) {
// StateChanged observers cannot/should not modify state.
state_reporter_->StateChanged(state);
}
HandleStateChanged(state.state());
}
void ScreenController::HandleStateChanged(State::States state_state) {
if (!dbus_recovery_state_.has_value() ||
(dbus_recovery_state_.value() == state_state))
return;
LOG(INFO) << "Recovery flow transitioning state: "
<< State_States_Name(dbus_recovery_state_.value()) << " -> "
<< State_States_Name(state_state);
dbus_recovery_state_ = state_state;
switch (state_state) {
case State::CONNECTED:
case State::NETWORK_CREDENTIALS:
case State::NETWORK_SELECTION: {
brillo::MessageLoop::current()->PostTask(
FROM_HERE,
base::BindOnce(base::IgnoreResult(&ScreenController::MoveForward),
base::Unretained(this), nullptr));
break;
}
case State::ERROR:
case State::COMPLETED: {
seeded_ssid_.clear();
seeded_passphrase_.clear();
dbus_recovery_state_.reset();
break;
}
default:
break;
}
}
} // namespace minios