Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b76af93a authored by Jerry Zhang's avatar Jerry Zhang Committed by Hridya Valsaraju
Browse files

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
parent 6f1f2c81
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ class Device {
    MOUNT_SYSTEM = 10,
    RUN_GRAPHICS_TEST = 11,
    RUN_LOCALE_TEST = 12,
    KEY_INTERRUPTED = 13,
  };

  explicit Device(RecoveryUI* ui);
+27 −4
Original line number Diff line number Diff line
@@ -326,6 +326,11 @@ static std::string browse_directory(const std::string& path, Device* device) {
        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 @@ static bool prompt_and_wipe_data(Device* device) {
    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 @@ static void choose_recovery_file(Device* device) {
    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,10 +760,14 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
    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::BuiltinAction chosen_action =
        (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::TIMED_OUT))
            ? Device::REBOOT
            : device->InvokeMenuItem(chosen_item);

@@ -831,6 +850,9 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
          }
        }
        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");
  ui->SetTitle(title_lines);

  ui->ResetKeyInterruptStatus();
  device->StartRecovery();

  printf("Command:");
+11 −2
Original line number Diff line number Diff line
@@ -417,6 +417,7 @@ void ScreenRecoveryUI::CheckBackgroundTextImages() {
  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 @@ void ScreenRecoveryUI::ShowFile(FILE* fp) {
      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 @@ size_t ScreenRecoveryUI::ShowMenu(const std::vector<std::string>& headers,
  // 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);
      }
    }

+38 −1
Original line number Diff line number Diff line
@@ -264,6 +264,10 @@ int TestableScreenRecoveryUI::KeyHandler(int key, bool) const {
}

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 @@ TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut) {
  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 @@ TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) {
                                        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;

+62 −23
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ RecoveryUI::RecoveryUI()
      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::EnqueueKey(int key_code) {
  }
}

int RecoveryUI::WaitKey() {
  std::unique_lock<std::mutex> lk(key_queue_mutex);

  // 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));
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";
      }

    if (screensaver_state_ != ScreensaverState::DISABLED) {
      if (rc == std::cv_status::timeout) {
        // Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF.
        if (screensaver_state_ == ScreensaverState::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";
      }
        } else if (screensaver_state_ == ScreensaverState::DIMMED) {
      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 {
    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 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 @@ int RecoveryUI::WaitKey() {
        }

        // 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 @@ int RecoveryUI::WaitKey() {
  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) {
Loading