blob: bb05d51c5326e2cef79387492e06e075b4e6837a [file] [log] [blame]
Doug Zongker211aebc2011-10-28 15:13:10 -07001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Tianjie Xu8f397302018-08-20 13:40:47 -070017#include "recovery_ui/screen_ui.h"
Tao Baoefb49ad2017-01-31 23:03:10 -080018
Elliott Hughes498cda62016-04-14 16:49:04 -070019#include <dirent.h>
Doug Zongker211aebc2011-10-28 15:13:10 -070020#include <errno.h>
21#include <fcntl.h>
Doug Zongker211aebc2011-10-28 15:13:10 -070022#include <stdarg.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <sys/stat.h>
27#include <sys/time.h>
28#include <sys/types.h>
29#include <time.h>
30#include <unistd.h>
31
Tao Bao1fe1afe2018-05-01 15:56:05 -070032#include <algorithm>
Tao Bao26ea9592018-05-09 16:32:02 -070033#include <chrono>
Tianjie Xu29d55752017-09-20 17:53:46 -070034#include <memory>
Tao Bao736d59c2017-01-03 10:15:33 -080035#include <string>
Tao Bao26ea9592018-05-09 16:32:02 -070036#include <thread>
Tianjie Xu29d55752017-09-20 17:53:46 -070037#include <unordered_map>
Elliott Hughes95fc63e2015-04-10 19:12:01 -070038#include <vector>
39
Mark Salyzyn30017e72020-05-13 12:39:12 -070040#include <android-base/chrono_utils.h>
Tianjie Xuc21edd42016-08-05 18:00:04 -070041#include <android-base/logging.h>
Elliott Hughescb220402016-09-23 15:30:55 -070042#include <android-base/properties.h>
Elliott Hughes4b166f02015-12-04 15:30:20 -080043#include <android-base/stringprintf.h>
Tao Baocb5524c2017-09-08 21:25:32 -070044#include <android-base/strings.h>
Tao Baob6918c72015-05-19 17:02:16 -070045
Tao Bao6cd81682018-05-03 21:53:11 -070046#include "minui/minui.h"
47#include "otautil/paths.h"
Tianjie Xu8f397302018-08-20 13:40:47 -070048#include "recovery_ui/device.h"
49#include "recovery_ui/ui.h"
Doug Zongker211aebc2011-10-28 15:13:10 -070050
Doug Zongker211aebc2011-10-28 15:13:10 -070051// Return the current time as a double (including fractions of a second).
52static double now() {
Tao Bao5d2e3bd2017-06-23 22:23:50 -070053 struct timeval tv;
54 gettimeofday(&tv, nullptr);
55 return tv.tv_sec + tv.tv_usec / 1000000.0;
Doug Zongker211aebc2011-10-28 15:13:10 -070056}
57
Tianjie Xu66dbf632018-10-11 16:54:50 -070058Menu::Menu(size_t initial_selection, const DrawInterface& draw_func)
59 : selection_(initial_selection), draw_funcs_(draw_func) {}
60
Alessandro Astonee8754752020-03-09 23:17:50 +010061int Menu::selection() const {
Tianjie Xu66dbf632018-10-11 16:54:50 -070062 return selection_;
63}
64
Alessandro Astone3525c4d2020-06-25 19:54:18 +020065TextMenu::TextMenu(bool wrappable, size_t max_length,
Tianjie Xu66dbf632018-10-11 16:54:50 -070066 const std::vector<std::string>& headers, const std::vector<std::string>& items,
67 size_t initial_selection, int char_height, const DrawInterface& draw_funcs)
68 : Menu(initial_selection, draw_funcs),
Alessandro Astonee8754752020-03-09 23:17:50 +010069 wrappable_(wrappable),
Alessandro Astone3525c4d2020-06-25 19:54:18 +020070 calibrated_height_(false),
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070071 max_item_length_(max_length),
Tao Baoe02a5b22018-05-02 15:46:11 -070072 text_headers_(headers),
Tianjie Xu66dbf632018-10-11 16:54:50 -070073 char_height_(char_height) {
Tao Baoe02a5b22018-05-02 15:46:11 -070074
Alessandro Astonee8754752020-03-09 23:17:50 +010075 size_t items_count = items.size();
Tao Bao1fe1afe2018-05-01 15:56:05 -070076 for (size_t i = 0; i < items_count; ++i) {
77 text_items_.emplace_back(items[i].substr(0, max_item_length_));
Tao Baoe02a5b22018-05-02 15:46:11 -070078 }
79
80 CHECK(!text_items_.empty());
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070081}
82
Tianjie Xu66dbf632018-10-11 16:54:50 -070083const std::vector<std::string>& TextMenu::text_headers() const {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070084 return text_headers_;
85}
86
Tianjie Xu66dbf632018-10-11 16:54:50 -070087std::string TextMenu::TextItem(size_t index) const {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070088 CHECK_LT(index, text_items_.size());
89
90 return text_items_[index];
91}
92
Tianjie Xu66dbf632018-10-11 16:54:50 -070093size_t TextMenu::MenuStart() const {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070094 return menu_start_;
95}
96
Tianjie Xu66dbf632018-10-11 16:54:50 -070097size_t TextMenu::MenuEnd() const {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070098 return std::min(ItemsCount(), menu_start_ + max_display_items_);
99}
100
Tianjie Xu66dbf632018-10-11 16:54:50 -0700101size_t TextMenu::ItemsCount() const {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700102 return text_items_.size();
103}
104
Tianjie Xu66dbf632018-10-11 16:54:50 -0700105bool TextMenu::ItemsOverflow(std::string* cur_selection_str) const {
Alessandro Astonee8754752020-03-09 23:17:50 +0100106 if (ItemsCount() <= max_display_items_) {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700107 return false;
108 }
109
110 *cur_selection_str =
Alessandro Astonee8754752020-03-09 23:17:50 +0100111 android::base::StringPrintf("Current item: %d/%zu", selection_ + 1, ItemsCount());
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700112 return true;
113}
114
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700115// TODO(xunchang) modify the function parameters to button up & down.
Tianjie Xu66dbf632018-10-11 16:54:50 -0700116int TextMenu::Select(int sel) {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700117 CHECK_LE(ItemsCount(), static_cast<size_t>(std::numeric_limits<int>::max()));
118 int count = ItemsCount();
119
Alessandro Astonee8754752020-03-09 23:17:50 +0100120 int min = IsMain() ? 0 : -1; // -1 is back arrow
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700121
Alessandro Astonee8754752020-03-09 23:17:50 +0100122 if (sel < min) {
123 selection_ = wrappable() ? count - 1 : min;
124 } else if (sel >= count) {
125 selection_ = wrappable() ? min : count - 1;
126 } else {
127 selection_ = sel;
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700128 }
129
Alessandro Astonee8754752020-03-09 23:17:50 +0100130 if (selection_ >= 0) {
131 if (selection_ < menu_start_) {
132 menu_start_ = selection_;
133 } else if (static_cast<size_t>(selection_) >= MenuEnd()) {
134 menu_start_ = selection_ - max_display_items_ + 1;
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700135 }
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700136 }
137
138 return selection_;
139}
140
Tom Marshall53176c72020-03-10 20:17:49 +0100141int TextMenu::SelectVisible(int relative_sel) {
142 int sel = relative_sel;
143 if (menu_start_ > 0) {
144 sel += menu_start_;
145 }
146
147 return Select(sel);
148}
149
150int TextMenu::Scroll(int updown) {
151 if ((updown > 0 && menu_start_ + max_display_items_ < ItemsCount()) ||
152 (updown < 0 && menu_start_ > 0)) {
153 menu_start_ += updown;
154
155 /* We can receive a kInvokeItem event from a different source than touch,
156 like from Power button. For this reason, selection should not get out of
157 the screen. Constrain it to the first or last visible item of the list */
158 if (selection_ < menu_start_) {
159 selection_ = menu_start_;
160 } else if (selection_ >= menu_start_ + max_display_items_) {
161 selection_ = menu_start_ + max_display_items_ - 1;
162 }
163 }
164 return selection_;
165}
166
Tianjie Xu66dbf632018-10-11 16:54:50 -0700167int TextMenu::DrawHeader(int x, int y) const {
168 int offset = 0;
169
170 draw_funcs_.SetColor(UIElement::HEADER);
Alessandro Astonee8754752020-03-09 23:17:50 +0100171 offset += draw_funcs_.DrawWrappedTextLines(x, y + offset, text_headers());
Tianjie Xu66dbf632018-10-11 16:54:50 -0700172
173 return offset;
174}
175
176int TextMenu::DrawItems(int x, int y, int screen_width, bool long_press) const {
177 int offset = 0;
Alessandro Astonee8754752020-03-09 23:17:50 +0100178 int padding = draw_funcs_.MenuItemPadding();
Tianjie Xu66dbf632018-10-11 16:54:50 -0700179
180 draw_funcs_.SetColor(UIElement::MENU);
Alessandro Astonee8754752020-03-09 23:17:50 +0100181 offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4;
182
183 int item_container_offset = offset; // store it for drawing scrollbar on most top
184
Tianjie Xu66dbf632018-10-11 16:54:50 -0700185 for (size_t i = MenuStart(); i < MenuEnd(); ++i) {
Tianjie Xu66dbf632018-10-11 16:54:50 -0700186 if (i == selection()) {
187 // Draw the highlight bar.
188 draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG);
189
Alessandro Astonee8754752020-03-09 23:17:50 +0100190 int bar_height = padding + char_height_ + padding;
191 draw_funcs_.DrawHighlightBar(0, y + offset, screen_width, bar_height);
Tianjie Xu66dbf632018-10-11 16:54:50 -0700192
Jesse Chanf8084632020-04-28 14:49:13 +0000193 // Colored text for the selected item.
Tianjie Xu66dbf632018-10-11 16:54:50 -0700194 draw_funcs_.SetColor(UIElement::MENU_SEL_FG);
Tianjie Xu66dbf632018-10-11 16:54:50 -0700195 }
Jesse Chanf8084632020-04-28 14:49:13 +0000196 offset += draw_funcs_.DrawTextLine(x, y + offset, TextItem(i), false /* bold */);
Tianjie Xu66dbf632018-10-11 16:54:50 -0700197
198 draw_funcs_.SetColor(UIElement::MENU);
199 }
200 offset += draw_funcs_.DrawHorizontalRule(y + offset);
201
Alessandro Astonee8754752020-03-09 23:17:50 +0100202 std::string unused;
203 if (ItemsOverflow(&unused)) {
204 int container_height = max_display_items_ * (2 * padding + char_height_);
205 int bar_height = container_height / (text_items_.size() - max_display_items_ + 1);
206 int start_y = y + item_container_offset + bar_height * menu_start_;
207 draw_funcs_.SetColor(UIElement::SCROLLBAR);
208 draw_funcs_.DrawScrollBar(start_y, bar_height);
209 }
210
Tianjie Xu66dbf632018-10-11 16:54:50 -0700211 return offset;
212}
213
Tao Baoda409fb2018-10-21 23:36:26 -0700214GraphicMenu::GraphicMenu(const GRSurface* graphic_headers,
215 const std::vector<const GRSurface*>& graphic_items,
Tianjie Xub99e6062018-10-16 15:13:09 -0700216 size_t initial_selection, const DrawInterface& draw_funcs)
Tao Baoda409fb2018-10-21 23:36:26 -0700217 : Menu(initial_selection, draw_funcs) {
218 graphic_headers_ = graphic_headers->Clone();
219 graphic_items_.reserve(graphic_items.size());
220 for (const auto& item : graphic_items) {
221 graphic_items_.emplace_back(item->Clone());
222 }
223}
Tianjie Xu66dbf632018-10-11 16:54:50 -0700224
225int GraphicMenu::Select(int sel) {
226 CHECK_LE(graphic_items_.size(), static_cast<size_t>(std::numeric_limits<int>::max()));
227 int count = graphic_items_.size();
228
229 // Wraps the selection at boundary if the menu is not scrollable.
230 if (sel < 0) {
231 selection_ = count - 1;
232 } else if (sel >= count) {
233 selection_ = 0;
234 } else {
235 selection_ = sel;
236 }
237
238 return selection_;
239}
240
241int GraphicMenu::DrawHeader(int x, int y) const {
Tianjie Xub99e6062018-10-16 15:13:09 -0700242 draw_funcs_.SetColor(UIElement::HEADER);
Tao Baoda409fb2018-10-21 23:36:26 -0700243 draw_funcs_.DrawTextIcon(x, y, graphic_headers_.get());
Tianjie Xu66dbf632018-10-11 16:54:50 -0700244 return graphic_headers_->height;
245}
246
247int GraphicMenu::DrawItems(int x, int y, int screen_width, bool long_press) const {
248 int offset = 0;
249
250 draw_funcs_.SetColor(UIElement::MENU);
251 offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4;
252
253 for (size_t i = 0; i < graphic_items_.size(); i++) {
254 auto& item = graphic_items_[i];
255 if (i == selection_) {
256 draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG);
257
258 int bar_height = item->height + 4;
259 draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height);
260
261 // Bold white text for the selected item.
262 draw_funcs_.SetColor(UIElement::MENU_SEL_FG);
263 }
Tao Baoda409fb2018-10-21 23:36:26 -0700264 draw_funcs_.DrawTextIcon(x, y + offset, item.get());
Tianjie Xu66dbf632018-10-11 16:54:50 -0700265 offset += item->height;
266
267 draw_funcs_.SetColor(UIElement::MENU);
268 }
xunchangc7dbc732018-12-20 11:31:18 -0800269 offset += draw_funcs_.DrawHorizontalRule(y + offset);
Tianjie Xu66dbf632018-10-11 16:54:50 -0700270
271 return offset;
272}
273
Tom Marshall53176c72020-03-10 20:17:49 +0100274size_t GraphicMenu::ItemsCount() const {
275 return graphic_items_.size();
276}
277
Tao Baoda409fb2018-10-21 23:36:26 -0700278bool GraphicMenu::Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers,
279 const std::vector<const GRSurface*>& graphic_items) {
Tianjie Xu66dbf632018-10-11 16:54:50 -0700280 int offset = 0;
Tianjie Xub99e6062018-10-16 15:13:09 -0700281 if (!ValidateGraphicSurface(max_width, max_height, offset, graphic_headers)) {
Tianjie Xu66dbf632018-10-11 16:54:50 -0700282 return false;
283 }
Tianjie Xub99e6062018-10-16 15:13:09 -0700284 offset += graphic_headers->height;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700285
Tianjie Xub99e6062018-10-16 15:13:09 -0700286 for (const auto& item : graphic_items) {
287 if (!ValidateGraphicSurface(max_width, max_height, offset, item)) {
Tianjie Xu66dbf632018-10-11 16:54:50 -0700288 return false;
289 }
290 offset += item->height;
291 }
292
293 return true;
294}
295
Tianjie Xub99e6062018-10-16 15:13:09 -0700296bool GraphicMenu::ValidateGraphicSurface(size_t max_width, size_t max_height, int y,
297 const GRSurface* surface) {
Tianjie Xu66dbf632018-10-11 16:54:50 -0700298 if (!surface) {
Tao Baoa00b4492019-01-16 09:29:47 -0800299 fprintf(stderr, "Graphic surface can not be null\n");
Tianjie Xu66dbf632018-10-11 16:54:50 -0700300 return false;
301 }
302
303 if (surface->pixel_bytes != 1 || surface->width != surface->row_bytes) {
Tao Baoa00b4492019-01-16 09:29:47 -0800304 fprintf(stderr, "Invalid graphic surface, pixel bytes: %zu, width: %zu row_bytes: %zu\n",
Tianjie Xu66dbf632018-10-11 16:54:50 -0700305 surface->pixel_bytes, surface->width, surface->row_bytes);
306 return false;
307 }
308
Tianjie Xub99e6062018-10-16 15:13:09 -0700309 if (surface->width > max_width || surface->height > max_height - y) {
Tianjie Xu66dbf632018-10-11 16:54:50 -0700310 fprintf(stderr,
Tao Baodd789822018-11-26 16:28:07 -0800311 "Graphic surface doesn't fit into the screen. width: %zu, height: %zu, max_width: %zu,"
Tianjie Xu66dbf632018-10-11 16:54:50 -0700312 " max_height: %zu, vertical offset: %d\n",
Tianjie Xub99e6062018-10-16 15:13:09 -0700313 surface->width, surface->height, max_width, max_height, y);
Tianjie Xu66dbf632018-10-11 16:54:50 -0700314 return false;
315 }
316
317 return true;
318}
319
Alessandro Astonee8754752020-03-09 23:17:50 +0100320MenuDrawFunctions::MenuDrawFunctions(const DrawInterface& wrappee)
321 : wrappee_(wrappee) {
322}
323
324int MenuDrawFunctions::DrawTextLine(int x, int y, const std::string& line, bool bold) const {
325 gr_text(gr_menu_font(), x, y + MenuItemPadding(), line.c_str(), bold);
326 return 2 * MenuItemPadding() + MenuCharHeight();
327}
328
329int MenuDrawFunctions::DrawTextLines(int x, int y, const std::vector<std::string>& lines) const {
330 int offset = 0;
331 for (const auto& line : lines) {
332 offset += DrawTextLine(x, y + offset, line, false);
333 }
334 return offset;
335}
336
337int MenuDrawFunctions::DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const {
Alessandro Astone049a8712020-10-01 16:45:53 +0200338 const int padding = MenuItemPadding() / 2;
339
Alessandro Astonee8754752020-03-09 23:17:50 +0100340 // Keep symmetrical margins based on the given offset (i.e. x).
341 size_t text_cols = (gr_fb_width() - x * 2) / MenuCharWidth();
342 int offset = 0;
343 for (const auto& line : lines) {
344 size_t next_start = 0;
345 while (next_start < line.size()) {
346 std::string sub = line.substr(next_start, text_cols + 1);
347 if (sub.size() <= text_cols) {
348 next_start += sub.size();
349 } else {
350 // Line too long and must be wrapped to text_cols columns.
351 size_t last_space = sub.find_last_of(" \t\n");
352 if (last_space == std::string::npos) {
353 // No space found, just draw as much as we can.
354 sub.resize(text_cols);
355 next_start += text_cols;
356 } else {
357 sub.resize(last_space);
358 next_start += last_space + 1;
359 }
360 }
Alessandro Astone049a8712020-10-01 16:45:53 +0200361 offset += DrawTextLine(x, y + offset, sub, false) - (2 * MenuItemPadding() - padding);
Alessandro Astonee8754752020-03-09 23:17:50 +0100362 }
363 }
Alessandro Astone049a8712020-10-01 16:45:53 +0200364 if (!lines.empty()) {
365 offset += 2 * MenuItemPadding() - padding;
366 }
Alessandro Astonee8754752020-03-09 23:17:50 +0100367 return offset;
368}
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700369
Tao Bao0bc88de2018-07-31 14:53:16 -0700370constexpr int kDefaultMarginHeight = 0;
371constexpr int kDefaultMarginWidth = 0;
372constexpr int kDefaultAnimationFps = 30;
373
Alessandro Astonee8754752020-03-09 23:17:50 +0100374ScreenRecoveryUI::ScreenRecoveryUI()
Tao Bao0bc88de2018-07-31 14:53:16 -0700375 : margin_width_(
376 android::base::GetIntProperty("ro.recovery.ui.margin_width", kDefaultMarginWidth)),
377 margin_height_(
378 android::base::GetIntProperty("ro.recovery.ui.margin_height", kDefaultMarginHeight)),
379 animation_fps_(
380 android::base::GetIntProperty("ro.recovery.ui.animation_fps", kDefaultAnimationFps)),
381 density_(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f),
Michael Bestas2e2d5d62019-03-23 17:28:22 +0200382 blank_unblank_on_init_(
383 android::base::GetBoolProperty("ro.recovery.ui.blank_unblank_on_init", false)),
Tao Baoda409fb2018-10-21 23:36:26 -0700384 current_icon_(NONE),
385 current_frame_(0),
386 intro_done_(false),
Tao Bao736d59c2017-01-03 10:15:33 -0800387 progressBarType(EMPTY),
388 progressScopeStart(0),
389 progressScopeSize(0),
390 progress(0),
391 pagesIdentical(false),
392 text_cols_(0),
393 text_rows_(0),
394 text_(nullptr),
395 text_col_(0),
396 text_row_(0),
Tao Bao736d59c2017-01-03 10:15:33 -0800397 show_text(false),
398 show_text_ever(false),
Tao Bao736d59c2017-01-03 10:15:33 -0800399 file_viewer_text_(nullptr),
Tao Bao736d59c2017-01-03 10:15:33 -0800400 stage(-1),
401 max_stage(-1),
Tao Baoefb49ad2017-01-31 23:03:10 -0800402 locale_(""),
Jerry Zhangb31f9ce2018-05-21 16:04:57 -0700403 rtl_locale_(false) {}
Doug Zongker211aebc2011-10-28 15:13:10 -0700404
Tao Bao26ea9592018-05-09 16:32:02 -0700405ScreenRecoveryUI::~ScreenRecoveryUI() {
406 progress_thread_stopped_ = true;
Tao Bao94371fd2018-06-06 07:38:54 -0700407 if (progress_thread_.joinable()) {
408 progress_thread_.join();
409 }
Tao Bao60ac6222018-06-13 14:33:51 -0700410 // No-op if gr_init() (via Init()) was not called or had failed.
411 gr_exit();
Tao Bao26ea9592018-05-09 16:32:02 -0700412}
413
Tao Baoda409fb2018-10-21 23:36:26 -0700414const GRSurface* ScreenRecoveryUI::GetCurrentFrame() const {
415 if (current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) {
416 return intro_done_ ? loop_frames_[current_frame_].get() : intro_frames_[current_frame_].get();
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700417 }
Tao Baoda409fb2018-10-21 23:36:26 -0700418 return error_icon_.get();
Elliott Hughes498cda62016-04-14 16:49:04 -0700419}
420
Tao Baoda409fb2018-10-21 23:36:26 -0700421const GRSurface* ScreenRecoveryUI::GetCurrentText() const {
422 switch (current_icon_) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700423 case ERASING:
Tao Baoda409fb2018-10-21 23:36:26 -0700424 return erasing_text_.get();
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700425 case ERROR:
Tao Baoda409fb2018-10-21 23:36:26 -0700426 return error_text_.get();
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700427 case INSTALLING_UPDATE:
Tao Baoda409fb2018-10-21 23:36:26 -0700428 return installing_text_.get();
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700429 case NO_COMMAND:
Tao Baoda409fb2018-10-21 23:36:26 -0700430 return no_command_text_.get();
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700431 case NONE:
432 abort();
433 }
Elliott Hughes498cda62016-04-14 16:49:04 -0700434}
435
Mikhail Lappob49767c2017-03-23 21:44:26 +0100436int ScreenRecoveryUI::PixelsFromDp(int dp) const {
Tao Bao0bc88de2018-07-31 14:53:16 -0700437 return dp * density_;
Elliott Hughesfaf36e02016-04-20 17:22:16 -0700438}
439
440// Here's the intended layout:
441
Elliott Hughes6d089a92016-07-08 17:23:41 -0700442// | portrait large landscape large
443// ---------+-------------------------------------------------
Tao Bao3250f722017-06-29 14:32:05 -0700444// gap |
Elliott Hughes6d089a92016-07-08 17:23:41 -0700445// icon | (200dp)
446// gap | 68dp 68dp 56dp 112dp
447// text | (14sp)
448// gap | 32dp 32dp 26dp 52dp
449// progress | (2dp)
Tao Bao3250f722017-06-29 14:32:05 -0700450// gap |
Elliott Hughesfaf36e02016-04-20 17:22:16 -0700451
Tao Bao3250f722017-06-29 14:32:05 -0700452// Note that "baseline" is actually the *top* of each icon (because that's how our drawing routines
453// work), so that's the more useful measurement for calling code. We use even top and bottom gaps.
Elliott Hughesfaf36e02016-04-20 17:22:16 -0700454
Elliott Hughes6d089a92016-07-08 17:23:41 -0700455enum Layout { PORTRAIT = 0, PORTRAIT_LARGE = 1, LANDSCAPE = 2, LANDSCAPE_LARGE = 3, LAYOUT_MAX };
Tao Bao3250f722017-06-29 14:32:05 -0700456enum Dimension { TEXT = 0, ICON = 1, DIMENSION_MAX };
Elliott Hughes6d089a92016-07-08 17:23:41 -0700457static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = {
Tianjie Xu8f397302018-08-20 13:40:47 -0700458 { 32, 68 }, // PORTRAIT
459 { 32, 68 }, // PORTRAIT_LARGE
460 { 26, 56 }, // LANDSCAPE
461 { 52, 112 }, // LANDSCAPE_LARGE
Elliott Hughes6d089a92016-07-08 17:23:41 -0700462};
463
Tao Bao99b2d772017-06-23 22:47:03 -0700464int ScreenRecoveryUI::GetAnimationBaseline() const {
Tao Baoda409fb2018-10-21 23:36:26 -0700465 return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) -
466 gr_get_height(loop_frames_[0].get());
Elliott Hughesfaf36e02016-04-20 17:22:16 -0700467}
468
Tao Bao99b2d772017-06-23 22:47:03 -0700469int ScreenRecoveryUI::GetTextBaseline() const {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700470 return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) -
Tao Baoda409fb2018-10-21 23:36:26 -0700471 gr_get_height(installing_text_.get());
Elliott Hughesfaf36e02016-04-20 17:22:16 -0700472}
473
Tao Bao99b2d772017-06-23 22:47:03 -0700474int ScreenRecoveryUI::GetProgressBaseline() const {
Tao Baoda409fb2018-10-21 23:36:26 -0700475 int elements_sum = gr_get_height(loop_frames_[0].get()) + PixelsFromDp(kLayouts[layout_][ICON]) +
476 gr_get_height(installing_text_.get()) + PixelsFromDp(kLayouts[layout_][TEXT]) +
477 gr_get_height(progress_bar_fill_.get());
Luke Song92eda4d2017-09-19 10:51:35 -0700478 int bottom_gap = (ScreenHeight() - elements_sum) / 2;
Tao Baoda409fb2018-10-21 23:36:26 -0700479 return ScreenHeight() - bottom_gap - gr_get_height(progress_bar_fill_.get());
Elliott Hughesfaf36e02016-04-20 17:22:16 -0700480}
481
Doug Zongker211aebc2011-10-28 15:13:10 -0700482// Clear the screen and draw the currently selected background icon (if any).
483// Should only be called with updateMutex locked.
Elliott Hughes498cda62016-04-14 16:49:04 -0700484void ScreenRecoveryUI::draw_background_locked() {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700485 pagesIdentical = false;
486 gr_color(0, 0, 0, 255);
487 gr_clear();
Tao Baoda409fb2018-10-21 23:36:26 -0700488 if (current_icon_ != NONE) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700489 if (max_stage != -1) {
Tao Baoda409fb2018-10-21 23:36:26 -0700490 int stage_height = gr_get_height(stage_marker_empty_.get());
491 int stage_width = gr_get_width(stage_marker_empty_.get());
492 int x = (ScreenWidth() - max_stage * gr_get_width(stage_marker_empty_.get())) / 2;
Tao Bao0bc88de2018-07-31 14:53:16 -0700493 int y = ScreenHeight() - stage_height - margin_height_;
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700494 for (int i = 0; i < max_stage; ++i) {
Tao Baoda409fb2018-10-21 23:36:26 -0700495 const auto& stage_surface = (i < stage) ? stage_marker_fill_ : stage_marker_empty_;
496 DrawSurface(stage_surface.get(), 0, 0, stage_width, stage_height, x, y);
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700497 x += stage_width;
498 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700499 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700500
Tao Baoda409fb2018-10-21 23:36:26 -0700501 const auto& text_surface = GetCurrentText();
Luke Song92eda4d2017-09-19 10:51:35 -0700502 int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2;
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700503 int text_y = GetTextBaseline();
504 gr_color(255, 255, 255, 255);
Luke Song92eda4d2017-09-19 10:51:35 -0700505 DrawTextIcon(text_x, text_y, text_surface);
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700506 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700507}
508
Tao Baoea78d862017-06-28 14:52:17 -0700509// Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be
510// called with updateMutex locked.
Elliott Hughesfaf36e02016-04-20 17:22:16 -0700511void ScreenRecoveryUI::draw_foreground_locked() {
Tao Baoda409fb2018-10-21 23:36:26 -0700512 if (current_icon_ != NONE) {
513 const auto& frame = GetCurrentFrame();
Tao Bao736d59c2017-01-03 10:15:33 -0800514 int frame_width = gr_get_width(frame);
515 int frame_height = gr_get_height(frame);
Luke Song92eda4d2017-09-19 10:51:35 -0700516 int frame_x = (ScreenWidth() - frame_width) / 2;
Tao Bao736d59c2017-01-03 10:15:33 -0800517 int frame_y = GetAnimationBaseline();
zhang sanshan0d81b892019-08-07 16:21:10 +0800518 if (frame_x >= 0 && frame_y >= 0 && (frame_x + frame_width) < ScreenWidth() &&
519 (frame_y + frame_height) < ScreenHeight())
520 DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
Tao Bao736d59c2017-01-03 10:15:33 -0800521 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700522
Tao Bao736d59c2017-01-03 10:15:33 -0800523 if (progressBarType != EMPTY) {
Tao Baoda409fb2018-10-21 23:36:26 -0700524 int width = gr_get_width(progress_bar_empty_.get());
525 int height = gr_get_height(progress_bar_empty_.get());
Doug Zongker211aebc2011-10-28 15:13:10 -0700526
Luke Song92eda4d2017-09-19 10:51:35 -0700527 int progress_x = (ScreenWidth() - width) / 2;
Tao Bao736d59c2017-01-03 10:15:33 -0800528 int progress_y = GetProgressBaseline();
Doug Zongker211aebc2011-10-28 15:13:10 -0700529
Tao Bao736d59c2017-01-03 10:15:33 -0800530 // Erase behind the progress bar (in case this was a progress-only update)
531 gr_color(0, 0, 0, 255);
Luke Song92eda4d2017-09-19 10:51:35 -0700532 DrawFill(progress_x, progress_y, width, height);
Doug Zongker211aebc2011-10-28 15:13:10 -0700533
Tao Bao736d59c2017-01-03 10:15:33 -0800534 if (progressBarType == DETERMINATE) {
535 float p = progressScopeStart + progress * progressScopeSize;
536 int pos = static_cast<int>(p * width);
Doug Zongker211aebc2011-10-28 15:13:10 -0700537
Tao Bao736d59c2017-01-03 10:15:33 -0800538 if (rtl_locale_) {
539 // Fill the progress bar from right to left.
540 if (pos > 0) {
Tao Baoda409fb2018-10-21 23:36:26 -0700541 DrawSurface(progress_bar_fill_.get(), width - pos, 0, pos, height,
542 progress_x + width - pos, progress_y);
Doug Zongker211aebc2011-10-28 15:13:10 -0700543 }
Tao Bao736d59c2017-01-03 10:15:33 -0800544 if (pos < width - 1) {
Tao Baoda409fb2018-10-21 23:36:26 -0700545 DrawSurface(progress_bar_empty_.get(), 0, 0, width - pos, height, progress_x, progress_y);
Tao Bao736d59c2017-01-03 10:15:33 -0800546 }
547 } else {
548 // Fill the progress bar from left to right.
549 if (pos > 0) {
Tao Baoda409fb2018-10-21 23:36:26 -0700550 DrawSurface(progress_bar_fill_.get(), 0, 0, pos, height, progress_x, progress_y);
Tao Bao736d59c2017-01-03 10:15:33 -0800551 }
552 if (pos < width - 1) {
Tao Baoda409fb2018-10-21 23:36:26 -0700553 DrawSurface(progress_bar_empty_.get(), pos, 0, width - pos, height, progress_x + pos,
554 progress_y);
Tao Bao736d59c2017-01-03 10:15:33 -0800555 }
556 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700557 }
Tao Bao736d59c2017-01-03 10:15:33 -0800558 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700559}
560
Jesse Chanf8084632020-04-28 14:49:13 +0000561/* recovery dark: #7C4DFF
562 recovery light: #F890FF
563 fastbootd dark: #E65100
564 fastboot light: #FDD835 */
Tao Bao99b2d772017-06-23 22:47:03 -0700565void ScreenRecoveryUI::SetColor(UIElement e) const {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700566 switch (e) {
Tianjie Xu66dbf632018-10-11 16:54:50 -0700567 case UIElement::INFO:
Richard Hansen8b867d02020-05-04 15:58:49 -0400568 if (fastbootd_logo_enabled_)
569 gr_color(0xfd, 0xd8, 0x35, 255);
570 else
Alexander Martinze06e7452022-12-20 13:15:28 +0100571 gr_color(0, 0x66, 0x77, 255);
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700572 break;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700573 case UIElement::HEADER:
Jesse Chanf8084632020-04-28 14:49:13 +0000574 if (fastbootd_logo_enabled_)
575 gr_color(0xfd, 0xd8,0x35, 255);
576 else
Alexander Martinze06e7452022-12-20 13:15:28 +0100577 gr_color(0, 0x66, 0x77, 255);
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700578 break;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700579 case UIElement::MENU:
Alessandro Astonee8754752020-03-09 23:17:50 +0100580 gr_color(0xd8, 0xd8, 0xd8, 255);
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700581 break;
Jesse Chanf8084632020-04-28 14:49:13 +0000582 case UIElement::MENU_SEL_BG:
583 case UIElement::SCROLLBAR:
584 if (fastbootd_logo_enabled_)
585 gr_color(0xe6, 0x51, 0x00, 255);
586 else
Alexander Martinze06e7452022-12-20 13:15:28 +0100587 gr_color(0, 0x66, 0x77, 255);
Jesse Chanf8084632020-04-28 14:49:13 +0000588 break;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700589 case UIElement::MENU_SEL_BG_ACTIVE:
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700590 gr_color(0, 156, 100, 255);
591 break;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700592 case UIElement::MENU_SEL_FG:
Jesse Chanf8084632020-04-28 14:49:13 +0000593 if (fastbootd_logo_enabled_)
594 gr_color(0, 0, 0, 255);
595 else
596 gr_color(0xd8, 0xd8, 0xd8, 255);
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700597 break;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700598 case UIElement::LOG:
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700599 gr_color(196, 196, 196, 255);
600 break;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700601 case UIElement::TEXT_FILL:
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700602 gr_color(0, 0, 0, 160);
603 break;
604 default:
605 gr_color(255, 255, 255, 255);
606 break;
607 }
Doug Zongkerc0441d12013-07-31 11:28:24 -0700608}
Doug Zongker211aebc2011-10-28 15:13:10 -0700609
Tianjie Xu29d55752017-09-20 17:53:46 -0700610void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector<std::string>& locales_entries,
611 size_t sel) {
612 SetLocale(locales_entries[sel]);
613 std::vector<std::string> text_name = { "erasing_text", "error_text", "installing_text",
614 "installing_security_text", "no_command_text" };
Tao Baoda409fb2018-10-21 23:36:26 -0700615 std::unordered_map<std::string, std::unique_ptr<GRSurface>> surfaces;
Tianjie Xu29d55752017-09-20 17:53:46 -0700616 for (const auto& name : text_name) {
Tao Baoda409fb2018-10-21 23:36:26 -0700617 auto text_image = LoadLocalizedBitmap(name);
Tianjie Xu29d55752017-09-20 17:53:46 -0700618 if (!text_image) {
619 Print("Failed to load %s\n", name.c_str());
620 return;
621 }
Tao Baoda409fb2018-10-21 23:36:26 -0700622 surfaces.emplace(name, std::move(text_image));
Tianjie Xu29d55752017-09-20 17:53:46 -0700623 }
624
Jerry Zhangb31f9ce2018-05-21 16:04:57 -0700625 std::lock_guard<std::mutex> lg(updateMutex);
Tianjie Xu29d55752017-09-20 17:53:46 -0700626 gr_color(0, 0, 0, 255);
627 gr_clear();
628
Tao Bao0bc88de2018-07-31 14:53:16 -0700629 int text_y = margin_height_;
630 int text_x = margin_width_;
Tianjie Xu29d55752017-09-20 17:53:46 -0700631 int line_spacing = gr_sys_font()->char_height; // Put some extra space between images.
632 // Write the header and descriptive texts.
Tianjie Xu66dbf632018-10-11 16:54:50 -0700633 SetColor(UIElement::INFO);
Tianjie Xu29d55752017-09-20 17:53:46 -0700634 std::string header = "Show background text image";
Tao Bao93e46ad2018-05-02 14:57:21 -0700635 text_y += DrawTextLine(text_x, text_y, header, true);
Tianjie Xu29d55752017-09-20 17:53:46 -0700636 std::string locale_selection = android::base::StringPrintf(
Tao Bao39c49182018-05-07 22:50:33 -0700637 "Current locale: %s, %zu/%zu", locales_entries[sel].c_str(), sel + 1, locales_entries.size());
Tao Bao93e46ad2018-05-02 14:57:21 -0700638 // clang-format off
639 std::vector<std::string> instruction = {
640 locale_selection,
641 "Use volume up/down to switch locales and power to exit."
642 };
643 // clang-format on
Tianjie Xu29d55752017-09-20 17:53:46 -0700644 text_y += DrawWrappedTextLines(text_x, text_y, instruction);
645
646 // Iterate through the text images and display them in order for the current locale.
647 for (const auto& p : surfaces) {
648 text_y += line_spacing;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700649 SetColor(UIElement::LOG);
Tao Bao93e46ad2018-05-02 14:57:21 -0700650 text_y += DrawTextLine(text_x, text_y, p.first, false);
Tianjie Xu29d55752017-09-20 17:53:46 -0700651 gr_color(255, 255, 255, 255);
652 gr_texticon(text_x, text_y, p.second.get());
653 text_y += gr_get_height(p.second.get());
654 }
655 // Update the whole screen.
656 gr_flip();
Tianjie Xu29d55752017-09-20 17:53:46 -0700657}
658
Tao Bao39c49182018-05-07 22:50:33 -0700659void ScreenRecoveryUI::CheckBackgroundTextImages() {
Tianjie Xu29d55752017-09-20 17:53:46 -0700660 // Load a list of locales embedded in one of the resource files.
661 std::vector<std::string> locales_entries = get_locales_in_png("installing_text");
662 if (locales_entries.empty()) {
663 Print("Failed to load locales from the resource files\n");
664 return;
665 }
Tao Bao39c49182018-05-07 22:50:33 -0700666 std::string saved_locale = locale_;
Tianjie Xu29d55752017-09-20 17:53:46 -0700667 size_t selected = 0;
668 SelectAndShowBackgroundText(locales_entries, selected);
669
670 FlushKeys();
671 while (true) {
Tom Marshall53176c72020-03-10 20:17:49 +0100672 InputEvent evt = WaitInputEvent();
673 if (evt.type() == EventType::EXTRA) {
674 if (evt.key() == static_cast<int>(KeyError::INTERRUPTED)) break;
675 }
676 if (evt.type() == EventType::KEY) {
677 if (evt.key() == KEY_POWER || evt.key() == KEY_ENTER) {
678 break;
679 } else if (evt.key() == KEY_UP || evt.key() == KEY_VOLUMEUP) {
680 selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1;
681 SelectAndShowBackgroundText(locales_entries, selected);
682 } else if (evt.key() == KEY_DOWN || evt.key() == KEY_VOLUMEDOWN) {
683 selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1;
684 SelectAndShowBackgroundText(locales_entries, selected);
685 }
Tianjie Xu29d55752017-09-20 17:53:46 -0700686 }
687 }
688
689 SetLocale(saved_locale);
690}
691
Luke Song92eda4d2017-09-19 10:51:35 -0700692int ScreenRecoveryUI::ScreenWidth() const {
693 return gr_fb_width();
694}
695
696int ScreenRecoveryUI::ScreenHeight() const {
697 return gr_fb_height();
698}
699
Tao Bao65815b62018-10-23 10:54:02 -0700700void ScreenRecoveryUI::DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx,
Luke Song92eda4d2017-09-19 10:51:35 -0700701 int dy) const {
702 gr_blit(surface, sx, sy, w, h, dx, dy);
703}
704
Tao Baoea78d862017-06-28 14:52:17 -0700705int ScreenRecoveryUI::DrawHorizontalRule(int y) const {
Luke Song92eda4d2017-09-19 10:51:35 -0700706 gr_fill(0, y + 4, ScreenWidth(), y + 6);
Tao Baoea78d862017-06-28 14:52:17 -0700707 return 8;
Elliott Hughes95fc63e2015-04-10 19:12:01 -0700708}
709
Luke Songe2bd8762017-06-12 16:08:33 -0700710void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const {
Alessandro Astonee8754752020-03-09 23:17:50 +0100711 if (y + height > ScreenHeight())
712 height = ScreenHeight() - y;
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700713 gr_fill(x, y, x + width, y + height);
Luke Songe2bd8762017-06-12 16:08:33 -0700714}
715
Alessandro Astonee8754752020-03-09 23:17:50 +0100716void ScreenRecoveryUI::DrawScrollBar(int y, int height) const {
717 int x = ScreenWidth() - margin_width_;
718 int width = 8;
719 gr_fill(x - width, y, x, y + height);
720}
721
Luke Song92eda4d2017-09-19 10:51:35 -0700722void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const {
723 gr_fill(x, y, w, h);
724}
725
Tao Bao65815b62018-10-23 10:54:02 -0700726void ScreenRecoveryUI::DrawTextIcon(int x, int y, const GRSurface* surface) const {
Luke Song92eda4d2017-09-19 10:51:35 -0700727 gr_texticon(x, y, surface);
728}
729
Tao Bao93e46ad2018-05-02 14:57:21 -0700730int ScreenRecoveryUI::DrawTextLine(int x, int y, const std::string& line, bool bold) const {
731 gr_text(gr_sys_font(), x, y, line.c_str(), bold);
Tao Baoea78d862017-06-28 14:52:17 -0700732 return char_height_ + 4;
Elliott Hughes8fd86d72015-04-13 14:36:02 -0700733}
734
Tao Bao93e46ad2018-05-02 14:57:21 -0700735int ScreenRecoveryUI::DrawTextLines(int x, int y, const std::vector<std::string>& lines) const {
Tao Baoea78d862017-06-28 14:52:17 -0700736 int offset = 0;
Tao Bao93e46ad2018-05-02 14:57:21 -0700737 for (const auto& line : lines) {
738 offset += DrawTextLine(x, y + offset, line, false);
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700739 }
Tao Baoea78d862017-06-28 14:52:17 -0700740 return offset;
Elliott Hughes8fd86d72015-04-13 14:36:02 -0700741}
742
Tao Bao93e46ad2018-05-02 14:57:21 -0700743int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y,
744 const std::vector<std::string>& lines) const {
Tao Bao452b4872018-05-09 11:52:09 -0700745 // Keep symmetrical margins based on the given offset (i.e. x).
746 size_t text_cols = (ScreenWidth() - x * 2) / char_width_;
Tao Bao2bbc6d62017-08-13 23:48:55 -0700747 int offset = 0;
Tao Bao93e46ad2018-05-02 14:57:21 -0700748 for (const auto& line : lines) {
Tao Bao2bbc6d62017-08-13 23:48:55 -0700749 size_t next_start = 0;
750 while (next_start < line.size()) {
Tao Bao452b4872018-05-09 11:52:09 -0700751 std::string sub = line.substr(next_start, text_cols + 1);
752 if (sub.size() <= text_cols) {
Tao Bao2bbc6d62017-08-13 23:48:55 -0700753 next_start += sub.size();
754 } else {
Tao Bao452b4872018-05-09 11:52:09 -0700755 // Line too long and must be wrapped to text_cols columns.
Tao Bao2bbc6d62017-08-13 23:48:55 -0700756 size_t last_space = sub.find_last_of(" \t\n");
757 if (last_space == std::string::npos) {
Tao Bao93e46ad2018-05-02 14:57:21 -0700758 // No space found, just draw as much as we can.
Tao Bao452b4872018-05-09 11:52:09 -0700759 sub.resize(text_cols);
760 next_start += text_cols;
Tao Bao2bbc6d62017-08-13 23:48:55 -0700761 } else {
762 sub.resize(last_space);
763 next_start += last_space + 1;
764 }
765 }
Tao Bao93e46ad2018-05-02 14:57:21 -0700766 offset += DrawTextLine(x, y + offset, sub, false);
Tao Bao2bbc6d62017-08-13 23:48:55 -0700767 }
768 }
769 return offset;
770}
771
Jerry Zhang0e577ee2018-05-07 11:21:10 -0700772void ScreenRecoveryUI::SetTitle(const std::vector<std::string>& lines) {
773 title_lines_ = lines;
774}
775
Tianjie Xue5032212019-07-23 13:23:29 -0700776std::vector<std::string> ScreenRecoveryUI::GetMenuHelpMessage() const {
777 // clang-format off
778 static std::vector<std::string> REGULAR_HELP{
Richard Hansen36cf08d2020-05-13 10:07:36 -0400779 "Use the volume up/down keys to navigate.",
780 "Use the power key to select.",
Tianjie Xue5032212019-07-23 13:23:29 -0700781 };
782 static std::vector<std::string> LONG_PRESS_HELP{
783 "Any button cycles highlight.",
784 "Long-press activates.",
785 };
Richard Hansen22b04102020-05-12 18:18:43 -0400786 static const std::vector<std::string> NO_HELP = {};
Tianjie Xue5032212019-07-23 13:23:29 -0700787 // clang-format on
Richard Hansen22b04102020-05-12 18:18:43 -0400788 return HasTouchScreen() ? NO_HELP : HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP;
Tianjie Xue5032212019-07-23 13:23:29 -0700789}
790
Tao Bao171b4c42017-06-19 23:10:44 -0700791// Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex
792// locked.
Elliott Hughes8de52072015-04-08 20:06:50 -0700793void ScreenRecoveryUI::draw_screen_locked() {
Tao Bao171b4c42017-06-19 23:10:44 -0700794 if (!show_text) {
795 draw_background_locked();
796 draw_foreground_locked();
797 return;
798 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700799
Tao Bao171b4c42017-06-19 23:10:44 -0700800 gr_color(0, 0, 0, 255);
801 gr_clear();
Elliott Hughes8fd86d72015-04-13 14:36:02 -0700802
Tianjie Xue5032212019-07-23 13:23:29 -0700803 draw_menu_and_text_buffer_locked(GetMenuHelpMessage());
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700804}
805
806// Draws the menu and text buffer on the screen. Should only be called with updateMutex locked.
Tao Bao93e46ad2018-05-02 14:57:21 -0700807void ScreenRecoveryUI::draw_menu_and_text_buffer_locked(
808 const std::vector<std::string>& help_message) {
Tao Bao0bc88de2018-07-31 14:53:16 -0700809 int y = margin_height_;
David Anderson983e2d52019-01-02 11:35:38 -0800810
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700811 if (menu_) {
Alessandro Astone5d1957d2020-03-29 15:08:09 +0200812 auto& logo = fastbootd_logo_enabled_ ? fastbootd_logo_ : lineage_logo_;
Richard Hansend72e0c32020-05-12 18:08:56 -0400813 auto logo_width = gr_get_width(logo.get());
814 auto logo_height = gr_get_height(logo.get());
815 auto centered_x = ScreenWidth() / 2 - logo_width / 2;
816 DrawSurface(logo.get(), 0, 0, logo_width, logo_height, centered_x, y);
817 y += logo_height;
Tao Bao171b4c42017-06-19 23:10:44 -0700818
Richard Hansend72e0c32020-05-12 18:08:56 -0400819 if (!menu_->IsMain()) {
820 auto icon_w = gr_get_width(back_icon_.get());
821 auto icon_h = gr_get_height(back_icon_.get());
822 auto icon_x = centered_x / 2 - icon_w / 2;
823 auto icon_y = y - logo_height / 2 - icon_h / 2;
824 gr_blit(back_icon_sel_ && menu_->selection() == -1 ? back_icon_sel_.get() : back_icon_.get(),
825 0, 0, icon_w, icon_h, icon_x, icon_y);
Alessandro Astonee8754752020-03-09 23:17:50 +0100826 }
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700827
Richard Hansend72e0c32020-05-12 18:08:56 -0400828 int x = margin_width_ + kMenuIndent;
829 if (!title_lines_.empty()) {
830 SetColor(UIElement::INFO);
831 y += DrawTextLines(x, y, title_lines_);
832 }
Tianjie Xu66dbf632018-10-11 16:54:50 -0700833 y += menu_->DrawHeader(x, y);
Tom Marshall53176c72020-03-10 20:17:49 +0100834 menu_start_y_ = y + 12; // Skip horizontal rule and some margin
Alessandro Astone3525c4d2020-06-25 19:54:18 +0200835 menu_->SetMenuHeight(std::max(0, ScreenHeight() - menu_start_y_));
Tianjie Xu66dbf632018-10-11 16:54:50 -0700836 y += menu_->DrawItems(x, y, ScreenWidth(), IsLongPress());
Richard Hansen22b04102020-05-12 18:18:43 -0400837 if (!help_message.empty()) {
838 y += MenuItemPadding();
839 SetColor(UIElement::INFO);
840 y += DrawTextLines(x, y, help_message);
841 }
Tao Bao171b4c42017-06-19 23:10:44 -0700842 }
843
844 // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or
845 // we've displayed the entire text buffer.
Tianjie Xu66dbf632018-10-11 16:54:50 -0700846 SetColor(UIElement::LOG);
Tao Baocb5524c2017-09-08 21:25:32 -0700847 int row = text_row_;
Tao Bao171b4c42017-06-19 23:10:44 -0700848 size_t count = 0;
Tao Bao0bc88de2018-07-31 14:53:16 -0700849 for (int ty = ScreenHeight() - margin_height_ - char_height_; ty >= y && count < text_rows_;
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700850 ty -= char_height_, ++count) {
Tao Bao0bc88de2018-07-31 14:53:16 -0700851 DrawTextLine(margin_width_, ty, text_[row], false);
Tao Bao171b4c42017-06-19 23:10:44 -0700852 --row;
853 if (row < 0) row = text_rows_ - 1;
854 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700855}
856
857// Redraw everything on the screen and flip the screen (make it visible).
858// Should only be called with updateMutex locked.
Elliott Hughes8de52072015-04-08 20:06:50 -0700859void ScreenRecoveryUI::update_screen_locked() {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700860 draw_screen_locked();
861 gr_flip();
Doug Zongker211aebc2011-10-28 15:13:10 -0700862}
863
864// Updates only the progress bar, if possible, otherwise redraws the screen.
865// Should only be called with updateMutex locked.
Elliott Hughes8de52072015-04-08 20:06:50 -0700866void ScreenRecoveryUI::update_progress_locked() {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700867 if (show_text || !pagesIdentical) {
868 draw_screen_locked(); // Must redraw the whole screen
869 pagesIdentical = true;
870 } else {
871 draw_foreground_locked(); // Draw only the progress bar and overlays
872 }
873 gr_flip();
Doug Zongker211aebc2011-10-28 15:13:10 -0700874}
875
Elliott Hughes985022a2015-04-13 13:04:32 -0700876void ScreenRecoveryUI::ProgressThreadLoop() {
Tao Bao0bc88de2018-07-31 14:53:16 -0700877 double interval = 1.0 / animation_fps_;
Tao Bao26ea9592018-05-09 16:32:02 -0700878 while (!progress_thread_stopped_) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700879 double start = now();
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700880 bool redraw = false;
Jerry Zhangb31f9ce2018-05-21 16:04:57 -0700881 {
882 std::lock_guard<std::mutex> lg(updateMutex);
Doug Zongker211aebc2011-10-28 15:13:10 -0700883
Jerry Zhangb31f9ce2018-05-21 16:04:57 -0700884 // update the installation animation, if active
885 // skip this if we have a text overlay (too expensive to update)
Tao Baoda409fb2018-10-21 23:36:26 -0700886 if ((current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) && !show_text) {
887 if (!intro_done_) {
888 if (current_frame_ == intro_frames_.size() - 1) {
889 intro_done_ = true;
890 current_frame_ = 0;
Jerry Zhangb31f9ce2018-05-21 16:04:57 -0700891 } else {
Tao Baoda409fb2018-10-21 23:36:26 -0700892 ++current_frame_;
Jerry Zhangb31f9ce2018-05-21 16:04:57 -0700893 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700894 } else {
Tao Baoda409fb2018-10-21 23:36:26 -0700895 current_frame_ = (current_frame_ + 1) % loop_frames_.size();
Doug Zongker211aebc2011-10-28 15:13:10 -0700896 }
897
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700898 redraw = true;
899 }
Jerry Zhangb31f9ce2018-05-21 16:04:57 -0700900
901 // move the progress bar forward on timed intervals, if configured
902 int duration = progressScopeDuration;
903 if (progressBarType == DETERMINATE && duration > 0) {
904 double elapsed = now() - progressScopeTime;
905 float p = 1.0 * elapsed / duration;
906 if (p > 1.0) p = 1.0;
907 if (p > progress) {
908 progress = p;
909 redraw = true;
910 }
911 }
912
913 if (redraw) update_progress_locked();
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700914 }
915
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700916 double end = now();
917 // minimum of 20ms delay between frames
918 double delay = interval - (end - start);
919 if (delay < 0.02) delay = 0.02;
920 usleep(static_cast<useconds_t>(delay * 1000000));
921 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700922}
923
Tao Baoda409fb2018-10-21 23:36:26 -0700924std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadBitmap(const std::string& filename) {
925 GRSurface* surface;
926 if (auto result = res_create_display_surface(filename.c_str(), &surface); result < 0) {
927 LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")";
928 return nullptr;
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700929 }
Tao Baoda409fb2018-10-21 23:36:26 -0700930 return std::unique_ptr<GRSurface>(surface);
Doug Zongkereac881c2014-03-07 09:21:25 -0800931}
932
Tao Baoda409fb2018-10-21 23:36:26 -0700933std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadLocalizedBitmap(const std::string& filename) {
934 GRSurface* surface;
xunchang9d05c8a2019-04-16 12:07:42 -0700935 auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface);
936 if (result == 0) {
937 return std::unique_ptr<GRSurface>(surface);
Tao Bao736d59c2017-01-03 10:15:33 -0800938 }
xunchang9d05c8a2019-04-16 12:07:42 -0700939
940 result = res_create_localized_alpha_surface(filename.c_str(), DEFAULT_LOCALE, &surface);
941 if (result == 0) {
942 return std::unique_ptr<GRSurface>(surface);
943 }
944
xunchang9d05c8a2019-04-16 12:07:42 -0700945 return nullptr;
Doug Zongker02ec6b82012-08-22 17:26:40 -0700946}
947
Elliott Hughesaa0d6af2015-04-08 12:42:50 -0700948static char** Alloc2d(size_t rows, size_t cols) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700949 char** result = new char*[rows];
950 for (size_t i = 0; i < rows; ++i) {
951 result[i] = new char[cols];
952 memset(result[i], 0, cols);
953 }
954 return result;
Elliott Hughesaa0d6af2015-04-08 12:42:50 -0700955}
956
Tianjie Xu35926c42016-04-28 18:06:26 -0700957// Choose the right background string to display during update.
958void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700959 if (security_update) {
Tao Baoda409fb2018-10-21 23:36:26 -0700960 installing_text_ = LoadLocalizedBitmap("installing_security_text");
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700961 } else {
Tao Baoda409fb2018-10-21 23:36:26 -0700962 installing_text_ = LoadLocalizedBitmap("installing_text");
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700963 }
964 Redraw();
Tianjie Xu35926c42016-04-28 18:06:26 -0700965}
966
Sen Jiangd5304492016-12-09 16:20:49 -0800967bool ScreenRecoveryUI::InitTextParams() {
Tao Baoba4edb32018-06-13 11:22:50 -0700968 // gr_init() would return successfully on font initialization failure.
969 if (gr_sys_font() == nullptr) {
Tao Bao171b4c42017-06-19 23:10:44 -0700970 return false;
971 }
Tao Bao171b4c42017-06-19 23:10:44 -0700972 gr_font_size(gr_sys_font(), &char_width_, &char_height_);
Alessandro Astonee8754752020-03-09 23:17:50 +0100973 gr_font_size(gr_menu_font(), &menu_char_width_, &menu_char_height_);
Tao Bao0bc88de2018-07-31 14:53:16 -0700974 text_rows_ = (ScreenHeight() - margin_height_ * 2) / char_height_;
975 text_cols_ = (ScreenWidth() - margin_width_ * 2) / char_width_;
Tao Bao171b4c42017-06-19 23:10:44 -0700976 return true;
Damien Bargiacchi5e7cfb92016-08-24 18:28:43 -0700977}
978
Tianjie Xub99e6062018-10-16 15:13:09 -0700979bool ScreenRecoveryUI::LoadWipeDataMenuText() {
Tianjie Xu1a0a30a2018-10-25 15:22:07 -0700980 // Ignores the errors since the member variables will stay as nullptr.
981 cancel_wipe_data_text_ = LoadLocalizedBitmap("cancel_wipe_data_text");
982 factory_data_reset_text_ = LoadLocalizedBitmap("factory_data_reset_text");
983 try_again_text_ = LoadLocalizedBitmap("try_again_text");
984 wipe_data_confirmation_text_ = LoadLocalizedBitmap("wipe_data_confirmation_text");
985 wipe_data_menu_header_text_ = LoadLocalizedBitmap("wipe_data_menu_header_text");
Tianjie Xub99e6062018-10-16 15:13:09 -0700986 return true;
987}
988
Mark Salyzyn30017e72020-05-13 12:39:12 -0700989static bool InitGraphics() {
990 // Timeout is same as init wait for file default of 5 seconds and is arbitrary
991 const unsigned timeout = 500; // 10ms increments
992 for (auto retry = timeout; retry > 0; --retry) {
993 if (gr_init() == 0) {
994 if (retry < timeout) {
995 // Log message like init wait for file completion log for consistency.
996 LOG(WARNING) << "wait for 'graphics' took " << ((timeout - retry) * 10) << "ms";
997 }
998 return true;
999 }
1000 std::this_thread::sleep_for(10ms);
1001 }
1002 // Log message like init wait for file timeout log for consistency.
1003 LOG(ERROR) << "timeout wait for 'graphics' took " << (timeout * 10) << "ms";
1004 return false;
1005}
1006
Tao Bao736d59c2017-01-03 10:15:33 -08001007bool ScreenRecoveryUI::Init(const std::string& locale) {
1008 RecoveryUI::Init(locale);
Tao Baoefb49ad2017-01-31 23:03:10 -08001009
Mark Salyzyn30017e72020-05-13 12:39:12 -07001010 if (!InitGraphics()) {
Tao Baoba4edb32018-06-13 11:22:50 -07001011 return false;
1012 }
1013
Tao Bao736d59c2017-01-03 10:15:33 -08001014 if (!InitTextParams()) {
1015 return false;
1016 }
Alessandro Astonee8754752020-03-09 23:17:50 +01001017 menu_draw_funcs_ = std::make_unique<MenuDrawFunctions>(*this);
Damien Bargiacchi5e7cfb92016-08-24 18:28:43 -07001018
Michael Bestas2e2d5d62019-03-23 17:28:22 +02001019 if (blank_unblank_on_init_) {
1020 gr_fb_blank(true);
1021 gr_fb_blank(false);
1022 }
1023
Tao Bao736d59c2017-01-03 10:15:33 -08001024 // Are we portrait or landscape?
1025 layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT;
1026 // Are we the large variant of our base layout?
1027 if (gr_fb_height() > PixelsFromDp(800)) ++layout_;
Elliott Hughesfaf36e02016-04-20 17:22:16 -07001028
Tao Bao736d59c2017-01-03 10:15:33 -08001029 text_ = Alloc2d(text_rows_, text_cols_ + 1);
1030 file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
Doug Zongker55a36ac2013-03-04 15:49:02 -08001031
Tao Bao736d59c2017-01-03 10:15:33 -08001032 text_col_ = text_row_ = 0;
Doug Zongker211aebc2011-10-28 15:13:10 -07001033
Tao Baoefb49ad2017-01-31 23:03:10 -08001034 // Set up the locale info.
1035 SetLocale(locale);
1036
Tao Baoda409fb2018-10-21 23:36:26 -07001037 error_icon_ = LoadBitmap("icon_error");
Doug Zongker02ec6b82012-08-22 17:26:40 -07001038
Tao Baoda409fb2018-10-21 23:36:26 -07001039 progress_bar_empty_ = LoadBitmap("progress_empty");
1040 progress_bar_fill_ = LoadBitmap("progress_fill");
1041 stage_marker_empty_ = LoadBitmap("stage_empty");
1042 stage_marker_fill_ = LoadBitmap("stage_fill");
Elliott Hughes498cda62016-04-14 16:49:04 -07001043
Tao Baoda409fb2018-10-21 23:36:26 -07001044 erasing_text_ = LoadLocalizedBitmap("erasing_text");
1045 no_command_text_ = LoadLocalizedBitmap("no_command_text");
1046 error_text_ = LoadLocalizedBitmap("error_text");
Doug Zongker211aebc2011-10-28 15:13:10 -07001047
Alessandro Astonee8754752020-03-09 23:17:50 +01001048 back_icon_ = LoadBitmap("ic_back");
1049 back_icon_sel_ = LoadBitmap("ic_back_sel");
Alexander Martinzdeb4fe22022-12-20 13:17:44 +01001050 lineage_logo_ = LoadBitmap("logo_image");
Alessandro Astoneb9f974d2020-02-26 17:25:54 +01001051 if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false) ||
1052 android::base::GetBoolProperty("ro.fastbootd.available", false)) {
David Anderson983e2d52019-01-02 11:35:38 -08001053 fastbootd_logo_ = LoadBitmap("fastbootd");
1054 }
1055
Tao Baoda409fb2018-10-21 23:36:26 -07001056 // Background text for "installing_update" could be "installing update" or
1057 // "installing security update". It will be set after Init() according to the commands in BCB.
1058 installing_text_.reset();
Elliott Hughes498cda62016-04-14 16:49:04 -07001059
Tianjie Xub99e6062018-10-16 15:13:09 -07001060 LoadWipeDataMenuText();
1061
Tao Bao736d59c2017-01-03 10:15:33 -08001062 LoadAnimation();
Doug Zongker02ec6b82012-08-22 17:26:40 -07001063
Tao Bao26ea9592018-05-09 16:32:02 -07001064 // Keep the progress bar updated, even when the process is otherwise busy.
1065 progress_thread_ = std::thread(&ScreenRecoveryUI::ProgressThreadLoop, this);
Sen Jiangd5304492016-12-09 16:20:49 -08001066
Tao Bao736d59c2017-01-03 10:15:33 -08001067 return true;
Doug Zongker211aebc2011-10-28 15:13:10 -07001068}
1069
Tao Bao551d2c32018-05-09 20:53:13 -07001070std::string ScreenRecoveryUI::GetLocale() const {
Jerry Zhang2dea53e2018-05-02 17:15:03 -07001071 return locale_;
1072}
1073
Elliott Hughes498cda62016-04-14 16:49:04 -07001074void ScreenRecoveryUI::LoadAnimation() {
Tao Bao6cd81682018-05-03 21:53:11 -07001075 std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(Paths::Get().resource_dir().c_str()),
1076 closedir);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001077 dirent* de;
1078 std::vector<std::string> intro_frame_names;
Joshua Lambert94a83be2019-12-09 20:24:18 +00001079 std::vector<std::string> loop_frame_names;
Damien Bargiacchi5e7cfb92016-08-24 18:28:43 -07001080
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001081 while ((de = readdir(dir.get())) != nullptr) {
1082 int value, num_chars;
1083 if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) {
1084 intro_frame_names.emplace_back(de->d_name, num_chars);
1085 } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) {
Joshua Lambert94a83be2019-12-09 20:24:18 +00001086 loop_frame_names.emplace_back(de->d_name, num_chars);
Elliott Hughes498cda62016-04-14 16:49:04 -07001087 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001088 }
Elliott Hughes498cda62016-04-14 16:49:04 -07001089
Tao Baoda409fb2018-10-21 23:36:26 -07001090 size_t intro_frames = intro_frame_names.size();
1091 size_t loop_frames = loop_frame_names.size();
Damien Bargiacchi5e7cfb92016-08-24 18:28:43 -07001092
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001093 // It's okay to not have an intro.
Tao Baoda409fb2018-10-21 23:36:26 -07001094 if (intro_frames == 0) intro_done_ = true;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001095 // But you must have an animation.
1096 if (loop_frames == 0) abort();
Elliott Hughes498cda62016-04-14 16:49:04 -07001097
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001098 std::sort(intro_frame_names.begin(), intro_frame_names.end());
1099 std::sort(loop_frame_names.begin(), loop_frame_names.end());
Damien Bargiacchi5e7cfb92016-08-24 18:28:43 -07001100
Tao Baoda409fb2018-10-21 23:36:26 -07001101 intro_frames_.clear();
1102 intro_frames_.reserve(intro_frames);
1103 for (const auto& frame_name : intro_frame_names) {
1104 intro_frames_.emplace_back(LoadBitmap(frame_name));
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001105 }
Elliott Hughes498cda62016-04-14 16:49:04 -07001106
Tao Baoda409fb2018-10-21 23:36:26 -07001107 loop_frames_.clear();
1108 loop_frames_.reserve(loop_frames);
1109 for (const auto& frame_name : loop_frame_names) {
1110 loop_frames_.emplace_back(LoadBitmap(frame_name));
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001111 }
Elliott Hughes498cda62016-04-14 16:49:04 -07001112}
1113
Elliott Hughes8de52072015-04-08 20:06:50 -07001114void ScreenRecoveryUI::SetBackground(Icon icon) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001115 std::lock_guard<std::mutex> lg(updateMutex);
Doug Zongker02ec6b82012-08-22 17:26:40 -07001116
Tao Baoda409fb2018-10-21 23:36:26 -07001117 current_icon_ = icon;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001118 update_screen_locked();
Doug Zongker211aebc2011-10-28 15:13:10 -07001119}
1120
Elliott Hughes8de52072015-04-08 20:06:50 -07001121void ScreenRecoveryUI::SetProgressType(ProgressType type) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001122 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001123 if (progressBarType != type) {
1124 progressBarType = type;
1125 }
1126 progressScopeStart = 0;
1127 progressScopeSize = 0;
1128 progress = 0;
1129 update_progress_locked();
Doug Zongker211aebc2011-10-28 15:13:10 -07001130}
1131
Elliott Hughes8de52072015-04-08 20:06:50 -07001132void ScreenRecoveryUI::ShowProgress(float portion, float seconds) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001133 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001134 progressBarType = DETERMINATE;
1135 progressScopeStart += progressScopeSize;
1136 progressScopeSize = portion;
1137 progressScopeTime = now();
1138 progressScopeDuration = seconds;
1139 progress = 0;
1140 update_progress_locked();
Doug Zongker211aebc2011-10-28 15:13:10 -07001141}
1142
Elliott Hughes8de52072015-04-08 20:06:50 -07001143void ScreenRecoveryUI::SetProgress(float fraction) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001144 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001145 if (fraction < 0.0) fraction = 0.0;
1146 if (fraction > 1.0) fraction = 1.0;
1147 if (progressBarType == DETERMINATE && fraction > progress) {
1148 // Skip updates that aren't visibly different.
Tao Baoda409fb2018-10-21 23:36:26 -07001149 int width = gr_get_width(progress_bar_empty_.get());
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001150 float scale = width * progressScopeSize;
1151 if ((int)(progress * scale) != (int)(fraction * scale)) {
1152 progress = fraction;
1153 update_progress_locked();
Doug Zongker211aebc2011-10-28 15:13:10 -07001154 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001155 }
Doug Zongker211aebc2011-10-28 15:13:10 -07001156}
1157
Doug Zongkerc87bab12013-11-25 13:53:25 -08001158void ScreenRecoveryUI::SetStage(int current, int max) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001159 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001160 stage = current;
1161 max_stage = max;
Doug Zongkerc87bab12013-11-25 13:53:25 -08001162}
1163
Tao Baob6918c72015-05-19 17:02:16 -07001164void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001165 std::string str;
1166 android::base::StringAppendV(&str, fmt, ap);
Doug Zongker211aebc2011-10-28 15:13:10 -07001167
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001168 if (copy_to_stdout) {
1169 fputs(str.c_str(), stdout);
1170 }
Doug Zongker211aebc2011-10-28 15:13:10 -07001171
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001172 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001173 if (text_rows_ > 0 && text_cols_ > 0) {
1174 for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) {
1175 if (*ptr == '\n' || text_col_ >= text_cols_) {
Elliott Hughesc0491632015-05-06 12:40:05 -07001176 text_[text_row_][text_col_] = '\0';
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001177 text_col_ = 0;
1178 text_row_ = (text_row_ + 1) % text_rows_;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001179 }
1180 if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
Doug Zongker211aebc2011-10-28 15:13:10 -07001181 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001182 text_[text_row_][text_col_] = '\0';
1183 update_screen_locked();
1184 }
Doug Zongker211aebc2011-10-28 15:13:10 -07001185}
1186
Tao Baob6918c72015-05-19 17:02:16 -07001187void ScreenRecoveryUI::Print(const char* fmt, ...) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001188 va_list ap;
1189 va_start(ap, fmt);
1190 PrintV(fmt, true, ap);
1191 va_end(ap);
Tao Baob6918c72015-05-19 17:02:16 -07001192}
1193
Tianjie Xu8f397302018-08-20 13:40:47 -07001194void ScreenRecoveryUI::PrintOnScreenOnly(const char* fmt, ...) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001195 va_list ap;
1196 va_start(ap, fmt);
1197 PrintV(fmt, false, ap);
1198 va_end(ap);
Tao Baob6918c72015-05-19 17:02:16 -07001199}
1200
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001201void ScreenRecoveryUI::PutChar(char ch) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001202 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001203 if (ch != '\n') text_[text_row_][text_col_++] = ch;
1204 if (ch == '\n' || text_col_ >= text_cols_) {
1205 text_col_ = 0;
1206 ++text_row_;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001207 }
Elliott Hughes8de52072015-04-08 20:06:50 -07001208}
1209
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001210void ScreenRecoveryUI::ClearText() {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001211 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001212 text_col_ = 0;
1213 text_row_ = 0;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001214 for (size_t i = 0; i < text_rows_; ++i) {
1215 memset(text_[i], 0, text_cols_ + 1);
1216 }
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001217}
1218
1219void ScreenRecoveryUI::ShowFile(FILE* fp) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001220 std::vector<off_t> offsets;
1221 offsets.push_back(ftello(fp));
1222 ClearText();
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001223
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001224 struct stat sb;
1225 fstat(fileno(fp), &sb);
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001226
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001227 bool show_prompt = false;
1228 while (true) {
1229 if (show_prompt) {
1230 PrintOnScreenOnly("--(%d%% of %d bytes)--",
1231 static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))),
1232 static_cast<int>(sb.st_size));
1233 Redraw();
1234 while (show_prompt) {
1235 show_prompt = false;
Tom Marshall53176c72020-03-10 20:17:49 +01001236 InputEvent evt = WaitInputEvent();
1237 if (evt.type() == EventType::EXTRA) {
1238 if (evt.key() == static_cast<int>(KeyError::INTERRUPTED)) {
1239 return;
1240 }
1241 }
1242 if (evt.type() != EventType::KEY) {
1243 show_prompt = true;
1244 continue;
1245 }
1246 if (evt.key() == KEY_POWER || evt.key() == KEY_ENTER || evt.key() == KEY_BACKSPACE ||
1247 evt.key() == KEY_BACK || evt.key() == KEY_HOME || evt.key() == KEY_HOMEPAGE) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001248 return;
Tom Marshall53176c72020-03-10 20:17:49 +01001249 } else if (evt.key() == KEY_UP || evt.key() == KEY_VOLUMEUP || evt.key() == KEY_SCROLLUP) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001250 if (offsets.size() <= 1) {
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001251 show_prompt = true;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001252 } else {
1253 offsets.pop_back();
1254 fseek(fp, offsets.back(), SEEK_SET);
1255 }
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001256 } else {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001257 if (feof(fp)) {
1258 return;
1259 }
1260 offsets.push_back(ftello(fp));
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001261 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001262 }
1263 ClearText();
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001264 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001265
1266 int ch = getc(fp);
1267 if (ch == EOF) {
1268 while (text_row_ < text_rows_ - 1) PutChar('\n');
1269 show_prompt = true;
1270 } else {
1271 PutChar(ch);
1272 if (text_col_ == 0 && text_row_ >= text_rows_ - 1) {
1273 show_prompt = true;
1274 }
1275 }
1276 }
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001277}
1278
Tao Bao1d156b92018-05-02 12:43:18 -07001279void ScreenRecoveryUI::ShowFile(const std::string& filename) {
1280 std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(filename.c_str(), "re"), fclose);
1281 if (!fp) {
1282 Print(" Unable to open %s: %s\n", filename.c_str(), strerror(errno));
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001283 return;
1284 }
Elliott Hughesc0491632015-05-06 12:40:05 -07001285
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001286 char** old_text = text_;
1287 size_t old_text_col = text_col_;
1288 size_t old_text_row = text_row_;
Elliott Hughesc0491632015-05-06 12:40:05 -07001289
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001290 // Swap in the alternate screen and clear it.
1291 text_ = file_viewer_text_;
1292 ClearText();
Elliott Hughesc0491632015-05-06 12:40:05 -07001293
Tao Bao1d156b92018-05-02 12:43:18 -07001294 ShowFile(fp.get());
Elliott Hughesc0491632015-05-06 12:40:05 -07001295
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001296 text_ = old_text;
1297 text_col_ = old_text_col;
1298 text_row_ = old_text_row;
Elliott Hughes8de52072015-04-08 20:06:50 -07001299}
1300
Tao Baoda409fb2018-10-21 23:36:26 -07001301std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu(
1302 const GRSurface* graphic_header, const std::vector<const GRSurface*>& graphic_items,
1303 const std::vector<std::string>& text_headers, const std::vector<std::string>& text_items,
1304 size_t initial_selection) const {
Tianjie Xub99e6062018-10-16 15:13:09 -07001305 // horizontal unusable area: margin width + menu indent
1306 size_t max_width = ScreenWidth() - margin_width_ - kMenuIndent;
1307 // vertical unusable area: margin height + title lines + helper message + high light bar.
1308 // It is safe to reserve more space.
1309 size_t max_height = ScreenHeight() - margin_height_ - char_height_ * (title_lines_.size() + 3);
1310 if (GraphicMenu::Validate(max_width, max_height, graphic_header, graphic_items)) {
1311 return std::make_unique<GraphicMenu>(graphic_header, graphic_items, initial_selection, *this);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001312 }
Tianjie Xub99e6062018-10-16 15:13:09 -07001313
1314 fprintf(stderr, "Failed to initialize graphic menu, falling back to use the text menu.\n");
1315
1316 return CreateMenu(text_headers, text_items, initial_selection);
1317}
1318
1319std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu(const std::vector<std::string>& text_headers,
1320 const std::vector<std::string>& text_items,
1321 size_t initial_selection) const {
Alessandro Astonee8754752020-03-09 23:17:50 +01001322 int menu_char_width = MenuCharWidth();
1323 int menu_char_height = MenuCharHeight();
Alessandro Astonee8754752020-03-09 23:17:50 +01001324 int menu_cols = (ScreenWidth() - margin_width_*2 - kMenuIndent) / menu_char_width;
1325 bool wrap_selection = !HasThreeButtons() && !HasTouchScreen();
Alessandro Astone3525c4d2020-06-25 19:54:18 +02001326 return std::make_unique<TextMenu>(wrap_selection, menu_cols, text_headers, text_items,
Alessandro Astonee8754752020-03-09 23:17:50 +01001327 initial_selection, menu_char_height, *menu_draw_funcs_);
Doug Zongker211aebc2011-10-28 15:13:10 -07001328}
1329
1330int ScreenRecoveryUI::SelectMenu(int sel) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001331 std::lock_guard<std::mutex> lg(updateMutex);
Tianjie Xu5fe5eb62018-03-20 16:07:39 -07001332 if (menu_) {
1333 int old_sel = menu_->selection();
1334 sel = menu_->Select(sel);
Elliott Hughesfc06f872015-03-23 13:45:31 -07001335
Tianjie Xu5fe5eb62018-03-20 16:07:39 -07001336 if (sel != old_sel) {
1337 update_screen_locked();
1338 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001339 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001340 return sel;
Doug Zongker211aebc2011-10-28 15:13:10 -07001341}
1342
Alessandro Astone4a00e642022-03-21 17:14:47 +01001343int ScreenRecoveryUI::SelectMenu(const Point& p) {
1344 // Correct position for overscan
1345 const Point point(p.x() - gr_overscan_offset_x(), p.y() - gr_overscan_offset_y());
1346
Tom Marshall53176c72020-03-10 20:17:49 +01001347 int new_sel = Device::kNoAction;
1348 std::lock_guard<std::mutex> lg(updateMutex);
1349 if (menu_) {
1350 if (!menu_->IsMain()) {
1351 // Back arrow hitbox
1352 const static int logo_width = gr_get_width(lineage_logo_.get());
1353 const static int logo_height = gr_get_height(lineage_logo_.get());
1354 const static int icon_w = gr_get_width(back_icon_.get());
1355 const static int icon_h = gr_get_height(back_icon_.get());
1356 const static int centered_x = ScreenWidth() / 2 - logo_width / 2;
1357 const static int icon_x = centered_x / 2 - icon_w / 2;
1358 const static int icon_y = margin_height_ + logo_height / 2 - icon_h / 2;
1359
1360 if (point.x() >= icon_x && point.x() <= icon_x + icon_w &&
1361 point.y() >= icon_y && point.y() <= icon_y + icon_h) {
1362 return Device::kGoBack;
1363 }
1364 }
1365
1366 if (point.y() >= menu_start_y_ &&
1367 point.y() < menu_start_y_ + menu_->ItemsCount() * MenuItemHeight()) {
1368 int old_sel = menu_->selection();
1369 int relative_sel = (point.y() - menu_start_y_) / MenuItemHeight();
1370 new_sel = menu_->SelectVisible(relative_sel);
1371 if (new_sel != -1 && new_sel != old_sel) {
1372 update_screen_locked();
1373 }
1374 }
1375 }
1376 return new_sel;
1377}
1378
1379int ScreenRecoveryUI::ScrollMenu(int updown) {
1380 std::lock_guard<std::mutex> lg(updateMutex);
1381 int sel = Device::kNoAction;
1382 if (menu_) {
1383 sel = menu_->Scroll(updown);
1384 update_screen_locked();
1385 }
1386 return sel;
1387}
1388
Tianjie Xub99e6062018-10-16 15:13:09 -07001389size_t ScreenRecoveryUI::ShowMenu(std::unique_ptr<Menu>&& menu, bool menu_only,
Tom Marshall55220ba2019-01-04 14:37:31 -08001390 const std::function<int(int, bool)>& key_handler,
1391 bool refreshable) {
Tao Bao3aec6962018-04-20 09:24:58 -07001392 // Throw away keys pressed previously, so user doesn't accidentally trigger menu items.
1393 FlushKeys();
1394
Jerry Zhangb76af932018-05-22 12:08:35 -07001395 // If there is a key interrupt in progress, return KeyError::INTERRUPTED without starting the
1396 // menu.
1397 if (IsKeyInterrupted()) return static_cast<size_t>(KeyError::INTERRUPTED);
1398
Tianjie Xub99e6062018-10-16 15:13:09 -07001399 CHECK(menu != nullptr);
Tao Bao3aec6962018-04-20 09:24:58 -07001400
Tianjie Xub99e6062018-10-16 15:13:09 -07001401 // Starts and displays the menu
1402 menu_ = std::move(menu);
1403 Redraw();
1404
1405 int selected = menu_->selection();
Tao Bao3aec6962018-04-20 09:24:58 -07001406 int chosen_item = -1;
1407 while (chosen_item < 0) {
Tom Marshall53176c72020-03-10 20:17:49 +01001408 InputEvent evt = WaitInputEvent();
1409 if (evt.type() == EventType::EXTRA) {
1410 if (evt.key() == static_cast<int>(KeyError::INTERRUPTED)) {
1411 // WaitKey() was interrupted.
1412 return static_cast<size_t>(KeyError::INTERRUPTED);
1413 }
1414 if (evt.key() == static_cast<int>(KeyError::TIMED_OUT)) { // WaitKey() timed out.
1415 if (WasTextEverVisible()) {
1416 continue;
1417 } else {
1418 LOG(INFO) << "Timed out waiting for key input; rebooting.";
1419 menu_.reset();
1420 Redraw();
1421 return static_cast<size_t>(KeyError::TIMED_OUT);
1422 }
Tao Bao3aec6962018-04-20 09:24:58 -07001423 }
1424 }
1425
Tom Marshall53176c72020-03-10 20:17:49 +01001426 int action = Device::kNoAction;
1427 if (evt.type() == EventType::TOUCH) {
1428 int touch_sel = SelectMenu(evt.pos());
1429 if (touch_sel < 0) {
1430 action = touch_sel;
1431 } else {
1432 action = Device::kInvokeItem;
1433 selected = touch_sel;
1434 }
1435 } else {
1436 bool visible = IsTextVisible();
1437 action = key_handler(evt.key(), visible);
1438 }
Tom Marshall55220ba2019-01-04 14:37:31 -08001439
Tao Bao3aec6962018-04-20 09:24:58 -07001440 if (action < 0) {
1441 switch (action) {
1442 case Device::kHighlightUp:
1443 selected = SelectMenu(--selected);
1444 break;
1445 case Device::kHighlightDown:
1446 selected = SelectMenu(++selected);
1447 break;
Tom Marshall53176c72020-03-10 20:17:49 +01001448 case Device::kScrollUp:
1449 selected = ScrollMenu(-1);
1450 break;
1451 case Device::kScrollDown:
1452 selected = ScrollMenu(1);
1453 break;
Tao Bao3aec6962018-04-20 09:24:58 -07001454 case Device::kInvokeItem:
Alessandro Astonee8754752020-03-09 23:17:50 +01001455 if (selected < 0) {
1456 chosen_item = Device::kGoBack;
1457 } else {
1458 chosen_item = selected;
1459 }
Tao Bao3aec6962018-04-20 09:24:58 -07001460 break;
1461 case Device::kNoAction:
1462 break;
Tom Marshalld13a8c52017-08-24 13:50:01 +00001463 case Device::kGoBack:
1464 chosen_item = Device::kGoBack;
1465 break;
1466 case Device::kGoHome:
1467 chosen_item = Device::kGoHome;
1468 break;
Tom Marshall90e09f82018-12-17 15:57:44 -08001469 case Device::kDoSideload:
1470 chosen_item = Device::kDoSideload;
1471 break;
Tom Marshall55220ba2019-01-04 14:37:31 -08001472 case Device::kRefresh:
1473 if (refreshable) {
1474 chosen_item = Device::kRefresh;
1475 }
1476 break;
Tao Bao3aec6962018-04-20 09:24:58 -07001477 }
1478 } else if (!menu_only) {
1479 chosen_item = action;
1480 }
Tom Marshall90e09f82018-12-17 15:57:44 -08001481
1482 if (chosen_item == Device::kGoBack || chosen_item == Device::kGoHome ||
Tom Marshall55220ba2019-01-04 14:37:31 -08001483 chosen_item == Device::kDoSideload || chosen_item == Device::kRefresh) {
Tom Marshalld13a8c52017-08-24 13:50:01 +00001484 break;
1485 }
Tao Bao3aec6962018-04-20 09:24:58 -07001486 }
1487
Tianjie Xub99e6062018-10-16 15:13:09 -07001488 menu_.reset();
Tianjie Xub99e6062018-10-16 15:13:09 -07001489
Tao Bao3aec6962018-04-20 09:24:58 -07001490 return chosen_item;
1491}
1492
Tianjie Xub99e6062018-10-16 15:13:09 -07001493size_t ScreenRecoveryUI::ShowMenu(const std::vector<std::string>& headers,
1494 const std::vector<std::string>& items, size_t initial_selection,
1495 bool menu_only,
Tom Marshall55220ba2019-01-04 14:37:31 -08001496 const std::function<int(int, bool)>& key_handler,
1497 bool refreshable) {
Tianjie Xub99e6062018-10-16 15:13:09 -07001498 auto menu = CreateMenu(headers, items, initial_selection);
1499 if (menu == nullptr) {
1500 return initial_selection;
1501 }
1502
Tom Marshall55220ba2019-01-04 14:37:31 -08001503 return ShowMenu(std::move(menu), menu_only, key_handler, refreshable);
Tianjie Xub99e6062018-10-16 15:13:09 -07001504}
1505
1506size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector<std::string>& backup_headers,
1507 const std::vector<std::string>& backup_items,
1508 const std::function<int(int, bool)>& key_handler) {
Alessandro Astone5ed22c82020-10-05 15:11:36 +02001509 auto wipe_data_menu = CreateMenu(backup_headers, backup_items, 0);
Tianjie Xub99e6062018-10-16 15:13:09 -07001510 if (wipe_data_menu == nullptr) {
1511 return 0;
1512 }
1513
1514 return ShowMenu(std::move(wipe_data_menu), true, key_handler);
1515}
1516
Tianjie Xu1a0a30a2018-10-25 15:22:07 -07001517size_t ScreenRecoveryUI::ShowPromptWipeDataConfirmationMenu(
1518 const std::vector<std::string>& backup_headers, const std::vector<std::string>& backup_items,
1519 const std::function<int(int, bool)>& key_handler) {
1520 auto confirmation_menu =
1521 CreateMenu(wipe_data_confirmation_text_.get(),
1522 { cancel_wipe_data_text_.get(), factory_data_reset_text_.get() }, backup_headers,
1523 backup_items, 0);
1524 if (confirmation_menu == nullptr) {
1525 return 0;
1526 }
1527
1528 return ShowMenu(std::move(confirmation_menu), true, key_handler);
1529}
1530
Elliott Hughes8de52072015-04-08 20:06:50 -07001531bool ScreenRecoveryUI::IsTextVisible() {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001532 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001533 int visible = show_text;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001534 return visible;
Doug Zongker211aebc2011-10-28 15:13:10 -07001535}
1536
Elliott Hughes8de52072015-04-08 20:06:50 -07001537bool ScreenRecoveryUI::WasTextEverVisible() {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001538 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001539 int ever_visible = show_text_ever;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001540 return ever_visible;
Doug Zongker211aebc2011-10-28 15:13:10 -07001541}
1542
Elliott Hughes8de52072015-04-08 20:06:50 -07001543void ScreenRecoveryUI::ShowText(bool visible) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001544 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001545 show_text = visible;
1546 if (show_text) show_text_ever = true;
1547 update_screen_locked();
Doug Zongker211aebc2011-10-28 15:13:10 -07001548}
Doug Zongkerc0441d12013-07-31 11:28:24 -07001549
Elliott Hughes8de52072015-04-08 20:06:50 -07001550void ScreenRecoveryUI::Redraw() {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001551 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001552 update_screen_locked();
Doug Zongkerc0441d12013-07-31 11:28:24 -07001553}
Elliott Hughes642aaa72015-04-10 12:47:46 -07001554
1555void ScreenRecoveryUI::KeyLongPress(int) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001556 // Redraw so that if we're in the menu, the highlight
1557 // will change color to indicate a successful long press.
1558 Redraw();
Elliott Hughes642aaa72015-04-10 12:47:46 -07001559}
Tao Baoefb49ad2017-01-31 23:03:10 -08001560
1561void ScreenRecoveryUI::SetLocale(const std::string& new_locale) {
1562 locale_ = new_locale;
1563 rtl_locale_ = false;
1564
1565 if (!new_locale.empty()) {
Tao Bao347a6592018-05-08 15:58:29 -07001566 size_t separator = new_locale.find('-');
1567 // lang has the language prefix prior to the separator, or full string if none exists.
1568 std::string lang = new_locale.substr(0, separator);
Tao Baoefb49ad2017-01-31 23:03:10 -08001569
1570 // A bit cheesy: keep an explicit list of supported RTL languages.
1571 if (lang == "ar" || // Arabic
1572 lang == "fa" || // Persian (Farsi)
1573 lang == "he" || // Hebrew (new language code)
1574 lang == "iw" || // Hebrew (old language code)
1575 lang == "ur") { // Urdu
1576 rtl_locale_ = true;
1577 }
1578 }
1579}