recovery: Add ability to interrupt UI

Normally calling a UI method will block
indefinitely until the UI is actually
used. This creates a method to interrupt
the UI, causing waitKey to return -2. This
in turn, will cause ShowMenu to return -2.
This allows switching between recovery and
fastbootd via usb commands.

Test: adb shell /data/nativetest64/recovery_unit_test/recovery_unit_test
Bug: 78793464
Change-Id: I4c6c9aa18d79070877841a5c9818acf723fa6096
This commit is contained in:
Jerry Zhang 2018-05-22 12:08:35 -07:00 committed by Hridya Valsaraju
parent 6f1f2c811a
commit b76af93ab5
6 changed files with 163 additions and 33 deletions

View file

@ -47,6 +47,7 @@ class Device {
MOUNT_SYSTEM = 10, MOUNT_SYSTEM = 10,
RUN_GRAPHICS_TEST = 11, RUN_GRAPHICS_TEST = 11,
RUN_LOCALE_TEST = 12, RUN_LOCALE_TEST = 12,
KEY_INTERRUPTED = 13,
}; };
explicit Device(RecoveryUI* ui); explicit Device(RecoveryUI* ui);

View file

@ -326,6 +326,11 @@ static std::string browse_directory(const std::string& path, Device* device) {
headers, entries, chosen_item, true, headers, entries, chosen_item, true,
std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
// Return if WaitKey() was interrupted.
if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
return "";
}
const std::string& item = entries[chosen_item]; const std::string& item = entries[chosen_item];
if (chosen_item == 0) { if (chosen_item == 0) {
// Go up but continue browsing (if the caller is browse_directory). // Go up but continue browsing (if the caller is browse_directory).
@ -401,6 +406,11 @@ static bool prompt_and_wipe_data(Device* device) {
size_t chosen_item = ui->ShowMenu( size_t chosen_item = ui->ShowMenu(
headers, items, 0, true, headers, items, 0, true,
std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
// If ShowMenu() returned RecoveryUI::KeyError::INTERRUPTED, WaitKey() was interrupted.
if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
return false;
}
if (chosen_item != 1) { if (chosen_item != 1) {
return true; // Just reboot, no wipe; not a failure, user asked for it return true; // Just reboot, no wipe; not a failure, user asked for it
} }
@ -597,6 +607,11 @@ static void choose_recovery_file(Device* device) {
chosen_item = ui->ShowMenu( chosen_item = ui->ShowMenu(
headers, entries, chosen_item, true, headers, entries, chosen_item, true,
std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
// Handle WaitKey() interrupt.
if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
break;
}
if (entries[chosen_item] == "Back") break; if (entries[chosen_item] == "Back") break;
ui->ShowFile(entries[chosen_item]); ui->ShowFile(entries[chosen_item]);
@ -745,10 +760,14 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
size_t chosen_item = ui->ShowMenu( size_t chosen_item = ui->ShowMenu(
{}, device->GetMenuItems(), 0, false, {}, device->GetMenuItems(), 0, false,
std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
// Handle Interrupt key
if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
return Device::KEY_INTERRUPTED;
}
// Device-specific code may take some action here. It may return one of the core actions // Device-specific code may take some action here. It may return one of the core actions
// handled in the switch statement below. // handled in the switch statement below.
Device::BuiltinAction chosen_action = (chosen_item == static_cast<size_t>(-1)) Device::BuiltinAction chosen_action =
(chosen_item == static_cast<size_t>(RecoveryUI::KeyError::TIMED_OUT))
? Device::REBOOT ? Device::REBOOT
: device->InvokeMenuItem(chosen_item); : device->InvokeMenuItem(chosen_item);
@ -831,6 +850,9 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
} }
} }
break; break;
case Device::KEY_INTERRUPTED:
return Device::KEY_INTERRUPTED;
} }
} }
} }
@ -1072,6 +1094,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri
title_lines.insert(std::begin(title_lines), "Android Recovery"); title_lines.insert(std::begin(title_lines), "Android Recovery");
ui->SetTitle(title_lines); ui->SetTitle(title_lines);
ui->ResetKeyInterruptStatus();
device->StartRecovery(); device->StartRecovery();
printf("Command:"); printf("Command:");

View file

@ -417,6 +417,7 @@ void ScreenRecoveryUI::CheckBackgroundTextImages() {
FlushKeys(); FlushKeys();
while (true) { while (true) {
int key = WaitKey(); int key = WaitKey();
if (key == static_cast<int>(KeyError::INTERRUPTED)) break;
if (key == KEY_POWER || key == KEY_ENTER) { if (key == KEY_POWER || key == KEY_ENTER) {
break; break;
} else if (key == KEY_UP || key == KEY_VOLUMEUP) { } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
@ -925,6 +926,7 @@ void ScreenRecoveryUI::ShowFile(FILE* fp) {
while (show_prompt) { while (show_prompt) {
show_prompt = false; show_prompt = false;
int key = WaitKey(); int key = WaitKey();
if (key == static_cast<int>(KeyError::INTERRUPTED)) return;
if (key == KEY_POWER || key == KEY_ENTER) { if (key == KEY_POWER || key == KEY_ENTER) {
return; return;
} else if (key == KEY_UP || key == KEY_VOLUMEUP) { } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
@ -1017,19 +1019,26 @@ size_t ScreenRecoveryUI::ShowMenu(const std::vector<std::string>& headers,
// Throw away keys pressed previously, so user doesn't accidentally trigger menu items. // Throw away keys pressed previously, so user doesn't accidentally trigger menu items.
FlushKeys(); FlushKeys();
// If there is a key interrupt in progress, return KeyError::INTERRUPTED without starting the
// menu.
if (IsKeyInterrupted()) return static_cast<size_t>(KeyError::INTERRUPTED);
StartMenu(headers, items, initial_selection); StartMenu(headers, items, initial_selection);
int selected = initial_selection; int selected = initial_selection;
int chosen_item = -1; int chosen_item = -1;
while (chosen_item < 0) { while (chosen_item < 0) {
int key = WaitKey(); int key = WaitKey();
if (key == -1) { // WaitKey() timed out. if (key == static_cast<int>(KeyError::INTERRUPTED)) { // WaitKey() was interrupted.
return static_cast<size_t>(KeyError::INTERRUPTED);
}
if (key == static_cast<int>(KeyError::TIMED_OUT)) { // WaitKey() timed out.
if (WasTextEverVisible()) { if (WasTextEverVisible()) {
continue; continue;
} else { } else {
LOG(INFO) << "Timed out waiting for key input; rebooting."; LOG(INFO) << "Timed out waiting for key input; rebooting.";
EndMenu(); EndMenu();
return static_cast<size_t>(-1); return static_cast<size_t>(KeyError::TIMED_OUT);
} }
} }

View file

@ -264,6 +264,10 @@ int TestableScreenRecoveryUI::KeyHandler(int key, bool) const {
} }
int TestableScreenRecoveryUI::WaitKey() { int TestableScreenRecoveryUI::WaitKey() {
if (IsKeyInterrupted()) {
return static_cast<int>(RecoveryUI::KeyError::INTERRUPTED);
}
CHECK_LT(key_buffer_index_, key_buffer_.size()); CHECK_LT(key_buffer_index_, key_buffer_.size());
return static_cast<int>(key_buffer_[key_buffer_index_++]); return static_cast<int>(key_buffer_[key_buffer_index_++]);
} }
@ -391,7 +395,8 @@ TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut) {
ui_->SetKeyBuffer({ ui_->SetKeyBuffer({
KeyCode::TIMEOUT, KeyCode::TIMEOUT,
}); });
ASSERT_EQ(static_cast<size_t>(-1), ui_->ShowMenu(HEADERS, ITEMS, 3, true, nullptr)); ASSERT_EQ(static_cast<size_t>(RecoveryUI::KeyError::TIMED_OUT),
ui_->ShowMenu(HEADERS, ITEMS, 3, true, nullptr));
} }
TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) { TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) {
@ -412,6 +417,38 @@ TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) {
std::placeholders::_1, std::placeholders::_2))); std::placeholders::_1, std::placeholders::_2)));
} }
TEST_F(ScreenRecoveryUITest, ShowMenuWithInterrupt) {
RETURN_IF_NO_GRAPHICS;
ASSERT_TRUE(ui_->Init(kTestLocale));
ui_->SetKeyBuffer({
KeyCode::UP,
KeyCode::DOWN,
KeyCode::UP,
KeyCode::DOWN,
KeyCode::ENTER,
});
ui_->InterruptKey();
ASSERT_EQ(static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED),
ui_->ShowMenu(HEADERS, ITEMS, 3, true,
std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
std::placeholders::_1, std::placeholders::_2)));
ui_->SetKeyBuffer({
KeyCode::UP,
KeyCode::UP,
KeyCode::NO_OP,
KeyCode::NO_OP,
KeyCode::UP,
KeyCode::ENTER,
});
ASSERT_EQ(static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED),
ui_->ShowMenu(HEADERS, ITEMS, 0, true,
std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
std::placeholders::_1, std::placeholders::_2)));
}
TEST_F(ScreenRecoveryUITest, LoadAnimation) { TEST_F(ScreenRecoveryUITest, LoadAnimation) {
RETURN_IF_NO_GRAPHICS; RETURN_IF_NO_GRAPHICS;

85
ui.cpp
View file

@ -58,6 +58,7 @@ RecoveryUI::RecoveryUI()
touch_screen_allowed_(false), touch_screen_allowed_(false),
kTouchLowThreshold(RECOVERY_UI_TOUCH_LOW_THRESHOLD), kTouchLowThreshold(RECOVERY_UI_TOUCH_LOW_THRESHOLD),
kTouchHighThreshold(RECOVERY_UI_TOUCH_HIGH_THRESHOLD), kTouchHighThreshold(RECOVERY_UI_TOUCH_HIGH_THRESHOLD),
key_interrupted_(false),
key_queue_len(0), key_queue_len(0),
key_last_down(-1), key_last_down(-1),
key_long_press(false), key_long_press(false),
@ -404,34 +405,69 @@ void RecoveryUI::EnqueueKey(int key_code) {
} }
} }
int RecoveryUI::WaitKey() { void RecoveryUI::SetScreensaverState(ScreensaverState state) {
std::unique_lock<std::mutex> lk(key_queue_mutex); switch (state) {
case ScreensaverState::NORMAL:
// Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_),
// plugged in. brightness_file_)) {
do { screensaver_state_ = ScreensaverState::NORMAL;
std::cv_status rc = std::cv_status::no_timeout; LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_
while (key_queue_len == 0 && rc != std::cv_status::timeout) { << "%)";
rc = key_queue_cond.wait_for(lk, std::chrono::seconds(UI_WAIT_KEY_TIMEOUT_SEC)); } else {
LOG(ERROR) << "Unable to set brightness to normal";
} }
break;
if (screensaver_state_ != ScreensaverState::DISABLED) { case ScreensaverState::DIMMED:
if (rc == std::cv_status::timeout) {
// Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF.
if (screensaver_state_ == ScreensaverState::NORMAL) {
if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_), if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_),
brightness_file_)) { brightness_file_)) {
LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_ LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_
<< "%)"; << "%)";
screensaver_state_ = ScreensaverState::DIMMED; screensaver_state_ = ScreensaverState::DIMMED;
} else {
LOG(ERROR) << "Unable to set brightness to dim";
} }
} else if (screensaver_state_ == ScreensaverState::DIMMED) { break;
case ScreensaverState::OFF:
if (android::base::WriteStringToFile("0", brightness_file_)) { if (android::base::WriteStringToFile("0", brightness_file_)) {
LOG(INFO) << "Brightness: 0 (off)"; LOG(INFO) << "Brightness: 0 (off)";
screensaver_state_ = ScreensaverState::OFF; screensaver_state_ = ScreensaverState::OFF;
} else {
LOG(ERROR) << "Unable to set brightness to off";
}
break;
default:
LOG(ERROR) << "Invalid screensaver state";
} }
} }
} else if (screensaver_state_ != ScreensaverState::NORMAL) {
int RecoveryUI::WaitKey() {
std::unique_lock<std::mutex> lk(key_queue_mutex);
// Check for a saved key queue interruption.
if (key_interrupted_) {
SetScreensaverState(ScreensaverState::NORMAL);
return static_cast<int>(KeyError::INTERRUPTED);
}
// Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is
// plugged in.
do {
bool rc = key_queue_cond.wait_for(lk, std::chrono::seconds(UI_WAIT_KEY_TIMEOUT_SEC), [this] {
return this->key_queue_len != 0 || key_interrupted_;
});
if (key_interrupted_) {
SetScreensaverState(ScreensaverState::NORMAL);
return static_cast<int>(KeyError::INTERRUPTED);
}
if (screensaver_state_ != ScreensaverState::DISABLED) {
if (!rc) {
// Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF.
if (screensaver_state_ == ScreensaverState::NORMAL) {
SetScreensaverState(ScreensaverState::DIMMED);
} else if (screensaver_state_ == ScreensaverState::DIMMED) {
SetScreensaverState(ScreensaverState::OFF);
}
} else {
// Drop the first key if it's changing from OFF to NORMAL. // Drop the first key if it's changing from OFF to NORMAL.
if (screensaver_state_ == ScreensaverState::OFF) { if (screensaver_state_ == ScreensaverState::OFF) {
if (key_queue_len > 0) { if (key_queue_len > 0) {
@ -440,17 +476,12 @@ int RecoveryUI::WaitKey() {
} }
// Reset the brightness to normal. // Reset the brightness to normal.
if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_), SetScreensaverState(ScreensaverState::NORMAL);
brightness_file_)) {
screensaver_state_ = ScreensaverState::NORMAL;
LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_
<< "%)";
}
} }
} }
} while (IsUsbConnected() && key_queue_len == 0); } while (IsUsbConnected() && key_queue_len == 0);
int key = -1; int key = static_cast<int>(KeyError::TIMED_OUT);
if (key_queue_len > 0) { if (key_queue_len > 0) {
key = key_queue[0]; key = key_queue[0];
memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
@ -458,6 +489,14 @@ int RecoveryUI::WaitKey() {
return key; return key;
} }
void RecoveryUI::InterruptKey() {
{
std::lock_guard<std::mutex> lg(key_queue_mutex);
key_interrupted_ = true;
}
key_queue_cond.notify_one();
}
bool RecoveryUI::IsUsbConnected() { bool RecoveryUI::IsUsbConnected() {
int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); int fd = open("/sys/class/android_usb/android0/state", O_RDONLY);
if (fd < 0) { if (fd < 0) {

27
ui.h
View file

@ -51,6 +51,11 @@ class RecoveryUI {
IGNORE IGNORE
}; };
enum class KeyError : int {
TIMED_OUT = -1,
INTERRUPTED = -2,
};
RecoveryUI(); RecoveryUI();
virtual ~RecoveryUI(); virtual ~RecoveryUI();
@ -99,9 +104,13 @@ class RecoveryUI {
// --- key handling --- // --- key handling ---
// Waits for a key and return it. May return -1 after timeout. // Waits for a key and return it. May return TIMED_OUT after timeout and
// KeyError::INTERRUPTED on a key interrupt.
virtual int WaitKey(); virtual int WaitKey();
// Wakes up the UI if it is waiting on key input, causing WaitKey to return KeyError::INTERRUPTED.
virtual void InterruptKey();
virtual bool IsKeyPressed(int key); virtual bool IsKeyPressed(int key);
virtual bool IsLongPress(); virtual bool IsLongPress();
@ -147,11 +156,22 @@ class RecoveryUI {
// device-specific action, even without that being listed in the menu. Caller needs to handle // device-specific action, even without that being listed in the menu. Caller needs to handle
// such a case accordingly (e.g. by calling Device::InvokeMenuItem() to process the action). // such a case accordingly (e.g. by calling Device::InvokeMenuItem() to process the action).
// Returns a non-negative value (the chosen item number or device-specific action code), or // Returns a non-negative value (the chosen item number or device-specific action code), or
// static_cast<size_t>(-1) if timed out waiting for input. // static_cast<size_t>(TIMED_OUT) if timed out waiting for input or
// static_cast<size_t>(ERR_KEY_INTERTUPT) if interrupted, such as by InterruptKey().
virtual size_t ShowMenu(const std::vector<std::string>& headers, virtual size_t ShowMenu(const std::vector<std::string>& headers,
const std::vector<std::string>& items, size_t initial_selection, const std::vector<std::string>& items, size_t initial_selection,
bool menu_only, const std::function<int(int, bool)>& key_handler) = 0; bool menu_only, const std::function<int(int, bool)>& key_handler) = 0;
// Resets the key interrupt status.
void ResetKeyInterruptStatus() {
key_interrupted_ = false;
}
// Returns the key interrupt status.
bool IsKeyInterrupted() const {
return key_interrupted_;
}
protected: protected:
void EnqueueKey(int key_code); void EnqueueKey(int key_code);
@ -187,10 +207,11 @@ class RecoveryUI {
bool IsUsbConnected(); bool IsUsbConnected();
bool InitScreensaver(); bool InitScreensaver();
void SetScreensaverState(ScreensaverState state);
// Key event input queue // Key event input queue
std::mutex key_queue_mutex; std::mutex key_queue_mutex;
std::condition_variable key_queue_cond; std::condition_variable key_queue_cond;
bool key_interrupted_;
int key_queue[256], key_queue_len; int key_queue[256], key_queue_len;
char key_pressed[KEY_MAX + 1]; // under key_queue_mutex char key_pressed[KEY_MAX + 1]; // under key_queue_mutex
int key_last_down; // under key_queue_mutex int key_last_down; // under key_queue_mutex