一般我们用adb reboot recovery或从bootloader按键进入recovery时,会出现一个倒地小机器人,下方提示No command。

我们来看下为啥会这样以及此场景下如何进入recovery menu。

ok, 让我们先check No command,以下参考10.0:

const GRSurface* ScreenRecoveryUI::GetCurrentText() const {
switch (current_icon_) {
case ERASING:
return erasing_text_.get();
case ERROR:
return error_text_.get();
case INSTALLING_UPDATE:
return installing_text_.get();
case NO_COMMAND:
return no_command_text_.get();
case NONE:
abort();
}
}

倒地小机器人是:

const GRSurface* ScreenRecoveryUI::GetCurrentFrame() const {
if (current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) {
return intro_done_ ? loop_frames_[current_frame_].get() : intro_frames_[current_frame_].get();
}
return error_icon_.get();
}

ok, 追下caller:

// Clear the screen and draw the currently selected background icon (if any).
// Should only be called with updateMutex locked.
void ScreenRecoveryUI::draw_background_locked() {
pagesIdentical = false;
gr_color(0, 0, 0, 255);
gr_clear();
if (current_icon_ != NONE) {
if (max_stage != -1) {
int stage_height = gr_get_height(stage_marker_empty_.get());
int stage_width = gr_get_width(stage_marker_empty_.get());
int x = (ScreenWidth() - max_stage * gr_get_width(stage_marker_empty_.get())) / 2;
int y = ScreenHeight() - stage_height - margin_height_;
for (int i = 0; i < max_stage; ++i) {
const auto& stage_surface = (i < stage) ? stage_marker_fill_ : stage_marker_empty_;
DrawSurface(stage_surface.get(), 0, 0, stage_width, stage_height, x, y);
x += stage_width;
}
}

const auto& text_surface = GetCurrentText();
int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2;
int text_y = GetTextBaseline();
gr_color(255, 255, 255, 255);
DrawTextIcon(text_x, text_y, text_surface);
}
}
// Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be
// called with updateMutex locked.
void ScreenRecoveryUI::draw_foreground_locked() {
if (current_icon_ != NONE) {
const auto& frame = GetCurrentFrame();
int frame_width = gr_get_width(frame);
int frame_height = gr_get_height(frame);
int frame_x = (ScreenWidth() - frame_width) / 2;
int frame_y = GetAnimationBaseline();
DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
}

有没有发现一个是foreground,一个是background。继续看:

// Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex
// locked.
void ScreenRecoveryUI::draw_screen_locked() {
if (!show_text) {
draw_background_locked();
draw_foreground_locked();
return;
}

gr_color(0, 0, 0, 255);
gr_clear();

// clang-format off
static std::vector<std::string> REGULAR_HELP{
"Use volume up/down and power.",
};
static std::vector<std::string> LONG_PRESS_HELP{
"Any button cycles highlight.",
"Long-press activates.",
};
// clang-format on
draw_menu_and_text_buffer_locked(HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP);
}

可以看到,draw_screen_locked()会根据show_text来区分,draw_menu_and_text_buffer_locked()才是recovery menu的drawer。

ok,我们转看下show_text:

–show_text - show the recovery text menu, used by some bootloader (e.g. http://b/36872519).

说的很清楚,这个变量就代表是否要show recovery text menu。

我们看下recovery入口:

ui->SetBackground(RecoveryUI::NONE);
if (show_text) ui->ShowText(true);

LOG(INFO) << "Starting recovery (pid " << getpid() << ") on " << ctime(&start);

show_text默认是false,如果带上参数show_text就直接show menu。

static constexpr struct option OPTIONS[] = {
{ "fastboot", no_argument, nullptr, 0 },
{ "locale", required_argument, nullptr, 0 },
{ "show_text", no_argument, nullptr, 't' },
{ nullptr, 0, nullptr, 0 },
};

bool show_text = false;
bool fastboot = false;
std::string locale;

int arg;
int option_index;
while ((arg = getopt_long(args_to_parse.size() - 1, args_to_parse.data(), "", OPTIONS,
&option_index)) != -1) {
switch (arg) {
case 't':
show_text = true;
break;

最终会走到start_recovery(),同样这里just_exit也是默认false。

Device::BuiltinAction start_recovery(Device* device, const std::vector<std::string>& args) {
static constexpr struct option OPTIONS[] = {
...
} else if (!just_exit) {
// If this is an eng or userdebug build, automatically turn on the text display if no command
// is specified. Note that this should be called before setting the background to avoid
// flickering the background image.
if (is_ro_debuggable()) {
ui->ShowText(true);
}
status = INSTALL_NONE; // No command specified
ui->SetBackground(RecoveryUI::NO_COMMAND);
}

注意了,只要是eng or userdebug版本,就直接call ->ShowText也就是show menu了。所以原生user版本才会出现小机器人。

看下ShowText():

void ScreenRecoveryUI::ShowText(bool visible) {
std::lock_guard<std::mutex> lg(updateMutex);
show_text = visible;
if (show_text) show_text_ever = true;
update_screen_locked(); //tj
}

继续看:

// Determine the next action.
// - If the state is INSTALL_REBOOT, device will reboot into the target as specified in
// `next_action`.
// - If the recovery menu is visible, prompt and wait for commands.
// - If the state is INSTALL_NONE, wait for commands (e.g. in user build, one manually boots
// into recovery to sideload a package or to wipe the device).
// - In all other cases, reboot the device. Therefore, normal users will observe the device
// rebooting a) immediately upon successful finish (INSTALL_SUCCESS); or b) an "error" screen
// for 5s followed by an automatic reboot.
if (status != INSTALL_REBOOT) {
if (status == INSTALL_NONE || ui->IsTextVisible()) {
Device::BuiltinAction temp = prompt_and_wait(device, status);
if (temp != Device::NO_ACTION) {
next_action = temp;
}
}
}

看注释,INSTALL_NONE的状态就是No command,会进入prompt_and_wait()

ok,到这里基本清楚了No command的由来。下面主要看下如何才能进入菜单,这里涉及到按键事件处理。

UI初始化时会有按键探测:

bool RecoveryUI::Init(const std::string& /* locale */) {
ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2),
touch_screen_allowed_);

ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));

一个是OnInputEvent()一个是OnKeyDetected()

int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
struct input_event ev;
if (ev_get_input(fd, epevents, &ev) == -1) {
return -1;
}
...
if (ev.type == EV_KEY && ev.code <= KEY_MAX) { //tj: here
if (touch_screen_allowed_) {
if (ev.code == BTN_TOUCH) {
// A BTN_TOUCH with value 1 indicates the start of contact (protocol A), with 0 means
// lifting the contact.
touch_finger_down_ = (ev.value == 1);
}

// Intentionally ignore BTN_TOUCH and BTN_TOOL_FINGER, which would otherwise trigger
// additional scrolling (because in ScreenRecoveryUI::ShowFile(), we consider keys other than
// KEY_POWER and KEY_UP as KEY_DOWN).
if (ev.code == BTN_TOUCH || ev.code == BTN_TOOL_FINGER) {
return 0;
}
}

ProcessKey(ev.code, ev.value); //tj: here
}

ProcessKey():

// Processes a key-up or -down event. A key is "registered" when it is pressed and then released,
// with no other keypresses or releases in between. Registered keys are passed to CheckKey() to
// see if it should trigger a visibility toggle, an immediate reboot, or be queued to be processed
// next time the foreground thread wants a key (eg, for the menu).
//
// We also keep track of which keys are currently down so that CheckKey() can call IsKeyPressed()
// to see what other keys are held when a key is registered.
//
// updown == 1 for key down events; 0 for key up events
void RecoveryUI::ProcessKey(int key_code, int updown) {
bool register_key = false;
bool long_press = false;

{

看注释,一个按键的up和down是registered key。注意We also keep track...,跟踪的是registered key和other keys。

CheckKey()会决定是否要显示,ok,再看代码:

void RecoveryUI::ProcessKey(int key_code, int updown) {
...
bool reboot_enabled = enable_reboot;
if (register_key) {
switch (CheckKey(key_code, long_press)) {
case RecoveryUI::IGNORE:
break;

case RecoveryUI::TOGGLE:
ShowText(!IsTextVisible()); //tj
break;

也就是CheckKey()返回的是TOGGLE就会显示菜单,前提不显示菜单(show_text是false)时:

bool ScreenRecoveryUI::IsTextVisible() {
std::lock_guard<std::mutex> lg(updateMutex);
int visible = show_text;
return visible;
}

ok,下来看看到底什么按键才能让No command再进入菜单界面:

RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) {
{
std::lock_guard<std::mutex> lg(key_queue_mutex);
key_long_press = false;
}

// If we have power and volume up keys, that chord is the signal to toggle the text display.
if (HasThreeButtons() || (HasPowerKey() && HasTouchScreen() && touch_screen_allowed_)) {
if ((key == KEY_VOLUMEUP || key == KEY_UP) && IsKeyPressed(KEY_POWER)) {
return TOGGLE;
}
} else {
...
}

bool RecoveryUI::IsKeyPressed(int key) {
std::lock_guard<std::mutex> lg(key_queue_mutex);
int pressed = key_pressed[key];
return pressed;
}

到这里,你应该知道如何进入了。如果还不知道怎么操作,check below: