// Return the current time as a double (including fractions of a second). staticdouble now() { struct timeval tv;
gettimeofday(&tv, nullptr); return tv.tv_sec + tv.tv_usec / 1000000.0;
}
// It's fine to have more entries than text_rows_ if scrollable menu is supported.
size_t items_count = scrollable_ ? items.size() : std::min(items.size(), max_display_items_); for (size_t i = 0; i < items_count; ++i) {
text_items_.emplace_back(items[i].substr(0, max_item_length_));
}
// TODO(xunchang) modify the function parameters to button up & down. int TextMenu::Select(int sel) {
CHECK_LE(ItemsCount(), static_cast<size_t>(std::numeric_limits<int>::max())); int count = ItemsCount();
// Wraps the selection at boundary if the menu is not scrollable. if (!scrollable_) { if (sel < 0) {
selection_ = count - 1;
} elseif (sel >= count) {
selection_ = 0;
} else {
selection_ = sel;
}
int TextMenu::DrawHeader(int x, int y) const { int offset = 0;
draw_funcs_.SetColor(UIElement::HEADER); if (!scrollable()) {
offset += draw_funcs_.DrawWrappedTextLines(x, y + offset, text_headers());
} else {
offset += draw_funcs_.DrawTextLines(x, y + offset, text_headers()); // Show the current menu item number in relation to total number if items don't fit on the // screen.
std::string cur_selection_str; if (ItemsOverflow(&cur_selection_str)) {
offset += draw_funcs_.DrawTextLine(x, y + offset, cur_selection_str, true);
}
}
return offset;
}
int TextMenu::DrawItems(int x, int y, int screen_width, bool long_press) const { int offset = 0;
draw_funcs_.SetColor(UIElement::MENU); // Do not draw the horizontal rule for wear devices. if (!scrollable()) {
offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4;
} for (size_t i = MenuStart(); i < MenuEnd(); ++i) { bool bold = false; if (i == selection()) { // Draw the highlight bar.
draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG);
int bar_height = char_height_ + 4;
draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height);
// Bold white text for the selected item.
draw_funcs_.SetColor(UIElement::MENU_SEL_FG);
bold = true;
}
offset += draw_funcs_.DrawTextLine(x, y + offset, TextItem(i), bold);
// Define destructor out-of-line so that GRSurface is defined for unique_ptr.
GraphicMenu::~GraphicMenu() = default;
int GraphicMenu::Select(int sel) {
CHECK_LE(graphic_items_.size(), static_cast<size_t>(std::numeric_limits<int>::max())); int count = graphic_items_.size();
// Wraps the selection at boundary if the menu is not scrollable. if (sel < 0) {
selection_ = count - 1;
} elseif (sel >= count) {
selection_ = 0;
} else {
selection_ = sel;
}
return selection_;
}
int GraphicMenu::DrawHeader(int x, int y) const {
draw_funcs_.SetColor(UIElement::HEADER);
draw_funcs_.DrawTextIcon(x, y, graphic_headers_.get()); return graphic_headers_->height;
}
int GraphicMenu::DrawItems(int x, int y, int screen_width, bool long_press) const { int offset = 0;
for (size_t i = 0; i < graphic_items_.size(); i++) { auto& item = graphic_items_[i]; if (i == selection_) {
draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG);
int bar_height = item->height + 4;
draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height);
// Bold white text for the selected item.
draw_funcs_.SetColor(UIElement::MENU_SEL_FG);
}
draw_funcs_.DrawTextIcon(x, y + offset, item.get());
offset += item->height;
for (constauto& item : graphic_items) { if (!ValidateGraphicSurface(max_width, max_height, offset, item)) { returnfalse;
}
offset += item->height;
}
returntrue;
}
bool GraphicMenu::ValidateGraphicSurface(size_t max_width, size_t max_height, int y, const GRSurface* surface) { if (!surface) {
fprintf(stderr, "Graphic surface can not be null\n"); returnfalse;
}
ScreenRecoveryUI::~ScreenRecoveryUI() {
progress_thread_stopped_ = true; if (progress_thread_.joinable()) {
progress_thread_.join();
} // No-op if gr_init() (via Init()) was not called or had failed.
gr_exit();
}
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();
}
}
int ScreenRecoveryUI::PixelsFromDp(int dp) const { return dp * density_;
}
// Here's the intended layout:
// | portrait large landscape large // ---------+------------------------------------------------- // gap | // icon | (200dp) // gap | 68dp 68dp 56dp 112dp // text | (14sp) // gap | 32dp 32dp 26dp 52dp // progress | (2dp) // gap |
// Note that "baseline" is actually the *top* of each icon (because that's how our drawing routines // work), so that's the more useful measurement for calling code. We use even top and bottom gaps.
// 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) { constauto& 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;
}
}
// 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) { constauto& 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(); if (frame_x >= 0 && frame_y >= 0 && (frame_x + frame_width) < ScreenWidth() &&
(frame_y + frame_height) < ScreenHeight())
DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
}
if (progressBarType != EMPTY) { int width = gr_get_width(progress_bar_empty_.get()); int height = gr_get_height(progress_bar_empty_.get());
int progress_x = (ScreenWidth() - width) / 2; int progress_y = GetProgressBaseline();
// Erase behind the progress bar (in case this was a progress-only update)
gr_color(0, 0, 0, 255);
DrawFill(progress_x, progress_y, width, height);
if (progressBarType == DETERMINATE) { float p = progressScopeStart + progress * progressScopeSize; int pos = static_cast<int>(p * width);
if (rtl_locale_) { // Fill the progress bar from right to left. if (pos > 0) {
DrawSurface(progress_bar_fill_.get(), width - pos, 0, pos, height,
progress_x + width - pos, progress_y);
} if (pos < width - 1) {
DrawSurface(progress_bar_empty_.get(), 0, 0, width - pos, height, progress_x, progress_y);
}
} else { // Fill the progress bar from left to right. if (pos > 0) {
DrawSurface(progress_bar_fill_.get(), 0, 0, pos, height, progress_x, progress_y);
} if (pos < width - 1) {
DrawSurface(progress_bar_empty_.get(), pos, 0, width - pos, height, progress_x + pos,
progress_y);
}
}
}
}
}
void ScreenRecoveryUI::SetColor(UIElement e) const { switch (e) { case UIElement::INFO:
gr_color(249, 194, 0, 255); break; case UIElement::HEADER:
gr_color(247, 0, 6, 255); break; case UIElement::MENU: case UIElement::MENU_SEL_BG:
gr_color(0, 106, 157, 255); break; case UIElement::MENU_SEL_BG_ACTIVE:
gr_color(0, 156, 100, 255); break; case UIElement::MENU_SEL_FG:
gr_color(255, 255, 255, 255); break; case UIElement::LOG:
gr_color(196, 196, 196, 255); break; case UIElement::TEXT_FILL:
gr_color(0, 0, 0, 160); break; default:
gr_color(255, 255, 255, 255); break;
}
}
void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector<std::string>& locales_entries,
size_t sel) {
SetLocale(locales_entries[sel]);
std::vector<std::string> text_name = { "erasing_text", "error_text", "installing_text", "installing_security_text", "no_command_text" };
std::unordered_map<std::string, std::unique_ptr<GRSurface>> surfaces; for (constauto& name : text_name) { auto text_image = LoadLocalizedBitmap(name); if (!text_image) {
Print("Failed to load %s\n", name.c_str()); return;
}
surfaces.emplace(name, std::move(text_image));
}
int text_y = margin_height_; int text_x = margin_width_; int line_spacing = gr_sys_font()->char_height; // Put some extra space between images. // Write the header and descriptive texts.
SetColor(UIElement::INFO);
std::string header = "Show background text image";
text_y += DrawTextLine(text_x, text_y, header, true);
std::string locale_selection = android::base::StringPrintf( "Current locale: %s, %zu/%zu", locales_entries[sel].c_str(), sel + 1, locales_entries.size()); // clang-format off
std::vector<std::string> instruction = {
locale_selection, "Use volume up/down to switch locales and power to exit."
}; // clang-format on
text_y += DrawWrappedTextLines(text_x, text_y, instruction);
// Iterate through the text images and display them in order for the current locale. for (constauto& p : surfaces) {
text_y += line_spacing;
SetColor(UIElement::LOG);
text_y += DrawTextLine(text_x, text_y, p.first, false);
gr_color(255, 255, 255, 255);
gr_texticon(text_x, text_y, p.second.get());
text_y += gr_get_height(p.second.get());
} // Update the whole screen.
gr_flip();
}
void ScreenRecoveryUI::CheckBackgroundTextImages() { // Load a list of locales embedded in one of the resource files.
std::vector<std::string> locales_entries = get_locales_in_png("installing_text"); if (locales_entries.empty()) {
Print("Failed to load locales from the resource files\n"); return;
}
std::string saved_locale = locale_;
size_t selected = 0;
SelectAndShowBackgroundText(locales_entries, selected);
int ScreenRecoveryUI::ScreenWidth() const { return gr_fb_width();
}
int ScreenRecoveryUI::ScreenHeight() const { return gr_fb_height();
}
void ScreenRecoveryUI::DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, int dy) const {
gr_blit(surface, sx, sy, w, h, dx, dy);
}
int ScreenRecoveryUI::DrawHorizontalRule(int y) const {
gr_fill(0, y + 4, ScreenWidth(), y + 6); return8;
}
void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const {
gr_fill(x, y, x + width, y + height);
}
void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const {
gr_fill(x, y, w, h);
}
void ScreenRecoveryUI::DrawTextIcon(int x, int y, const GRSurface* surface) const {
gr_texticon(x, y, surface);
}
int ScreenRecoveryUI::DrawTextLine(int x, int y, const std::string& line, bool bold) const {
gr_text(gr_sys_font(), x, y, line.c_str(), bold); return char_height_ + 4;
}
int ScreenRecoveryUI::DrawTextLines(int x, int y, const std::vector<std::string>& lines) const { int offset = 0; for (constauto& line : lines) {
offset += DrawTextLine(x, y + offset, line, false);
} return offset;
}
int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const { // Keep symmetrical margins based on the given offset (i.e. x).
size_t text_cols = (ScreenWidth() - x * 2) / char_width_; int offset = 0; for (constauto& line : lines) {
size_t next_start = 0; while (next_start < line.size()) {
std::string sub = line.substr(next_start, text_cols + 1); if (sub.size() <= text_cols) {
next_start += sub.size();
} else { // Line too long and must be wrapped to text_cols columns.
size_t last_space = sub.find_last_of(" \t\n"); if (last_space == std::string::npos) { // No space found, just draw as much as we can.
sub.resize(text_cols);
next_start += text_cols;
} else {
sub.resize(last_space);
next_start += last_space + 1;
}
}
offset += DrawTextLine(x, y + offset, sub, false);
}
} return offset;
}
// 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;
}
// Draws the menu and text buffer on the screen. Should only be called with updateMutex locked. void ScreenRecoveryUI::draw_menu_and_text_buffer_locked( const std::vector<std::string>& help_message) { int y = margin_height_;
if (fastbootd_logo_ && fastbootd_logo_enabled_) { // Try to get this centered on screen. auto width = gr_get_width(fastbootd_logo_.get()); auto height = gr_get_height(fastbootd_logo_.get()); auto centered_x = ScreenWidth() / 2 - width / 2;
DrawSurface(fastbootd_logo_.get(), 0, 0, width, height, centered_x, y);
y += height;
}
if (menu_) { int x = margin_width_ + kMenuIndent;
SetColor(UIElement::INFO);
for (size_t i = 0; i < title_lines_.size(); i++) {
y += DrawTextLine(x, y, title_lines_[i], i == 0);
}
y += DrawTextLines(x, y, help_message);
y += menu_->DrawHeader(x, y);
y += menu_->DrawItems(x, y, ScreenWidth(), IsLongPress());
}
// Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or // we've displayed the entire text buffer.
SetColor(UIElement::LOG); int row = text_row_;
size_t count = 0; for (int ty = ScreenHeight() - margin_height_ - char_height_; ty >= y && count < text_rows_;
ty -= char_height_, ++count) {
DrawTextLine(margin_width_, ty, text_[row], false);
--row; if (row < 0) row = text_rows_ - 1;
}
}
// Redraw everything on the screen and flip the screen (make it visible). // Should only be called with updateMutex locked. void ScreenRecoveryUI::update_screen_locked() {
draw_screen_locked();
gr_flip();
}
// Updates only the progress bar, if possible, otherwise redraws the screen. // Should only be called with updateMutex locked. void ScreenRecoveryUI::update_progress_locked() { if (show_text || !pagesIdentical) {
draw_screen_locked(); // Must redraw the whole screen
pagesIdentical = true;
} else {
draw_foreground_locked(); // Draw only the progress bar and overlays
}
gr_flip();
}
// update the installation animation, if active // skip this if we have a text overlay (too expensive to update) if ((current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) && !show_text) { if (!intro_done_) { if (current_frame_ == intro_frames_.size() - 1) {
intro_done_ = true;
current_frame_ = 0;
} else {
++current_frame_;
}
} else {
current_frame_ = (current_frame_ + 1) % loop_frames_.size();
}
redraw = true;
}
// move the progress bar forward on timed intervals, if configured int duration = progressScopeDuration; if (progressBarType == DETERMINATE && duration > 0) { double elapsed = now() - progressScopeTime; float p = 1.0 * elapsed / duration; if (p > 1.0) p = 1.0; if (p > progress) {
progress = p;
redraw = true;
}
}
if (redraw) update_progress_locked();
}
double end = now(); // minimum of 20ms delay between frames double delay = interval - (end - start); if (delay < 0.02) delay = 0.02;
usleep(static_cast<useconds_t>(delay * 1000000));
}
}
std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadBitmap(const std::string& filename) {
GRSurface* surface; if (auto result = res_create_display_surface(filename.c_str(), &surface); result < 0) {
LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")"; return nullptr;
} return std::unique_ptr<GRSurface>(surface);
}
std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadLocalizedBitmap(const std::string& filename) {
GRSurface* surface; auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface); if (result == 0) { return std::unique_ptr<GRSurface>(surface);
} // TODO(xunchang) create a error code enum to refine the retry condition.
LOG(WARNING) << "Failed to load bitmap " << filename << " for locale " << locale_ << " (error "
<< result << "). Falling back to use default locale.";
result = res_create_localized_alpha_surface(filename.c_str(), DEFAULT_LOCALE, &surface); if (result == 0) { return std::unique_ptr<GRSurface>(surface);
}
LOG(ERROR) << "Failed to load bitmap " << filename << " for locale " << DEFAULT_LOCALE
<< " (error " << result << ")"; return nullptr;
}
staticchar** Alloc2d(size_t rows, size_t cols) { char** result = newchar*[rows]; for (size_t i = 0; i < rows; ++i) {
result[i] = newchar[cols];
memset(result[i], 0, cols);
} return result;
}
// Choose the right background string to display during update. void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) { if (security_update) {
installing_text_ = LoadLocalizedBitmap("installing_security_text");
} else {
installing_text_ = LoadLocalizedBitmap("installing_text");
}
Redraw();
}
if (!InitGraphics()) { returnfalse;
}
is_graphics_available = true;
if (!InitTextParams()) { returnfalse;
}
// Are we portrait or landscape?
layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT; // Are we the large variant of our base layout? if (gr_fb_height() > PixelsFromDp(800)) ++layout_;
if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
fastbootd_logo_ = LoadBitmap("fastbootd");
}
// Background text for "installing_update" could be "installing update" or // "installing security update". It will be set after Init() according to the commands in BCB.
installing_text_.reset();
LoadWipeDataMenuText();
LoadAnimation();
// Keep the progress bar updated, even when the process is otherwise busy.
progress_thread_ = std::thread(&ScreenRecoveryUI::ProgressThreadLoop, this);
// set the callback for hall sensor event
(void)ev_sync_sw_state([this](auto&& a, auto&& b) { returnthis->SetSwCallback(a, b);});
fprintf(stderr, "Failed to create text menu, text_rows %zu, text_cols %zu.\n", text_rows_,
text_cols_); return nullptr;
}
int ScreenRecoveryUI::SelectMenu(int sel) {
std::lock_guard<std::mutex> lg(updateMutex); if (menu_) { int old_sel = menu_->selection();
sel = menu_->Select(sel);
size_t ScreenRecoveryUI::ShowMenu(std::unique_ptr<Menu>&& menu, bool menu_only, const std::function<int(int, bool)>& key_handler) { // 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()) returnstatic_cast<size_t>(KeyError::INTERRUPTED);
CHECK(menu != nullptr);
// Starts and displays the menu
menu_ = std::move(menu);
Redraw();
int selected = menu_->selection(); int chosen_item = -1; while (chosen_item < 0) { int key = WaitKey(); if (key == static_cast<int>(KeyError::INTERRUPTED)) { // WaitKey() was interrupted. returnstatic_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.";
menu_.reset();
Redraw(); returnstatic_cast<size_t>(KeyError::TIMED_OUT);
}
}
bool visible = IsTextVisible(); int action = key_handler(key, visible); if (action < 0) { switch (action) { case Device::kHighlightUp:
selected = SelectMenu(--selected); break; case Device::kHighlightDown:
selected = SelectMenu(++selected); break; case Device::kInvokeItem:
chosen_item = selected; break; case Device::kNoAction: break;
}
} elseif (!menu_only) {
chosen_item = action;
}
}
menu_.reset();
Redraw();
return chosen_item;
}
size_t ScreenRecoveryUI::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) { auto menu = CreateMenu(headers, items, initial_selection); if (menu == nullptr) { return initial_selection;
}
void ScreenRecoveryUI::KeyLongPress(int) { // Redraw so that if we're in the menu, the highlight // will change color to indicate a successful long press.
Redraw();
}
if (!new_locale.empty()) {
size_t separator = new_locale.find('-'); // lang has the language prefix prior to the separator, or full string if none exists.
std::string lang = new_locale.substr(0, separator);
// A bit cheesy: keep an explicit list of supported RTL languages. if (lang == "ar" || // Arabic
lang == "fa" || // Persian (Farsi)
lang == "he" || // Hebrew (new language code)
lang == "iw" || // Hebrew (old language code)
lang == "ur") { // Urdu
rtl_locale_ = true;
}
}
}
int ScreenRecoveryUI::SetSwCallback(int code, int value) { if (!is_graphics_available) { return -1; } if (code > SW_MAX) { return -1; } if (code != SW_LID) { return0; }
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.