Merge "recovery: Add ability to interrupt UI"
diff --git a/device.h b/device.h
index 9c43371..cbecc43 100644
--- a/device.h
+++ b/device.h
@@ -47,6 +47,7 @@
MOUNT_SYSTEM = 10,
RUN_GRAPHICS_TEST = 11,
RUN_LOCALE_TEST = 12,
+ KEY_INTERRUPTED = 13,
};
explicit Device(RecoveryUI* ui);
diff --git a/recovery.cpp b/recovery.cpp
index 3284440..3828e29 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -326,6 +326,11 @@
headers, entries, chosen_item, true,
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];
if (chosen_item == 0) {
// Go up but continue browsing (if the caller is browse_directory).
@@ -401,6 +406,11 @@
size_t chosen_item = ui->ShowMenu(
headers, items, 0, true,
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) {
return true; // Just reboot, no wipe; not a failure, user asked for it
}
@@ -597,6 +607,11 @@
chosen_item = ui->ShowMenu(
headers, entries, chosen_item, true,
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;
ui->ShowFile(entries[chosen_item]);
@@ -745,12 +760,16 @@
size_t chosen_item = ui->ShowMenu(
{}, device->GetMenuItems(), 0, false,
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
// handled in the switch statement below.
- Device::BuiltinAction chosen_action = (chosen_item == static_cast<size_t>(-1))
- ? Device::REBOOT
- : device->InvokeMenuItem(chosen_item);
+ Device::BuiltinAction chosen_action =
+ (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::TIMED_OUT))
+ ? Device::REBOOT
+ : device->InvokeMenuItem(chosen_item);
bool should_wipe_cache = false;
switch (chosen_action) {
@@ -831,6 +850,9 @@
}
}
break;
+
+ case Device::KEY_INTERRUPTED:
+ return Device::KEY_INTERRUPTED;
}
}
}
@@ -1072,6 +1094,7 @@
title_lines.insert(std::begin(title_lines), "Android Recovery");
ui->SetTitle(title_lines);
+ ui->ResetKeyInterruptStatus();
device->StartRecovery();
printf("Command:");
diff --git a/screen_ui.cpp b/screen_ui.cpp
index f9c4a06..c14f29d 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -417,6 +417,7 @@
FlushKeys();
while (true) {
int key = WaitKey();
+ if (key == static_cast<int>(KeyError::INTERRUPTED)) break;
if (key == KEY_POWER || key == KEY_ENTER) {
break;
} else if (key == KEY_UP || key == KEY_VOLUMEUP) {
@@ -925,6 +926,7 @@
while (show_prompt) {
show_prompt = false;
int key = WaitKey();
+ if (key == static_cast<int>(KeyError::INTERRUPTED)) return;
if (key == KEY_POWER || key == KEY_ENTER) {
return;
} else if (key == KEY_UP || key == KEY_VOLUMEUP) {
@@ -1017,19 +1019,26 @@
// Throw away keys pressed previously, so user doesn't accidentally trigger menu items.
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);
int selected = initial_selection;
int chosen_item = -1;
while (chosen_item < 0) {
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()) {
continue;
} else {
LOG(INFO) << "Timed out waiting for key input; rebooting.";
EndMenu();
- return static_cast<size_t>(-1);
+ return static_cast<size_t>(KeyError::TIMED_OUT);
}
}
diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp
index 4c0a868..7d97a00 100644
--- a/tests/unit/screen_ui_test.cpp
+++ b/tests/unit/screen_ui_test.cpp
@@ -264,6 +264,10 @@
}
int TestableScreenRecoveryUI::WaitKey() {
+ if (IsKeyInterrupted()) {
+ return static_cast<int>(RecoveryUI::KeyError::INTERRUPTED);
+ }
+
CHECK_LT(key_buffer_index_, key_buffer_.size());
return static_cast<int>(key_buffer_[key_buffer_index_++]);
}
@@ -391,7 +395,8 @@
ui_->SetKeyBuffer({
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) {
@@ -412,6 +417,38 @@
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) {
RETURN_IF_NO_GRAPHICS;
diff --git a/ui.cpp b/ui.cpp
index 6c91d01..a2c160f 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -58,6 +58,7 @@
touch_screen_allowed_(false),
kTouchLowThreshold(RECOVERY_UI_TOUCH_LOW_THRESHOLD),
kTouchHighThreshold(RECOVERY_UI_TOUCH_HIGH_THRESHOLD),
+ key_interrupted_(false),
key_queue_len(0),
key_last_down(-1),
key_long_press(false),
@@ -404,34 +405,69 @@
}
}
+void RecoveryUI::SetScreensaverState(ScreensaverState state) {
+ switch (state) {
+ case ScreensaverState::NORMAL:
+ if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_),
+ brightness_file_)) {
+ screensaver_state_ = ScreensaverState::NORMAL;
+ LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_
+ << "%)";
+ } else {
+ LOG(ERROR) << "Unable to set brightness to normal";
+ }
+ break;
+ case ScreensaverState::DIMMED:
+ if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_),
+ brightness_file_)) {
+ LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_
+ << "%)";
+ screensaver_state_ = ScreensaverState::DIMMED;
+ } else {
+ LOG(ERROR) << "Unable to set brightness to dim";
+ }
+ break;
+ case ScreensaverState::OFF:
+ if (android::base::WriteStringToFile("0", brightness_file_)) {
+ LOG(INFO) << "Brightness: 0 (off)";
+ screensaver_state_ = ScreensaverState::OFF;
+ } else {
+ LOG(ERROR) << "Unable to set brightness to off";
+ }
+ break;
+ default:
+ LOG(ERROR) << "Invalid screensaver state";
+ }
+}
+
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 {
- std::cv_status rc = std::cv_status::no_timeout;
- 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));
+ 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 == std::cv_status::timeout) {
+ if (!rc) {
// Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF.
if (screensaver_state_ == ScreensaverState::NORMAL) {
- if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_),
- brightness_file_)) {
- LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_
- << "%)";
- screensaver_state_ = ScreensaverState::DIMMED;
- }
+ SetScreensaverState(ScreensaverState::DIMMED);
} else if (screensaver_state_ == ScreensaverState::DIMMED) {
- if (android::base::WriteStringToFile("0", brightness_file_)) {
- LOG(INFO) << "Brightness: 0 (off)";
- screensaver_state_ = ScreensaverState::OFF;
- }
+ SetScreensaverState(ScreensaverState::OFF);
}
- } else if (screensaver_state_ != ScreensaverState::NORMAL) {
+ } else {
// Drop the first key if it's changing from OFF to NORMAL.
if (screensaver_state_ == ScreensaverState::OFF) {
if (key_queue_len > 0) {
@@ -440,17 +476,12 @@
}
// Reset the brightness to normal.
- if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_),
- brightness_file_)) {
- screensaver_state_ = ScreensaverState::NORMAL;
- LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_
- << "%)";
- }
+ SetScreensaverState(ScreensaverState::NORMAL);
}
}
} while (IsUsbConnected() && key_queue_len == 0);
- int key = -1;
+ int key = static_cast<int>(KeyError::TIMED_OUT);
if (key_queue_len > 0) {
key = key_queue[0];
memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
@@ -458,6 +489,14 @@
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() {
int fd = open("/sys/class/android_usb/android0/state", O_RDONLY);
if (fd < 0) {
diff --git a/ui.h b/ui.h
index 32e2809..a1e18cc 100644
--- a/ui.h
+++ b/ui.h
@@ -51,6 +51,11 @@
IGNORE
};
+ enum class KeyError : int {
+ TIMED_OUT = -1,
+ INTERRUPTED = -2,
+ };
+
RecoveryUI();
virtual ~RecoveryUI();
@@ -99,9 +104,13 @@
// --- 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();
+ // 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 IsLongPress();
@@ -147,11 +156,22 @@
// 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).
// 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,
const std::vector<std::string>& items, size_t initial_selection,
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:
void EnqueueKey(int key_code);
@@ -187,10 +207,11 @@
bool IsUsbConnected();
bool InitScreensaver();
-
+ void SetScreensaverState(ScreensaverState state);
// Key event input queue
std::mutex key_queue_mutex;
std::condition_variable key_queue_cond;
+ bool key_interrupted_;
int key_queue[256], key_queue_len;
char key_pressed[KEY_MAX + 1]; // under key_queue_mutex
int key_last_down; // under key_queue_mutex