diff options
Diffstat (limited to '')
-rw-r--r-- | wear_ui.cpp | 614 |
1 files changed, 614 insertions, 0 deletions
diff --git a/wear_ui.cpp b/wear_ui.cpp new file mode 100644 index 000000000..b437fd0ae --- /dev/null +++ b/wear_ui.cpp @@ -0,0 +1,614 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include <vector> + +#include "common.h" +#include "device.h" +#include "wear_ui.h" +#include "cutils/properties.h" +#include "android-base/strings.h" +#include "android-base/stringprintf.h" + +// There's only (at most) one of these objects, and global callbacks +// (for pthread_create, and the input event system) need to find it, +// so use a global variable. +static WearRecoveryUI* self = NULL; + +// Return the current time as a double (including fractions of a second). +static double now() { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +WearRecoveryUI::WearRecoveryUI() : + progress_bar_height(3), + progress_bar_width(200), + progress_bar_y(259), + outer_height(0), + outer_width(0), + menu_unusable_rows(0), + intro_frames(22), + loop_frames(60), + animation_fps(30), + currentIcon(NONE), + intro_done(false), + current_frame(0), + progressBarType(EMPTY), + progressScopeStart(0), + progressScopeSize(0), + progress(0), + text_cols(0), + text_rows(0), + text_col(0), + text_row(0), + text_top(0), + show_text(false), + show_text_ever(false), + show_menu(false), + menu_items(0), + menu_sel(0) { + + for (size_t i = 0; i < 5; i++) + backgroundIcon[i] = NULL; + + self = this; +} + +// Draw background frame on the screen. Does not flip pages. +// Should only be called with updateMutex locked. +void WearRecoveryUI::draw_background_locked(Icon icon) +{ + gr_color(0, 0, 0, 255); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + if (icon) { + GRSurface* surface; + if (icon == INSTALLING_UPDATE || icon == ERASING) { + if (!intro_done) { + surface = introFrames[current_frame]; + } else { + surface = loopFrames[current_frame]; + } + } + else { + surface = backgroundIcon[icon]; + } + + int width = gr_get_width(surface); + int height = gr_get_height(surface); + + int x = (gr_fb_width() - width) / 2; + int y = (gr_fb_height() - height) / 2; + + gr_blit(surface, 0, 0, width, height, x, y); + } +} + +// Draw the progress bar (if any) on the screen. Does not flip pages. +// Should only be called with updateMutex locked. +void WearRecoveryUI::draw_progress_locked() +{ + if (currentIcon == ERROR) return; + if (progressBarType != DETERMINATE) return; + + int width = progress_bar_width; + int height = progress_bar_height; + int dx = (gr_fb_width() - width)/2; + int dy = progress_bar_y; + + float p = progressScopeStart + progress * progressScopeSize; + int pos = (int) (p * width); + + gr_color(0x43, 0x43, 0x43, 0xff); + gr_fill(dx, dy, dx + width, dy + height); + + if (pos > 0) { + gr_color(0x02, 0xa8, 0xf3, 255); + if (rtl_locale) { + // Fill the progress bar from right to left. + gr_fill(dx + width - pos, dy, dx + width, dy + height); + } else { + // Fill the progress bar from left to right. + gr_fill(dx, dy, dx + pos, dy + height); + } + } +} + +static const char* HEADERS[] = { + "Swipe up/down to move.", + "Swipe left/right to select.", + "", + NULL +}; + +void WearRecoveryUI::draw_screen_locked() +{ + draw_background_locked(currentIcon); + draw_progress_locked(); + char cur_selection_str[50]; + + if (show_text) { + SetColor(TEXT_FILL); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + int y = outer_height; + int x = outer_width; + if (show_menu) { + char recovery_fingerprint[PROPERTY_VALUE_MAX]; + property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, ""); + SetColor(HEADER); + DrawTextLine(x + 4, &y, "Android Recovery", true); + for (auto& chunk: android::base::Split(recovery_fingerprint, ":")) { + DrawTextLine(x +4, &y, chunk.c_str(), false); + } + + // This is actually the help strings. + DrawTextLines(x + 4, &y, HEADERS); + SetColor(HEADER); + DrawTextLines(x + 4, &y, menu_headers_); + + // Show the current menu item number in relation to total number if + // items don't fit on the screen. + if (menu_items > menu_end - menu_start) { + sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items); + gr_text(x+4, y, cur_selection_str, 1); + y += char_height_+4; + } + + // Menu begins here + SetColor(MENU); + + for (int i = menu_start; i < menu_end; ++i) { + + if (i == menu_sel) { + // draw the highlight bar + SetColor(MENU_SEL_BG); + gr_fill(x, y-2, gr_fb_width()-x, y+char_height_+2); + // white text of selected item + SetColor(MENU_SEL_FG); + if (menu[i][0]) gr_text(x+4, y, menu[i], 1); + SetColor(MENU); + } else { + if (menu[i][0]) gr_text(x+4, y, menu[i], 0); + } + y += char_height_+4; + } + SetColor(MENU); + y += 4; + gr_fill(0, y, gr_fb_width(), y+2); + y += 4; + } + + SetColor(LOG); + + // 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. + int ty; + int row = (text_top+text_rows-1) % text_rows; + size_t count = 0; + for (int ty = gr_fb_height() - char_height_ - outer_height; + ty > y+2 && count < text_rows; + ty -= char_height_, ++count) { + gr_text(x+4, ty, text[row], 0); + --row; + if (row < 0) row = text_rows-1; + } + } +} + +void WearRecoveryUI::update_screen_locked() +{ + draw_screen_locked(); + gr_flip(); +} + +// Keeps the progress bar updated, even when the process is otherwise busy. +void* WearRecoveryUI::progress_thread(void *cookie) { + self->progress_loop(); + return NULL; +} + +void WearRecoveryUI::progress_loop() { + double interval = 1.0 / animation_fps; + for (;;) { + double start = now(); + pthread_mutex_lock(&updateMutex); + int redraw = 0; + + if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) + && !show_text) { + if (!intro_done) { + if (current_frame >= intro_frames - 1) { + intro_done = true; + current_frame = 0; + } else { + current_frame++; + } + } else { + current_frame = (current_frame + 1) % loop_frames; + } + redraw = 1; + } + + // 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 = 1; + } + } + + if (redraw) + update_screen_locked(); + + pthread_mutex_unlock(&updateMutex); + double end = now(); + // minimum of 20ms delay between frames + double delay = interval - (end-start); + if (delay < 0.02) delay = 0.02; + usleep((long)(delay * 1000000)); + } +} + +void WearRecoveryUI::Init() +{ + gr_init(); + + gr_font_size(&char_width_, &char_height_); + + text_col = text_row = 0; + text_rows = (gr_fb_height()) / char_height_; + visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height_; + if (text_rows > kMaxRows) text_rows = kMaxRows; + text_top = 1; + + text_cols = (gr_fb_width() - (outer_width * 2)) / char_width_; + if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1; + + LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]); + backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; + LoadBitmap("icon_error", &backgroundIcon[ERROR]); + backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; + + introFrames = (GRSurface**)malloc(intro_frames * sizeof(GRSurface*)); + for (int i = 0; i < intro_frames; ++i) { + char filename[40]; + sprintf(filename, "intro%02d", i); + LoadBitmap(filename, introFrames + i); + } + + loopFrames = (GRSurface**)malloc(loop_frames * sizeof(GRSurface*)); + for (int i = 0; i < loop_frames; ++i) { + char filename[40]; + sprintf(filename, "loop%02d", i); + LoadBitmap(filename, loopFrames + i); + } + + pthread_create(&progress_t, NULL, progress_thread, NULL); + RecoveryUI::Init(); +} + +void WearRecoveryUI::SetBackground(Icon icon) +{ + pthread_mutex_lock(&updateMutex); + currentIcon = icon; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::SetProgressType(ProgressType type) +{ + pthread_mutex_lock(&updateMutex); + if (progressBarType != type) { + progressBarType = type; + } + progressScopeStart = 0; + progressScopeSize = 0; + progress = 0; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::ShowProgress(float portion, float seconds) +{ + pthread_mutex_lock(&updateMutex); + progressBarType = DETERMINATE; + progressScopeStart += progressScopeSize; + progressScopeSize = portion; + progressScopeTime = now(); + progressScopeDuration = seconds; + progress = 0; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::SetProgress(float fraction) +{ + pthread_mutex_lock(&updateMutex); + if (fraction < 0.0) fraction = 0.0; + if (fraction > 1.0) fraction = 1.0; + if (progressBarType == DETERMINATE && fraction > progress) { + // Skip updates that aren't visibly different. + int width = progress_bar_width; + float scale = width * progressScopeSize; + if ((int) (progress * scale) != (int) (fraction * scale)) { + progress = fraction; + update_screen_locked(); + } + } + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::SetStage(int current, int max) +{ +} + +void WearRecoveryUI::Print(const char *fmt, ...) +{ + char buf[256]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, 256, fmt, ap); + va_end(ap); + + fputs(buf, stdout); + + // This can get called before ui_init(), so be careful. + pthread_mutex_lock(&updateMutex); + if (text_rows > 0 && text_cols > 0) { + char *ptr; + for (ptr = buf; *ptr != '\0'; ++ptr) { + if (*ptr == '\n' || text_col >= text_cols) { + text[text_row][text_col] = '\0'; + text_col = 0; + text_row = (text_row + 1) % text_rows; + if (text_row == text_top) text_top = (text_top + 1) % text_rows; + } + if (*ptr != '\n') text[text_row][text_col++] = *ptr; + } + text[text_row][text_col] = '\0'; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::StartMenu(const char* const * headers, const char* const * items, + int initial_selection) { + pthread_mutex_lock(&updateMutex); + if (text_rows > 0 && text_cols > 0) { + menu_headers_ = headers; + size_t i = 0; + // "i < text_rows" is removed from the loop termination condition, + // which is different from the one in ScreenRecoveryUI::StartMenu(). + // Because WearRecoveryUI supports scrollable menu, it's fine to have + // more entries than text_rows. The menu may be truncated otherwise. + // Bug: 23752519 + for (; items[i] != nullptr; i++) { + strncpy(menu[i], items[i], text_cols - 1); + menu[i][text_cols - 1] = '\0'; + } + menu_items = i; + show_menu = 1; + menu_sel = initial_selection; + menu_start = 0; + menu_end = visible_text_rows - 1 - menu_unusable_rows; + if (menu_items <= menu_end) + menu_end = menu_items; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +int WearRecoveryUI::SelectMenu(int sel) { + int old_sel; + pthread_mutex_lock(&updateMutex); + if (show_menu > 0) { + old_sel = menu_sel; + menu_sel = sel; + if (menu_sel < 0) menu_sel = 0; + if (menu_sel >= menu_items) menu_sel = menu_items-1; + if (menu_sel < menu_start) { + menu_start--; + menu_end--; + } else if (menu_sel >= menu_end && menu_sel < menu_items) { + menu_end++; + menu_start++; + } + sel = menu_sel; + if (menu_sel != old_sel) update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); + return sel; +} + +void WearRecoveryUI::EndMenu() { + int i; + pthread_mutex_lock(&updateMutex); + if (show_menu > 0 && text_rows > 0 && text_cols > 0) { + show_menu = 0; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +bool WearRecoveryUI::IsTextVisible() +{ + pthread_mutex_lock(&updateMutex); + int visible = show_text; + pthread_mutex_unlock(&updateMutex); + return visible; +} + +bool WearRecoveryUI::WasTextEverVisible() +{ + pthread_mutex_lock(&updateMutex); + int ever_visible = show_text_ever; + pthread_mutex_unlock(&updateMutex); + return ever_visible; +} + +void WearRecoveryUI::ShowText(bool visible) +{ + pthread_mutex_lock(&updateMutex); + // Don't show text during ota install or factory reset + if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { + pthread_mutex_unlock(&updateMutex); + return; + } + show_text = visible; + if (show_text) show_text_ever = 1; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::Redraw() +{ + pthread_mutex_lock(&updateMutex); + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::ShowFile(FILE* fp) { + std::vector<long> offsets; + offsets.push_back(ftell(fp)); + ClearText(); + + struct stat sb; + fstat(fileno(fp), &sb); + + bool show_prompt = false; + while (true) { + if (show_prompt) { + Print("--(%d%% of %d bytes)--", + static_cast<int>(100 * (double(ftell(fp)) / double(sb.st_size))), + static_cast<int>(sb.st_size)); + Redraw(); + while (show_prompt) { + show_prompt = false; + int key = WaitKey(); + if (key == KEY_POWER || key == KEY_ENTER) { + return; + } else if (key == KEY_UP || key == KEY_VOLUMEUP) { + if (offsets.size() <= 1) { + show_prompt = true; + } else { + offsets.pop_back(); + fseek(fp, offsets.back(), SEEK_SET); + } + } else { + if (feof(fp)) { + return; + } + offsets.push_back(ftell(fp)); + } + } + ClearText(); + } + + int ch = getc(fp); + if (ch == EOF) { + text_row = text_top = text_rows - 2; + show_prompt = true; + } else { + PutChar(ch); + if (text_col == 0 && text_row >= text_rows - 2) { + text_top = text_row; + show_prompt = true; + } + } + } +} + +void WearRecoveryUI::PutChar(char ch) { + pthread_mutex_lock(&updateMutex); + if (ch != '\n') text[text_row][text_col++] = ch; + if (ch == '\n' || text_col >= text_cols) { + text_col = 0; + ++text_row; + } + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::ShowFile(const char* filename) { + FILE* fp = fopen_path(filename, "re"); + if (fp == nullptr) { + Print(" Unable to open %s: %s\n", filename, strerror(errno)); + return; + } + ShowFile(fp); + fclose(fp); +} + +void WearRecoveryUI::ClearText() { + pthread_mutex_lock(&updateMutex); + text_col = 0; + text_row = 0; + text_top = 1; + for (size_t i = 0; i < text_rows; ++i) { + memset(text[i], 0, text_cols + 1); + } + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + PrintV(fmt, false, ap); + va_end(ap); +} + +void WearRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) { + std::string str; + android::base::StringAppendV(&str, fmt, ap); + + if (copy_to_stdout) { + fputs(str.c_str(), stdout); + } + + pthread_mutex_lock(&updateMutex); + if (text_rows > 0 && text_cols > 0) { + for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) { + if (*ptr == '\n' || text_col >= text_cols) { + text[text_row][text_col] = '\0'; + text_col = 0; + text_row = (text_row + 1) % text_rows; + if (text_row == text_top) text_top = (text_top + 1) % text_rows; + } + if (*ptr != '\n') text[text_row][text_col++] = *ptr; + } + text[text_row][text_col] = '\0'; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} |