blob: 1e0b6579dc65f3c3ddc715cbf8820c1d057f2913 [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
Chippa-a6c967e12023-10-19 00:06:41 +030040#include <aidl/android/hardware/health/BatteryStatus.h>
41
Mark Salyzyn30017e72020-05-13 12:39:12 -070042#include <android-base/chrono_utils.h>
Tianjie Xuc21edd42016-08-05 18:00:04 -070043#include <android-base/logging.h>
Elliott Hughescb220402016-09-23 15:30:55 -070044#include <android-base/properties.h>
Elliott Hughes4b166f02015-12-04 15:30:20 -080045#include <android-base/stringprintf.h>
Tao Baocb5524c2017-09-08 21:25:32 -070046#include <android-base/strings.h>
Tao Baob6918c72015-05-19 17:02:16 -070047
Chippa-a6c967e12023-10-19 00:06:41 +030048#include <health/utils.h>
49#include <healthd/BatteryMonitor.h>
50
Tao Bao6cd81682018-05-03 21:53:11 -070051#include "minui/minui.h"
52#include "otautil/paths.h"
Tianjie Xu8f397302018-08-20 13:40:47 -070053#include "recovery_ui/device.h"
54#include "recovery_ui/ui.h"
Doug Zongker211aebc2011-10-28 15:13:10 -070055
fredchioub44702a2022-02-11 16:39:53 +080056enum DirectRenderManager {
57 DRM_INNER,
58 DRM_OUTER,
59};
60
Doug Zongker211aebc2011-10-28 15:13:10 -070061// Return the current time as a double (including fractions of a second).
62static double now() {
Tao Bao5d2e3bd2017-06-23 22:23:50 -070063 struct timeval tv;
64 gettimeofday(&tv, nullptr);
65 return tv.tv_sec + tv.tv_usec / 1000000.0;
Doug Zongker211aebc2011-10-28 15:13:10 -070066}
67
Tianjie Xu66dbf632018-10-11 16:54:50 -070068Menu::Menu(size_t initial_selection, const DrawInterface& draw_func)
69 : selection_(initial_selection), draw_funcs_(draw_func) {}
70
Alessandro Astone0ff12c62020-03-09 23:17:50 +010071int Menu::selection() const {
Tianjie Xu66dbf632018-10-11 16:54:50 -070072 return selection_;
73}
74
Alessandro Astoneac09fbd2020-06-25 19:54:18 +020075TextMenu::TextMenu(bool wrappable, size_t max_length,
Tianjie Xu66dbf632018-10-11 16:54:50 -070076 const std::vector<std::string>& headers, const std::vector<std::string>& items,
77 size_t initial_selection, int char_height, const DrawInterface& draw_funcs)
78 : Menu(initial_selection, draw_funcs),
Alessandro Astone0ff12c62020-03-09 23:17:50 +010079 wrappable_(wrappable),
Alessandro Astoneac09fbd2020-06-25 19:54:18 +020080 calibrated_height_(false),
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070081 max_item_length_(max_length),
Tao Baoe02a5b22018-05-02 15:46:11 -070082 text_headers_(headers),
Tianjie Xu66dbf632018-10-11 16:54:50 -070083 char_height_(char_height) {
Tao Baoe02a5b22018-05-02 15:46:11 -070084
Alessandro Astone0ff12c62020-03-09 23:17:50 +010085 size_t items_count = items.size();
Tao Bao1fe1afe2018-05-01 15:56:05 -070086 for (size_t i = 0; i < items_count; ++i) {
87 text_items_.emplace_back(items[i].substr(0, max_item_length_));
Tao Baoe02a5b22018-05-02 15:46:11 -070088 }
89
90 CHECK(!text_items_.empty());
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070091}
92
Tianjie Xu66dbf632018-10-11 16:54:50 -070093const std::vector<std::string>& TextMenu::text_headers() const {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070094 return text_headers_;
95}
96
Tianjie Xu66dbf632018-10-11 16:54:50 -070097std::string TextMenu::TextItem(size_t index) const {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070098 CHECK_LT(index, text_items_.size());
99
100 return text_items_[index];
101}
102
Tianjie Xu66dbf632018-10-11 16:54:50 -0700103size_t TextMenu::MenuStart() const {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700104 return menu_start_;
105}
106
Tianjie Xu66dbf632018-10-11 16:54:50 -0700107size_t TextMenu::MenuEnd() const {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700108 return std::min(ItemsCount(), menu_start_ + max_display_items_);
109}
110
Tianjie Xu66dbf632018-10-11 16:54:50 -0700111size_t TextMenu::ItemsCount() const {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700112 return text_items_.size();
113}
114
Tianjie Xu66dbf632018-10-11 16:54:50 -0700115bool TextMenu::ItemsOverflow(std::string* cur_selection_str) const {
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100116 if (ItemsCount() <= max_display_items_) {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700117 return false;
118 }
119
120 *cur_selection_str =
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100121 android::base::StringPrintf("Current item: %d/%zu", selection_ + 1, ItemsCount());
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700122 return true;
123}
124
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700125// TODO(xunchang) modify the function parameters to button up & down.
Tianjie Xu66dbf632018-10-11 16:54:50 -0700126int TextMenu::Select(int sel) {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700127 CHECK_LE(ItemsCount(), static_cast<size_t>(std::numeric_limits<int>::max()));
128 int count = ItemsCount();
129
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100130 int min = IsMain() ? 0 : -1; // -1 is back arrow
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700131
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100132 if (sel < min) {
133 selection_ = wrappable() ? count - 1 : min;
134 } else if (sel >= count) {
135 selection_ = wrappable() ? min : count - 1;
136 } else {
137 selection_ = sel;
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700138 }
139
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100140 if (selection_ >= 0) {
141 if (selection_ < menu_start_) {
142 menu_start_ = selection_;
143 } else if (static_cast<size_t>(selection_) >= MenuEnd()) {
144 menu_start_ = selection_ - max_display_items_ + 1;
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700145 }
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700146 }
147
148 return selection_;
149}
150
Alessandro Astone2dfa4522020-10-04 18:11:40 +0200151int TextMenu::SelectVisible(int relative_sel) {
152 int sel = relative_sel;
153 if (menu_start_ > 0) {
154 sel += menu_start_;
155 }
156
157 return Select(sel);
158}
159
160int TextMenu::Scroll(int updown) {
161 if ((updown > 0 && menu_start_ + max_display_items_ < ItemsCount()) ||
162 (updown < 0 && menu_start_ > 0)) {
163 menu_start_ += updown;
164
165 /* We can receive a kInvokeItem event from a different source than touch,
166 like from Power button. For this reason, selection should not get out of
167 the screen. Constrain it to the first or last visible item of the list */
168 if (selection_ < menu_start_) {
169 selection_ = menu_start_;
170 } else if (selection_ >= menu_start_ + max_display_items_) {
171 selection_ = menu_start_ + max_display_items_ - 1;
172 }
173 }
174 return selection_;
175}
176
Tianjie Xu66dbf632018-10-11 16:54:50 -0700177int TextMenu::DrawHeader(int x, int y) const {
178 int offset = 0;
179
180 draw_funcs_.SetColor(UIElement::HEADER);
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100181 offset += draw_funcs_.DrawWrappedTextLines(x, y + offset, text_headers());
Tianjie Xu66dbf632018-10-11 16:54:50 -0700182
183 return offset;
184}
185
186int TextMenu::DrawItems(int x, int y, int screen_width, bool long_press) const {
187 int offset = 0;
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100188 int padding = draw_funcs_.MenuItemPadding();
Tianjie Xu66dbf632018-10-11 16:54:50 -0700189
190 draw_funcs_.SetColor(UIElement::MENU);
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100191 offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4;
192
193 int item_container_offset = offset; // store it for drawing scrollbar on most top
194
Ben Fennema3cd64912023-11-08 19:14:52 +0000195 for (size_t i = MenuStart(); i < MenuEnd(); ++i) {
Tianjie Xu66dbf632018-10-11 16:54:50 -0700196 if (i == selection()) {
197 // Draw the highlight bar.
198 draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG);
199
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100200 int bar_height = padding + char_height_ + padding;
201 draw_funcs_.DrawHighlightBar(0, y + offset, screen_width, bar_height);
Tianjie Xu66dbf632018-10-11 16:54:50 -0700202
Jesse Chanfdab22b2020-04-28 14:49:13 +0000203 // Colored text for the selected item.
Tianjie Xu66dbf632018-10-11 16:54:50 -0700204 draw_funcs_.SetColor(UIElement::MENU_SEL_FG);
Tianjie Xu66dbf632018-10-11 16:54:50 -0700205 }
Jesse Chanfdab22b2020-04-28 14:49:13 +0000206 offset += draw_funcs_.DrawTextLine(x, y + offset, TextItem(i), false /* bold */);
Tianjie Xu66dbf632018-10-11 16:54:50 -0700207
208 draw_funcs_.SetColor(UIElement::MENU);
209 }
210 offset += draw_funcs_.DrawHorizontalRule(y + offset);
211
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100212 std::string unused;
213 if (ItemsOverflow(&unused)) {
214 int container_height = max_display_items_ * (2 * padding + char_height_);
215 int bar_height = container_height / (text_items_.size() - max_display_items_ + 1);
216 int start_y = y + item_container_offset + bar_height * menu_start_;
217 draw_funcs_.SetColor(UIElement::SCROLLBAR);
218 draw_funcs_.DrawScrollBar(start_y, bar_height);
219 }
220
Tianjie Xu66dbf632018-10-11 16:54:50 -0700221 return offset;
222}
223
Tao Baoda409fb2018-10-21 23:36:26 -0700224GraphicMenu::GraphicMenu(const GRSurface* graphic_headers,
225 const std::vector<const GRSurface*>& graphic_items,
Tianjie Xub99e6062018-10-16 15:13:09 -0700226 size_t initial_selection, const DrawInterface& draw_funcs)
Tao Baoda409fb2018-10-21 23:36:26 -0700227 : Menu(initial_selection, draw_funcs) {
228 graphic_headers_ = graphic_headers->Clone();
229 graphic_items_.reserve(graphic_items.size());
230 for (const auto& item : graphic_items) {
231 graphic_items_.emplace_back(item->Clone());
232 }
233}
Tianjie Xu66dbf632018-10-11 16:54:50 -0700234
235int GraphicMenu::Select(int sel) {
236 CHECK_LE(graphic_items_.size(), static_cast<size_t>(std::numeric_limits<int>::max()));
237 int count = graphic_items_.size();
238
239 // Wraps the selection at boundary if the menu is not scrollable.
240 if (sel < 0) {
241 selection_ = count - 1;
242 } else if (sel >= count) {
243 selection_ = 0;
244 } else {
245 selection_ = sel;
246 }
247
248 return selection_;
249}
250
251int GraphicMenu::DrawHeader(int x, int y) const {
Tianjie Xub99e6062018-10-16 15:13:09 -0700252 draw_funcs_.SetColor(UIElement::HEADER);
Tao Baoda409fb2018-10-21 23:36:26 -0700253 draw_funcs_.DrawTextIcon(x, y, graphic_headers_.get());
Tianjie Xu66dbf632018-10-11 16:54:50 -0700254 return graphic_headers_->height;
255}
256
257int GraphicMenu::DrawItems(int x, int y, int screen_width, bool long_press) const {
258 int offset = 0;
259
260 draw_funcs_.SetColor(UIElement::MENU);
261 offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4;
262
263 for (size_t i = 0; i < graphic_items_.size(); i++) {
264 auto& item = graphic_items_[i];
265 if (i == selection_) {
266 draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG);
267
268 int bar_height = item->height + 4;
269 draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height);
270
271 // Bold white text for the selected item.
272 draw_funcs_.SetColor(UIElement::MENU_SEL_FG);
273 }
Tao Baoda409fb2018-10-21 23:36:26 -0700274 draw_funcs_.DrawTextIcon(x, y + offset, item.get());
Tianjie Xu66dbf632018-10-11 16:54:50 -0700275 offset += item->height;
276
277 draw_funcs_.SetColor(UIElement::MENU);
278 }
xunchangc7dbc732018-12-20 11:31:18 -0800279 offset += draw_funcs_.DrawHorizontalRule(y + offset);
Tianjie Xu66dbf632018-10-11 16:54:50 -0700280
281 return offset;
282}
283
Alessandro Astone2dfa4522020-10-04 18:11:40 +0200284size_t GraphicMenu::ItemsCount() const {
285 return graphic_items_.size();
286}
287
Tao Baoda409fb2018-10-21 23:36:26 -0700288bool GraphicMenu::Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers,
289 const std::vector<const GRSurface*>& graphic_items) {
Tianjie Xu66dbf632018-10-11 16:54:50 -0700290 int offset = 0;
Tianjie Xub99e6062018-10-16 15:13:09 -0700291 if (!ValidateGraphicSurface(max_width, max_height, offset, graphic_headers)) {
Tianjie Xu66dbf632018-10-11 16:54:50 -0700292 return false;
293 }
Tianjie Xub99e6062018-10-16 15:13:09 -0700294 offset += graphic_headers->height;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700295
Tianjie Xub99e6062018-10-16 15:13:09 -0700296 for (const auto& item : graphic_items) {
297 if (!ValidateGraphicSurface(max_width, max_height, offset, item)) {
Tianjie Xu66dbf632018-10-11 16:54:50 -0700298 return false;
299 }
300 offset += item->height;
301 }
302
303 return true;
304}
305
Tianjie Xub99e6062018-10-16 15:13:09 -0700306bool GraphicMenu::ValidateGraphicSurface(size_t max_width, size_t max_height, int y,
307 const GRSurface* surface) {
Tianjie Xu66dbf632018-10-11 16:54:50 -0700308 if (!surface) {
Tao Baoa00b4492019-01-16 09:29:47 -0800309 fprintf(stderr, "Graphic surface can not be null\n");
Tianjie Xu66dbf632018-10-11 16:54:50 -0700310 return false;
311 }
312
313 if (surface->pixel_bytes != 1 || surface->width != surface->row_bytes) {
Tao Baoa00b4492019-01-16 09:29:47 -0800314 fprintf(stderr, "Invalid graphic surface, pixel bytes: %zu, width: %zu row_bytes: %zu\n",
Tianjie Xu66dbf632018-10-11 16:54:50 -0700315 surface->pixel_bytes, surface->width, surface->row_bytes);
316 return false;
317 }
318
Tianjie Xub99e6062018-10-16 15:13:09 -0700319 if (surface->width > max_width || surface->height > max_height - y) {
Tianjie Xu66dbf632018-10-11 16:54:50 -0700320 fprintf(stderr,
Tao Baodd789822018-11-26 16:28:07 -0800321 "Graphic surface doesn't fit into the screen. width: %zu, height: %zu, max_width: %zu,"
Tianjie Xu66dbf632018-10-11 16:54:50 -0700322 " max_height: %zu, vertical offset: %d\n",
Tianjie Xub99e6062018-10-16 15:13:09 -0700323 surface->width, surface->height, max_width, max_height, y);
Tianjie Xu66dbf632018-10-11 16:54:50 -0700324 return false;
325 }
326
327 return true;
328}
329
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100330MenuDrawFunctions::MenuDrawFunctions(const DrawInterface& wrappee)
331 : wrappee_(wrappee) {
332}
333
334int MenuDrawFunctions::DrawTextLine(int x, int y, const std::string& line, bool bold) const {
335 gr_text(gr_menu_font(), x, y + MenuItemPadding(), line.c_str(), bold);
336 return 2 * MenuItemPadding() + MenuCharHeight();
337}
338
339int MenuDrawFunctions::DrawTextLines(int x, int y, const std::vector<std::string>& lines) const {
340 int offset = 0;
341 for (const auto& line : lines) {
342 offset += DrawTextLine(x, y + offset, line, false);
343 }
344 return offset;
345}
346
347int MenuDrawFunctions::DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const {
Alessandro Astonee76e5702020-10-01 16:45:53 +0200348 const int padding = MenuItemPadding() / 2;
349
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100350 // Keep symmetrical margins based on the given offset (i.e. x).
351 size_t text_cols = (gr_fb_width() - x * 2) / MenuCharWidth();
352 int offset = 0;
353 for (const auto& line : lines) {
354 size_t next_start = 0;
355 while (next_start < line.size()) {
356 std::string sub = line.substr(next_start, text_cols + 1);
357 if (sub.size() <= text_cols) {
358 next_start += sub.size();
359 } else {
360 // Line too long and must be wrapped to text_cols columns.
361 size_t last_space = sub.find_last_of(" \t\n");
362 if (last_space == std::string::npos) {
363 // No space found, just draw as much as we can.
364 sub.resize(text_cols);
365 next_start += text_cols;
366 } else {
367 sub.resize(last_space);
368 next_start += last_space + 1;
369 }
370 }
Alessandro Astonee76e5702020-10-01 16:45:53 +0200371 offset += DrawTextLine(x, y + offset, sub, false) - (2 * MenuItemPadding() - padding);
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100372 }
373 }
Alessandro Astonee76e5702020-10-01 16:45:53 +0200374 if (!lines.empty()) {
375 offset += 2 * MenuItemPadding() - padding;
376 }
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100377 return offset;
378}
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700379
Tao Bao0bc88de2018-07-31 14:53:16 -0700380constexpr int kDefaultMarginHeight = 0;
381constexpr int kDefaultMarginWidth = 0;
382constexpr int kDefaultAnimationFps = 30;
383
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100384ScreenRecoveryUI::ScreenRecoveryUI()
Tao Bao0bc88de2018-07-31 14:53:16 -0700385 : margin_width_(
386 android::base::GetIntProperty("ro.recovery.ui.margin_width", kDefaultMarginWidth)),
387 margin_height_(
388 android::base::GetIntProperty("ro.recovery.ui.margin_height", kDefaultMarginHeight)),
389 animation_fps_(
390 android::base::GetIntProperty("ro.recovery.ui.animation_fps", kDefaultAnimationFps)),
391 density_(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f),
Michael Bestas99407d12019-03-23 17:28:22 +0200392 blank_unblank_on_init_(
393 android::base::GetBoolProperty("ro.recovery.ui.blank_unblank_on_init", false)),
Tao Baoda409fb2018-10-21 23:36:26 -0700394 current_icon_(NONE),
395 current_frame_(0),
396 intro_done_(false),
Tao Bao736d59c2017-01-03 10:15:33 -0800397 progressBarType(EMPTY),
398 progressScopeStart(0),
399 progressScopeSize(0),
400 progress(0),
401 pagesIdentical(false),
402 text_cols_(0),
403 text_rows_(0),
404 text_(nullptr),
405 text_col_(0),
406 text_row_(0),
Tao Bao736d59c2017-01-03 10:15:33 -0800407 show_text(false),
408 show_text_ever(false),
Tao Bao736d59c2017-01-03 10:15:33 -0800409 file_viewer_text_(nullptr),
Tao Bao736d59c2017-01-03 10:15:33 -0800410 stage(-1),
411 max_stage(-1),
Tao Baoefb49ad2017-01-31 23:03:10 -0800412 locale_(""),
fredchioub44702a2022-02-11 16:39:53 +0800413 rtl_locale_(false),
Chippa-a6c967e12023-10-19 00:06:41 +0300414 batt_capacity_(0),
415 charging_(false),
fredchioub44702a2022-02-11 16:39:53 +0800416 is_graphics_available(false) {}
Doug Zongker211aebc2011-10-28 15:13:10 -0700417
Tao Bao26ea9592018-05-09 16:32:02 -0700418ScreenRecoveryUI::~ScreenRecoveryUI() {
Chippa-a6c967e12023-10-19 00:06:41 +0300419 batt_monitor_thread_stopped_ = true;
420 if (batt_monitor_thread_.joinable()) {
421 batt_monitor_thread_.join();
422 }
423
Tao Bao26ea9592018-05-09 16:32:02 -0700424 progress_thread_stopped_ = true;
Tao Bao94371fd2018-06-06 07:38:54 -0700425 if (progress_thread_.joinable()) {
426 progress_thread_.join();
427 }
Tao Bao60ac6222018-06-13 14:33:51 -0700428 // No-op if gr_init() (via Init()) was not called or had failed.
429 gr_exit();
Tao Bao26ea9592018-05-09 16:32:02 -0700430}
431
Tao Baoda409fb2018-10-21 23:36:26 -0700432const GRSurface* ScreenRecoveryUI::GetCurrentFrame() const {
433 if (current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) {
434 return intro_done_ ? loop_frames_[current_frame_].get() : intro_frames_[current_frame_].get();
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700435 }
Tao Baoda409fb2018-10-21 23:36:26 -0700436 return error_icon_.get();
Elliott Hughes498cda62016-04-14 16:49:04 -0700437}
438
Tao Baoda409fb2018-10-21 23:36:26 -0700439const GRSurface* ScreenRecoveryUI::GetCurrentText() const {
440 switch (current_icon_) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700441 case ERASING:
Tao Baoda409fb2018-10-21 23:36:26 -0700442 return erasing_text_.get();
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700443 case ERROR:
Tao Baoda409fb2018-10-21 23:36:26 -0700444 return error_text_.get();
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700445 case INSTALLING_UPDATE:
Tao Baoda409fb2018-10-21 23:36:26 -0700446 return installing_text_.get();
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700447 case NO_COMMAND:
Tao Baoda409fb2018-10-21 23:36:26 -0700448 return no_command_text_.get();
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700449 case NONE:
450 abort();
451 }
Elliott Hughes498cda62016-04-14 16:49:04 -0700452}
453
Mikhail Lappob49767c2017-03-23 21:44:26 +0100454int ScreenRecoveryUI::PixelsFromDp(int dp) const {
Tao Bao0bc88de2018-07-31 14:53:16 -0700455 return dp * density_;
Elliott Hughesfaf36e02016-04-20 17:22:16 -0700456}
457
458// Here's the intended layout:
459
Elliott Hughes6d089a92016-07-08 17:23:41 -0700460// | portrait large landscape large
461// ---------+-------------------------------------------------
Tao Bao3250f722017-06-29 14:32:05 -0700462// gap |
Elliott Hughes6d089a92016-07-08 17:23:41 -0700463// icon | (200dp)
464// gap | 68dp 68dp 56dp 112dp
465// text | (14sp)
466// gap | 32dp 32dp 26dp 52dp
467// progress | (2dp)
Tao Bao3250f722017-06-29 14:32:05 -0700468// gap |
Elliott Hughesfaf36e02016-04-20 17:22:16 -0700469
Tao Bao3250f722017-06-29 14:32:05 -0700470// Note that "baseline" is actually the *top* of each icon (because that's how our drawing routines
471// 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 -0700472
Elliott Hughes6d089a92016-07-08 17:23:41 -0700473enum Layout { PORTRAIT = 0, PORTRAIT_LARGE = 1, LANDSCAPE = 2, LANDSCAPE_LARGE = 3, LAYOUT_MAX };
Tao Bao3250f722017-06-29 14:32:05 -0700474enum Dimension { TEXT = 0, ICON = 1, DIMENSION_MAX };
Elliott Hughes6d089a92016-07-08 17:23:41 -0700475static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = {
Tianjie Xu8f397302018-08-20 13:40:47 -0700476 { 32, 68 }, // PORTRAIT
477 { 32, 68 }, // PORTRAIT_LARGE
478 { 26, 56 }, // LANDSCAPE
479 { 52, 112 }, // LANDSCAPE_LARGE
Elliott Hughes6d089a92016-07-08 17:23:41 -0700480};
481
Tao Bao99b2d772017-06-23 22:47:03 -0700482int ScreenRecoveryUI::GetAnimationBaseline() const {
Tao Baoda409fb2018-10-21 23:36:26 -0700483 return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) -
484 gr_get_height(loop_frames_[0].get());
Elliott Hughesfaf36e02016-04-20 17:22:16 -0700485}
486
Tao Bao99b2d772017-06-23 22:47:03 -0700487int ScreenRecoveryUI::GetTextBaseline() const {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700488 return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) -
Tao Baoda409fb2018-10-21 23:36:26 -0700489 gr_get_height(installing_text_.get());
Elliott Hughesfaf36e02016-04-20 17:22:16 -0700490}
491
Tao Bao99b2d772017-06-23 22:47:03 -0700492int ScreenRecoveryUI::GetProgressBaseline() const {
Tao Baoda409fb2018-10-21 23:36:26 -0700493 int elements_sum = gr_get_height(loop_frames_[0].get()) + PixelsFromDp(kLayouts[layout_][ICON]) +
494 gr_get_height(installing_text_.get()) + PixelsFromDp(kLayouts[layout_][TEXT]) +
495 gr_get_height(progress_bar_fill_.get());
Luke Song92eda4d2017-09-19 10:51:35 -0700496 int bottom_gap = (ScreenHeight() - elements_sum) / 2;
Tao Baoda409fb2018-10-21 23:36:26 -0700497 return ScreenHeight() - bottom_gap - gr_get_height(progress_bar_fill_.get());
Elliott Hughesfaf36e02016-04-20 17:22:16 -0700498}
499
Doug Zongker211aebc2011-10-28 15:13:10 -0700500// Clear the screen and draw the currently selected background icon (if any).
501// Should only be called with updateMutex locked.
Elliott Hughes498cda62016-04-14 16:49:04 -0700502void ScreenRecoveryUI::draw_background_locked() {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700503 pagesIdentical = false;
504 gr_color(0, 0, 0, 255);
505 gr_clear();
Tao Baoda409fb2018-10-21 23:36:26 -0700506 if (current_icon_ != NONE) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700507 if (max_stage != -1) {
Tao Baoda409fb2018-10-21 23:36:26 -0700508 int stage_height = gr_get_height(stage_marker_empty_.get());
509 int stage_width = gr_get_width(stage_marker_empty_.get());
510 int x = (ScreenWidth() - max_stage * gr_get_width(stage_marker_empty_.get())) / 2;
Tao Bao0bc88de2018-07-31 14:53:16 -0700511 int y = ScreenHeight() - stage_height - margin_height_;
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700512 for (int i = 0; i < max_stage; ++i) {
Tao Baoda409fb2018-10-21 23:36:26 -0700513 const auto& stage_surface = (i < stage) ? stage_marker_fill_ : stage_marker_empty_;
514 DrawSurface(stage_surface.get(), 0, 0, stage_width, stage_height, x, y);
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700515 x += stage_width;
516 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700517 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700518
Tao Baoda409fb2018-10-21 23:36:26 -0700519 const auto& text_surface = GetCurrentText();
Luke Song92eda4d2017-09-19 10:51:35 -0700520 int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2;
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700521 int text_y = GetTextBaseline();
522 gr_color(255, 255, 255, 255);
Luke Song92eda4d2017-09-19 10:51:35 -0700523 DrawTextIcon(text_x, text_y, text_surface);
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700524 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700525}
526
Tao Baoea78d862017-06-28 14:52:17 -0700527// Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be
528// called with updateMutex locked.
Elliott Hughesfaf36e02016-04-20 17:22:16 -0700529void ScreenRecoveryUI::draw_foreground_locked() {
Tao Baoda409fb2018-10-21 23:36:26 -0700530 if (current_icon_ != NONE) {
531 const auto& frame = GetCurrentFrame();
Tao Bao736d59c2017-01-03 10:15:33 -0800532 int frame_width = gr_get_width(frame);
533 int frame_height = gr_get_height(frame);
Luke Song92eda4d2017-09-19 10:51:35 -0700534 int frame_x = (ScreenWidth() - frame_width) / 2;
Tao Bao736d59c2017-01-03 10:15:33 -0800535 int frame_y = GetAnimationBaseline();
zhang sanshan0d81b892019-08-07 16:21:10 +0800536 if (frame_x >= 0 && frame_y >= 0 && (frame_x + frame_width) < ScreenWidth() &&
537 (frame_y + frame_height) < ScreenHeight())
538 DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
Tao Bao736d59c2017-01-03 10:15:33 -0800539 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700540
Tao Bao736d59c2017-01-03 10:15:33 -0800541 if (progressBarType != EMPTY) {
Tao Baoda409fb2018-10-21 23:36:26 -0700542 int width = gr_get_width(progress_bar_empty_.get());
543 int height = gr_get_height(progress_bar_empty_.get());
Doug Zongker211aebc2011-10-28 15:13:10 -0700544
Luke Song92eda4d2017-09-19 10:51:35 -0700545 int progress_x = (ScreenWidth() - width) / 2;
Tao Bao736d59c2017-01-03 10:15:33 -0800546 int progress_y = GetProgressBaseline();
Doug Zongker211aebc2011-10-28 15:13:10 -0700547
Tao Bao736d59c2017-01-03 10:15:33 -0800548 // Erase behind the progress bar (in case this was a progress-only update)
549 gr_color(0, 0, 0, 255);
Luke Song92eda4d2017-09-19 10:51:35 -0700550 DrawFill(progress_x, progress_y, width, height);
Doug Zongker211aebc2011-10-28 15:13:10 -0700551
Tao Bao736d59c2017-01-03 10:15:33 -0800552 if (progressBarType == DETERMINATE) {
553 float p = progressScopeStart + progress * progressScopeSize;
554 int pos = static_cast<int>(p * width);
Doug Zongker211aebc2011-10-28 15:13:10 -0700555
Tao Bao736d59c2017-01-03 10:15:33 -0800556 if (rtl_locale_) {
557 // Fill the progress bar from right to left.
558 if (pos > 0) {
Tao Baoda409fb2018-10-21 23:36:26 -0700559 DrawSurface(progress_bar_fill_.get(), width - pos, 0, pos, height,
560 progress_x + width - pos, progress_y);
Doug Zongker211aebc2011-10-28 15:13:10 -0700561 }
Tao Bao736d59c2017-01-03 10:15:33 -0800562 if (pos < width - 1) {
Tao Baoda409fb2018-10-21 23:36:26 -0700563 DrawSurface(progress_bar_empty_.get(), 0, 0, width - pos, height, progress_x, progress_y);
Tao Bao736d59c2017-01-03 10:15:33 -0800564 }
565 } else {
566 // Fill the progress bar from left to right.
567 if (pos > 0) {
Tao Baoda409fb2018-10-21 23:36:26 -0700568 DrawSurface(progress_bar_fill_.get(), 0, 0, pos, height, progress_x, progress_y);
Tao Bao736d59c2017-01-03 10:15:33 -0800569 }
570 if (pos < width - 1) {
Tao Baoda409fb2018-10-21 23:36:26 -0700571 DrawSurface(progress_bar_empty_.get(), pos, 0, width - pos, height, progress_x + pos,
572 progress_y);
Tao Bao736d59c2017-01-03 10:15:33 -0800573 }
574 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700575 }
Tao Bao736d59c2017-01-03 10:15:33 -0800576 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700577}
578
Jesse Chanfdab22b2020-04-28 14:49:13 +0000579/* recovery dark: #7C4DFF
580 recovery light: #F890FF
581 fastbootd dark: #E65100
582 fastboot light: #FDD835 */
Tao Bao99b2d772017-06-23 22:47:03 -0700583void ScreenRecoveryUI::SetColor(UIElement e) const {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700584 switch (e) {
Chippa-a6c967e12023-10-19 00:06:41 +0300585 case UIElement::BATTERY_LOW:
586 if (fastbootd_logo_enabled_)
587 gr_color(0xfd, 0x35, 0x35, 255);
588 else
589 gr_color(0xc7, 0x15, 0x85, 255);
590 break;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700591 case UIElement::INFO:
Richard Hansen19068492020-05-04 15:58:49 -0400592 if (fastbootd_logo_enabled_)
593 gr_color(0xfd, 0xd8, 0x35, 255);
594 else
595 gr_color(0xf8, 0x90, 0xff, 255);
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700596 break;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700597 case UIElement::HEADER:
Jesse Chanfdab22b2020-04-28 14:49:13 +0000598 if (fastbootd_logo_enabled_)
599 gr_color(0xfd, 0xd8,0x35, 255);
600 else
601 gr_color(0xf8, 0x90, 0xff, 255);
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700602 break;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700603 case UIElement::MENU:
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100604 gr_color(0xd8, 0xd8, 0xd8, 255);
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700605 break;
Jesse Chanfdab22b2020-04-28 14:49:13 +0000606 case UIElement::MENU_SEL_BG:
607 case UIElement::SCROLLBAR:
608 if (fastbootd_logo_enabled_)
609 gr_color(0xe6, 0x51, 0x00, 255);
610 else
611 gr_color(0x7c, 0x4d, 0xff, 255);
612 break;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700613 case UIElement::MENU_SEL_BG_ACTIVE:
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700614 gr_color(0, 156, 100, 255);
615 break;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700616 case UIElement::MENU_SEL_FG:
Jesse Chanfdab22b2020-04-28 14:49:13 +0000617 if (fastbootd_logo_enabled_)
618 gr_color(0, 0, 0, 255);
619 else
620 gr_color(0xd8, 0xd8, 0xd8, 255);
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700621 break;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700622 case UIElement::LOG:
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700623 gr_color(196, 196, 196, 255);
624 break;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700625 case UIElement::TEXT_FILL:
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700626 gr_color(0, 0, 0, 160);
627 break;
628 default:
629 gr_color(255, 255, 255, 255);
630 break;
631 }
Doug Zongkerc0441d12013-07-31 11:28:24 -0700632}
Doug Zongker211aebc2011-10-28 15:13:10 -0700633
Tianjie Xu29d55752017-09-20 17:53:46 -0700634void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector<std::string>& locales_entries,
635 size_t sel) {
636 SetLocale(locales_entries[sel]);
637 std::vector<std::string> text_name = { "erasing_text", "error_text", "installing_text",
638 "installing_security_text", "no_command_text" };
Tao Baoda409fb2018-10-21 23:36:26 -0700639 std::unordered_map<std::string, std::unique_ptr<GRSurface>> surfaces;
Tianjie Xu29d55752017-09-20 17:53:46 -0700640 for (const auto& name : text_name) {
Tao Baoda409fb2018-10-21 23:36:26 -0700641 auto text_image = LoadLocalizedBitmap(name);
Tianjie Xu29d55752017-09-20 17:53:46 -0700642 if (!text_image) {
643 Print("Failed to load %s\n", name.c_str());
644 return;
645 }
Tao Baoda409fb2018-10-21 23:36:26 -0700646 surfaces.emplace(name, std::move(text_image));
Tianjie Xu29d55752017-09-20 17:53:46 -0700647 }
648
Jerry Zhangb31f9ce2018-05-21 16:04:57 -0700649 std::lock_guard<std::mutex> lg(updateMutex);
Tianjie Xu29d55752017-09-20 17:53:46 -0700650 gr_color(0, 0, 0, 255);
651 gr_clear();
652
Tao Bao0bc88de2018-07-31 14:53:16 -0700653 int text_y = margin_height_;
654 int text_x = margin_width_;
Tianjie Xu29d55752017-09-20 17:53:46 -0700655 int line_spacing = gr_sys_font()->char_height; // Put some extra space between images.
656 // Write the header and descriptive texts.
Tianjie Xu66dbf632018-10-11 16:54:50 -0700657 SetColor(UIElement::INFO);
Tianjie Xu29d55752017-09-20 17:53:46 -0700658 std::string header = "Show background text image";
Tao Bao93e46ad2018-05-02 14:57:21 -0700659 text_y += DrawTextLine(text_x, text_y, header, true);
Tianjie Xu29d55752017-09-20 17:53:46 -0700660 std::string locale_selection = android::base::StringPrintf(
Tao Bao39c49182018-05-07 22:50:33 -0700661 "Current locale: %s, %zu/%zu", locales_entries[sel].c_str(), sel + 1, locales_entries.size());
Tao Bao93e46ad2018-05-02 14:57:21 -0700662 // clang-format off
663 std::vector<std::string> instruction = {
664 locale_selection,
665 "Use volume up/down to switch locales and power to exit."
666 };
667 // clang-format on
Tianjie Xu29d55752017-09-20 17:53:46 -0700668 text_y += DrawWrappedTextLines(text_x, text_y, instruction);
669
670 // Iterate through the text images and display them in order for the current locale.
671 for (const auto& p : surfaces) {
672 text_y += line_spacing;
Tianjie Xu66dbf632018-10-11 16:54:50 -0700673 SetColor(UIElement::LOG);
Tao Bao93e46ad2018-05-02 14:57:21 -0700674 text_y += DrawTextLine(text_x, text_y, p.first, false);
Tianjie Xu29d55752017-09-20 17:53:46 -0700675 gr_color(255, 255, 255, 255);
676 gr_texticon(text_x, text_y, p.second.get());
677 text_y += gr_get_height(p.second.get());
678 }
679 // Update the whole screen.
680 gr_flip();
Tianjie Xu29d55752017-09-20 17:53:46 -0700681}
682
Tao Bao39c49182018-05-07 22:50:33 -0700683void ScreenRecoveryUI::CheckBackgroundTextImages() {
Tianjie Xu29d55752017-09-20 17:53:46 -0700684 // Load a list of locales embedded in one of the resource files.
685 std::vector<std::string> locales_entries = get_locales_in_png("installing_text");
686 if (locales_entries.empty()) {
687 Print("Failed to load locales from the resource files\n");
688 return;
689 }
Tao Bao39c49182018-05-07 22:50:33 -0700690 std::string saved_locale = locale_;
Tianjie Xu29d55752017-09-20 17:53:46 -0700691 size_t selected = 0;
692 SelectAndShowBackgroundText(locales_entries, selected);
693
694 FlushKeys();
695 while (true) {
Alessandro Astone2dfa4522020-10-04 18:11:40 +0200696 InputEvent evt = WaitInputEvent();
697 if (evt.type() == EventType::EXTRA) {
698 if (evt.key() == static_cast<int>(KeyError::INTERRUPTED)) break;
699 }
700 if (evt.type() == EventType::KEY) {
701 if (evt.key() == KEY_POWER || evt.key() == KEY_ENTER) {
702 break;
703 } else if (evt.key() == KEY_UP || evt.key() == KEY_VOLUMEUP) {
704 selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1;
705 SelectAndShowBackgroundText(locales_entries, selected);
706 } else if (evt.key() == KEY_DOWN || evt.key() == KEY_VOLUMEDOWN) {
707 selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1;
708 SelectAndShowBackgroundText(locales_entries, selected);
709 }
Tianjie Xu29d55752017-09-20 17:53:46 -0700710 }
711 }
712
713 SetLocale(saved_locale);
714}
715
Luke Song92eda4d2017-09-19 10:51:35 -0700716int ScreenRecoveryUI::ScreenWidth() const {
717 return gr_fb_width();
718}
719
720int ScreenRecoveryUI::ScreenHeight() const {
721 return gr_fb_height();
722}
723
Tao Bao65815b62018-10-23 10:54:02 -0700724void ScreenRecoveryUI::DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx,
Luke Song92eda4d2017-09-19 10:51:35 -0700725 int dy) const {
726 gr_blit(surface, sx, sy, w, h, dx, dy);
727}
728
Tao Baoea78d862017-06-28 14:52:17 -0700729int ScreenRecoveryUI::DrawHorizontalRule(int y) const {
Luke Song92eda4d2017-09-19 10:51:35 -0700730 gr_fill(0, y + 4, ScreenWidth(), y + 6);
Tao Baoea78d862017-06-28 14:52:17 -0700731 return 8;
Elliott Hughes95fc63e2015-04-10 19:12:01 -0700732}
733
Luke Songe2bd8762017-06-12 16:08:33 -0700734void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const {
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100735 if (y + height > ScreenHeight())
736 height = ScreenHeight() - y;
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700737 gr_fill(x, y, x + width, y + height);
Luke Songe2bd8762017-06-12 16:08:33 -0700738}
739
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100740void ScreenRecoveryUI::DrawScrollBar(int y, int height) const {
741 int x = ScreenWidth() - margin_width_;
742 int width = 8;
743 gr_fill(x - width, y, x, y + height);
744}
745
Luke Song92eda4d2017-09-19 10:51:35 -0700746void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const {
747 gr_fill(x, y, w, h);
748}
749
Tao Bao65815b62018-10-23 10:54:02 -0700750void ScreenRecoveryUI::DrawTextIcon(int x, int y, const GRSurface* surface) const {
Luke Song92eda4d2017-09-19 10:51:35 -0700751 gr_texticon(x, y, surface);
752}
753
Tao Bao93e46ad2018-05-02 14:57:21 -0700754int ScreenRecoveryUI::DrawTextLine(int x, int y, const std::string& line, bool bold) const {
755 gr_text(gr_sys_font(), x, y, line.c_str(), bold);
Tao Baoea78d862017-06-28 14:52:17 -0700756 return char_height_ + 4;
Elliott Hughes8fd86d72015-04-13 14:36:02 -0700757}
758
Tao Bao93e46ad2018-05-02 14:57:21 -0700759int ScreenRecoveryUI::DrawTextLines(int x, int y, const std::vector<std::string>& lines) const {
Tao Baoea78d862017-06-28 14:52:17 -0700760 int offset = 0;
Tao Bao93e46ad2018-05-02 14:57:21 -0700761 for (const auto& line : lines) {
762 offset += DrawTextLine(x, y + offset, line, false);
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700763 }
Tao Baoea78d862017-06-28 14:52:17 -0700764 return offset;
Elliott Hughes8fd86d72015-04-13 14:36:02 -0700765}
766
Tao Bao93e46ad2018-05-02 14:57:21 -0700767int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y,
768 const std::vector<std::string>& lines) const {
Tao Bao452b4872018-05-09 11:52:09 -0700769 // Keep symmetrical margins based on the given offset (i.e. x).
770 size_t text_cols = (ScreenWidth() - x * 2) / char_width_;
Tao Bao2bbc6d62017-08-13 23:48:55 -0700771 int offset = 0;
Tao Bao93e46ad2018-05-02 14:57:21 -0700772 for (const auto& line : lines) {
Tao Bao2bbc6d62017-08-13 23:48:55 -0700773 size_t next_start = 0;
774 while (next_start < line.size()) {
Tao Bao452b4872018-05-09 11:52:09 -0700775 std::string sub = line.substr(next_start, text_cols + 1);
776 if (sub.size() <= text_cols) {
Tao Bao2bbc6d62017-08-13 23:48:55 -0700777 next_start += sub.size();
778 } else {
Tao Bao452b4872018-05-09 11:52:09 -0700779 // Line too long and must be wrapped to text_cols columns.
Tao Bao2bbc6d62017-08-13 23:48:55 -0700780 size_t last_space = sub.find_last_of(" \t\n");
781 if (last_space == std::string::npos) {
Tao Bao93e46ad2018-05-02 14:57:21 -0700782 // No space found, just draw as much as we can.
Tao Bao452b4872018-05-09 11:52:09 -0700783 sub.resize(text_cols);
784 next_start += text_cols;
Tao Bao2bbc6d62017-08-13 23:48:55 -0700785 } else {
786 sub.resize(last_space);
787 next_start += last_space + 1;
788 }
789 }
Tao Bao93e46ad2018-05-02 14:57:21 -0700790 offset += DrawTextLine(x, y + offset, sub, false);
Tao Bao2bbc6d62017-08-13 23:48:55 -0700791 }
792 }
793 return offset;
794}
795
Jerry Zhang0e577ee2018-05-07 11:21:10 -0700796void ScreenRecoveryUI::SetTitle(const std::vector<std::string>& lines) {
797 title_lines_ = lines;
798}
799
Tianjie Xue5032212019-07-23 13:23:29 -0700800std::vector<std::string> ScreenRecoveryUI::GetMenuHelpMessage() const {
801 // clang-format off
802 static std::vector<std::string> REGULAR_HELP{
Richard Hansen77953b32020-05-13 10:07:36 -0400803 "Use the volume up/down keys to navigate.",
804 "Use the power key to select.",
Tianjie Xue5032212019-07-23 13:23:29 -0700805 };
806 static std::vector<std::string> LONG_PRESS_HELP{
807 "Any button cycles highlight.",
808 "Long-press activates.",
809 };
Richard Hansen62301412020-05-12 18:18:43 -0400810 static const std::vector<std::string> NO_HELP = {};
Tianjie Xue5032212019-07-23 13:23:29 -0700811 // clang-format on
Richard Hansen62301412020-05-12 18:18:43 -0400812 return HasTouchScreen() ? NO_HELP : HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP;
Tianjie Xue5032212019-07-23 13:23:29 -0700813}
814
Tao Bao171b4c42017-06-19 23:10:44 -0700815// Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex
816// locked.
Elliott Hughes8de52072015-04-08 20:06:50 -0700817void ScreenRecoveryUI::draw_screen_locked() {
Tao Bao171b4c42017-06-19 23:10:44 -0700818 if (!show_text) {
819 draw_background_locked();
820 draw_foreground_locked();
821 return;
822 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700823
Tao Bao171b4c42017-06-19 23:10:44 -0700824 gr_color(0, 0, 0, 255);
825 gr_clear();
Elliott Hughes8fd86d72015-04-13 14:36:02 -0700826
Tianjie Xue5032212019-07-23 13:23:29 -0700827 draw_menu_and_text_buffer_locked(GetMenuHelpMessage());
Chippa-a6c967e12023-10-19 00:06:41 +0300828 draw_battery_capacity_locked();
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700829}
830
831// Draws the menu and text buffer on the screen. Should only be called with updateMutex locked.
Tao Bao93e46ad2018-05-02 14:57:21 -0700832void ScreenRecoveryUI::draw_menu_and_text_buffer_locked(
833 const std::vector<std::string>& help_message) {
Tao Bao0bc88de2018-07-31 14:53:16 -0700834 int y = margin_height_;
David Anderson983e2d52019-01-02 11:35:38 -0800835
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700836 if (menu_) {
Alessandro Astoneae01d492020-03-29 15:08:09 +0200837 auto& logo = fastbootd_logo_enabled_ ? fastbootd_logo_ : lineage_logo_;
Richard Hansen953bcae2020-05-12 18:08:56 -0400838 auto logo_width = gr_get_width(logo.get());
839 auto logo_height = gr_get_height(logo.get());
840 auto centered_x = ScreenWidth() / 2 - logo_width / 2;
841 DrawSurface(logo.get(), 0, 0, logo_width, logo_height, centered_x, y);
842 y += logo_height;
Tao Bao171b4c42017-06-19 23:10:44 -0700843
Richard Hansen953bcae2020-05-12 18:08:56 -0400844 if (!menu_->IsMain()) {
845 auto icon_w = gr_get_width(back_icon_.get());
846 auto icon_h = gr_get_height(back_icon_.get());
847 auto icon_x = centered_x / 2 - icon_w / 2;
848 auto icon_y = y - logo_height / 2 - icon_h / 2;
849 gr_blit(back_icon_sel_ && menu_->selection() == -1 ? back_icon_sel_.get() : back_icon_.get(),
850 0, 0, icon_w, icon_h, icon_x, icon_y);
Alessandro Astone0ff12c62020-03-09 23:17:50 +0100851 }
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700852
Richard Hansen953bcae2020-05-12 18:08:56 -0400853 int x = margin_width_ + kMenuIndent;
854 if (!title_lines_.empty()) {
855 SetColor(UIElement::INFO);
856 y += DrawTextLines(x, y, title_lines_);
857 }
Tianjie Xu66dbf632018-10-11 16:54:50 -0700858 y += menu_->DrawHeader(x, y);
Alessandro Astone2dfa4522020-10-04 18:11:40 +0200859 menu_start_y_ = y + 12; // Skip horizontal rule and some margin
Alessandro Astoneac09fbd2020-06-25 19:54:18 +0200860 menu_->SetMenuHeight(std::max(0, ScreenHeight() - menu_start_y_));
Tianjie Xu66dbf632018-10-11 16:54:50 -0700861 y += menu_->DrawItems(x, y, ScreenWidth(), IsLongPress());
Richard Hansen62301412020-05-12 18:18:43 -0400862 if (!help_message.empty()) {
863 y += MenuItemPadding();
864 SetColor(UIElement::INFO);
865 y += DrawTextLines(x, y, help_message);
866 }
Tao Bao171b4c42017-06-19 23:10:44 -0700867 }
868
869 // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or
870 // we've displayed the entire text buffer.
Tianjie Xu66dbf632018-10-11 16:54:50 -0700871 SetColor(UIElement::LOG);
Tao Baocb5524c2017-09-08 21:25:32 -0700872 int row = text_row_;
Tao Bao171b4c42017-06-19 23:10:44 -0700873 size_t count = 0;
Tao Bao0bc88de2018-07-31 14:53:16 -0700874 for (int ty = ScreenHeight() - margin_height_ - char_height_; ty >= y && count < text_rows_;
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700875 ty -= char_height_, ++count) {
Tao Bao0bc88de2018-07-31 14:53:16 -0700876 DrawTextLine(margin_width_, ty, text_[row], false);
Tao Bao171b4c42017-06-19 23:10:44 -0700877 --row;
878 if (row < 0) row = text_rows_ - 1;
879 }
Doug Zongker211aebc2011-10-28 15:13:10 -0700880}
881
Chippa-a6c967e12023-10-19 00:06:41 +0300882// Draws the battery capacity on the screen. Should only be called with updateMutex locked.
883void ScreenRecoveryUI::draw_battery_capacity_locked() {
884 int x;
Chippa-ad96b1722024-03-13 21:06:36 +0300885 int y = margin_height_ + gr_get_height(lineage_logo_.get());
Chippa-a6c967e12023-10-19 00:06:41 +0300886 int icon_x, icon_y, icon_h, icon_w;
887
888 // Battery status
889 std::string batt_capacity = std::to_string(batt_capacity_) + '%';
890
891 if (charging_)
892 batt_capacity.push_back('+');
893 else if (batt_capacity.back() == '+')
894 batt_capacity.pop_back();
895
896 if (menu_) {
897 // Battery icon
898 x = (ScreenWidth() - margin_width_ * 2 - kMenuIndent) - char_width_;
899
900 SetColor(UIElement::INFO);
901
902 // Top
903 icon_x = x + char_width_ / 3;
904 icon_y = y;
905 icon_w = char_width_ / 3;
906 icon_h = char_height_ / 12;
907 gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h);
908
909 // Main rect
910 icon_x = x;
911 icon_y = y + icon_h;
912 icon_w = char_width_;
913 icon_h = char_height_ - (char_height_ / 12);
914 gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h);
915
916 // Capacity
917 if (batt_capacity_ <= 15) SetColor(UIElement::BATTERY_LOW);
918 icon_x = x + char_width_ / 6;
919 icon_y = y + char_height_ / 12;
920 icon_w = char_width_ - (2 * char_width_ / 6);
921 icon_h = char_height_ - (3 * char_height_ / 12);
922 int cap_h = icon_h * batt_capacity_ / 100;
923 gr_fill(icon_x, icon_y + icon_h - cap_h, icon_x + icon_w, icon_y + icon_h);
924 gr_color(0, 0, 0, 255);
925 gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h - cap_h);
926
927 x -= char_width_; // Separator
928
929 // Battery text
930 SetColor(UIElement::INFO);
931 x -= batt_capacity.size() * char_width_;
932 DrawTextLine(x, icon_y, batt_capacity.c_str(), false);
933 }
934}
935
Doug Zongker211aebc2011-10-28 15:13:10 -0700936// Redraw everything on the screen and flip the screen (make it visible).
937// Should only be called with updateMutex locked.
Elliott Hughes8de52072015-04-08 20:06:50 -0700938void ScreenRecoveryUI::update_screen_locked() {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700939 draw_screen_locked();
940 gr_flip();
Doug Zongker211aebc2011-10-28 15:13:10 -0700941}
942
943// Updates only the progress bar, if possible, otherwise redraws the screen.
944// Should only be called with updateMutex locked.
Elliott Hughes8de52072015-04-08 20:06:50 -0700945void ScreenRecoveryUI::update_progress_locked() {
Tao Bao5d2e3bd2017-06-23 22:23:50 -0700946 if (show_text || !pagesIdentical) {
947 draw_screen_locked(); // Must redraw the whole screen
948 pagesIdentical = true;
949 } else {
950 draw_foreground_locked(); // Draw only the progress bar and overlays
951 }
952 gr_flip();
Doug Zongker211aebc2011-10-28 15:13:10 -0700953}
954
Chippa-a6c967e12023-10-19 00:06:41 +0300955void ScreenRecoveryUI::BattMonitorThreadLoop() {
956 using aidl::android::hardware::health::BatteryStatus;
957 using android::hardware::health::InitHealthdConfig;
958
959 auto config = std::make_unique<healthd_config>();
960 InitHealthdConfig(config.get());
961
962 auto batt_monitor = std::make_unique<android::BatteryMonitor>();
963 batt_monitor->init(config.get());
964
965 while (!batt_monitor_thread_stopped_) {
966 bool redraw = false;
967 {
968 std::lock_guard<std::mutex> lg(updateMutex);
969
970 auto charge_status = static_cast<BatteryStatus>(batt_monitor->getChargeStatus());
971 // Treat unknown status as on charger.
972 bool charging = (charge_status != BatteryStatus::DISCHARGING &&
973 charge_status != BatteryStatus::NOT_CHARGING &&
974 charge_status != BatteryStatus::FULL);
975 if (charging_ != charging) {
976 charging_ = charging;
977 redraw = true;
978 }
979
980 android::BatteryProperty prop;
981 android::status_t status = batt_monitor->getProperty(android::BATTERY_PROP_CAPACITY, &prop);
982 // If we can't read battery percentage, it may be a device without battery. In this
983 // situation, use 100 as a fake battery percentage.
984 if (status != android::OK) {
985 LOG(WARNING) << "Using fake battery capacity 100.";
986 prop.valueInt64 = 100;
987 }
988
989 int32_t batt_capacity = static_cast<int32_t>(prop.valueInt64);
990 if (batt_capacity_ != batt_capacity) {
991 batt_capacity_ = batt_capacity;
992 redraw = true;
993 }
994
995 if (redraw) update_screen_locked();
996 }
997 std::this_thread::sleep_for(5s);
998 }
999}
1000
Elliott Hughes985022a2015-04-13 13:04:32 -07001001void ScreenRecoveryUI::ProgressThreadLoop() {
Tao Bao0bc88de2018-07-31 14:53:16 -07001002 double interval = 1.0 / animation_fps_;
Tao Bao26ea9592018-05-09 16:32:02 -07001003 while (!progress_thread_stopped_) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001004 double start = now();
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001005 bool redraw = false;
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001006 {
1007 std::lock_guard<std::mutex> lg(updateMutex);
Doug Zongker211aebc2011-10-28 15:13:10 -07001008
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001009 // update the installation animation, if active
1010 // skip this if we have a text overlay (too expensive to update)
Tao Baoda409fb2018-10-21 23:36:26 -07001011 if ((current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) && !show_text) {
1012 if (!intro_done_) {
1013 if (current_frame_ == intro_frames_.size() - 1) {
1014 intro_done_ = true;
1015 current_frame_ = 0;
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001016 } else {
Tao Baoda409fb2018-10-21 23:36:26 -07001017 ++current_frame_;
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001018 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001019 } else {
Tao Baoda409fb2018-10-21 23:36:26 -07001020 current_frame_ = (current_frame_ + 1) % loop_frames_.size();
Doug Zongker211aebc2011-10-28 15:13:10 -07001021 }
1022
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001023 redraw = true;
1024 }
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001025
1026 // move the progress bar forward on timed intervals, if configured
1027 int duration = progressScopeDuration;
1028 if (progressBarType == DETERMINATE && duration > 0) {
1029 double elapsed = now() - progressScopeTime;
1030 float p = 1.0 * elapsed / duration;
1031 if (p > 1.0) p = 1.0;
1032 if (p > progress) {
1033 progress = p;
1034 redraw = true;
1035 }
1036 }
1037
1038 if (redraw) update_progress_locked();
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001039 }
1040
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001041 double end = now();
1042 // minimum of 20ms delay between frames
1043 double delay = interval - (end - start);
1044 if (delay < 0.02) delay = 0.02;
1045 usleep(static_cast<useconds_t>(delay * 1000000));
1046 }
Doug Zongker211aebc2011-10-28 15:13:10 -07001047}
1048
Tao Baoda409fb2018-10-21 23:36:26 -07001049std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadBitmap(const std::string& filename) {
1050 GRSurface* surface;
1051 if (auto result = res_create_display_surface(filename.c_str(), &surface); result < 0) {
1052 LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")";
1053 return nullptr;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001054 }
Tao Baoda409fb2018-10-21 23:36:26 -07001055 return std::unique_ptr<GRSurface>(surface);
Doug Zongkereac881c2014-03-07 09:21:25 -08001056}
1057
Tao Baoda409fb2018-10-21 23:36:26 -07001058std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadLocalizedBitmap(const std::string& filename) {
1059 GRSurface* surface;
xunchang9d05c8a2019-04-16 12:07:42 -07001060 auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface);
1061 if (result == 0) {
1062 return std::unique_ptr<GRSurface>(surface);
Tao Bao736d59c2017-01-03 10:15:33 -08001063 }
xunchang9d05c8a2019-04-16 12:07:42 -07001064
1065 result = res_create_localized_alpha_surface(filename.c_str(), DEFAULT_LOCALE, &surface);
1066 if (result == 0) {
1067 return std::unique_ptr<GRSurface>(surface);
1068 }
1069
xunchang9d05c8a2019-04-16 12:07:42 -07001070 return nullptr;
Doug Zongker02ec6b82012-08-22 17:26:40 -07001071}
1072
Elliott Hughesaa0d6af2015-04-08 12:42:50 -07001073static char** Alloc2d(size_t rows, size_t cols) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001074 char** result = new char*[rows];
1075 for (size_t i = 0; i < rows; ++i) {
1076 result[i] = new char[cols];
1077 memset(result[i], 0, cols);
1078 }
1079 return result;
Elliott Hughesaa0d6af2015-04-08 12:42:50 -07001080}
1081
Tianjie Xu35926c42016-04-28 18:06:26 -07001082// Choose the right background string to display during update.
1083void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001084 if (security_update) {
Tao Baoda409fb2018-10-21 23:36:26 -07001085 installing_text_ = LoadLocalizedBitmap("installing_security_text");
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001086 } else {
Tao Baoda409fb2018-10-21 23:36:26 -07001087 installing_text_ = LoadLocalizedBitmap("installing_text");
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001088 }
1089 Redraw();
Tianjie Xu35926c42016-04-28 18:06:26 -07001090}
1091
Sen Jiangd5304492016-12-09 16:20:49 -08001092bool ScreenRecoveryUI::InitTextParams() {
Tao Baoba4edb32018-06-13 11:22:50 -07001093 // gr_init() would return successfully on font initialization failure.
1094 if (gr_sys_font() == nullptr) {
Tao Bao171b4c42017-06-19 23:10:44 -07001095 return false;
1096 }
Tao Bao171b4c42017-06-19 23:10:44 -07001097 gr_font_size(gr_sys_font(), &char_width_, &char_height_);
Alessandro Astone0ff12c62020-03-09 23:17:50 +01001098 gr_font_size(gr_menu_font(), &menu_char_width_, &menu_char_height_);
Tao Bao0bc88de2018-07-31 14:53:16 -07001099 text_rows_ = (ScreenHeight() - margin_height_ * 2) / char_height_;
1100 text_cols_ = (ScreenWidth() - margin_width_ * 2) / char_width_;
Tao Bao171b4c42017-06-19 23:10:44 -07001101 return true;
Damien Bargiacchi5e7cfb92016-08-24 18:28:43 -07001102}
1103
Tianjie Xub99e6062018-10-16 15:13:09 -07001104bool ScreenRecoveryUI::LoadWipeDataMenuText() {
Tianjie Xu1a0a30a2018-10-25 15:22:07 -07001105 // Ignores the errors since the member variables will stay as nullptr.
1106 cancel_wipe_data_text_ = LoadLocalizedBitmap("cancel_wipe_data_text");
1107 factory_data_reset_text_ = LoadLocalizedBitmap("factory_data_reset_text");
1108 try_again_text_ = LoadLocalizedBitmap("try_again_text");
1109 wipe_data_confirmation_text_ = LoadLocalizedBitmap("wipe_data_confirmation_text");
1110 wipe_data_menu_header_text_ = LoadLocalizedBitmap("wipe_data_menu_header_text");
Tianjie Xub99e6062018-10-16 15:13:09 -07001111 return true;
1112}
1113
Mark Salyzyn30017e72020-05-13 12:39:12 -07001114static bool InitGraphics() {
1115 // Timeout is same as init wait for file default of 5 seconds and is arbitrary
1116 const unsigned timeout = 500; // 10ms increments
1117 for (auto retry = timeout; retry > 0; --retry) {
1118 if (gr_init() == 0) {
1119 if (retry < timeout) {
1120 // Log message like init wait for file completion log for consistency.
1121 LOG(WARNING) << "wait for 'graphics' took " << ((timeout - retry) * 10) << "ms";
1122 }
1123 return true;
1124 }
1125 std::this_thread::sleep_for(10ms);
1126 }
1127 // Log message like init wait for file timeout log for consistency.
1128 LOG(ERROR) << "timeout wait for 'graphics' took " << (timeout * 10) << "ms";
1129 return false;
1130}
1131
Tao Bao736d59c2017-01-03 10:15:33 -08001132bool ScreenRecoveryUI::Init(const std::string& locale) {
1133 RecoveryUI::Init(locale);
Tao Baoefb49ad2017-01-31 23:03:10 -08001134
Mark Salyzyn30017e72020-05-13 12:39:12 -07001135 if (!InitGraphics()) {
Tao Baoba4edb32018-06-13 11:22:50 -07001136 return false;
1137 }
fredchioub44702a2022-02-11 16:39:53 +08001138 is_graphics_available = true;
Tao Baoba4edb32018-06-13 11:22:50 -07001139
Tao Bao736d59c2017-01-03 10:15:33 -08001140 if (!InitTextParams()) {
1141 return false;
1142 }
Alessandro Astone0ff12c62020-03-09 23:17:50 +01001143 menu_draw_funcs_ = std::make_unique<MenuDrawFunctions>(*this);
Damien Bargiacchi5e7cfb92016-08-24 18:28:43 -07001144
Michael Bestas99407d12019-03-23 17:28:22 +02001145 if (blank_unblank_on_init_) {
1146 gr_fb_blank(true);
1147 gr_fb_blank(false);
1148 }
1149
Tao Bao736d59c2017-01-03 10:15:33 -08001150 // Are we portrait or landscape?
1151 layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT;
1152 // Are we the large variant of our base layout?
1153 if (gr_fb_height() > PixelsFromDp(800)) ++layout_;
Elliott Hughesfaf36e02016-04-20 17:22:16 -07001154
Tao Bao736d59c2017-01-03 10:15:33 -08001155 text_ = Alloc2d(text_rows_, text_cols_ + 1);
1156 file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
Doug Zongker55a36ac2013-03-04 15:49:02 -08001157
Tao Bao736d59c2017-01-03 10:15:33 -08001158 text_col_ = text_row_ = 0;
Doug Zongker211aebc2011-10-28 15:13:10 -07001159
Tao Baoefb49ad2017-01-31 23:03:10 -08001160 // Set up the locale info.
1161 SetLocale(locale);
1162
Tao Baoda409fb2018-10-21 23:36:26 -07001163 error_icon_ = LoadBitmap("icon_error");
Doug Zongker02ec6b82012-08-22 17:26:40 -07001164
Tao Baoda409fb2018-10-21 23:36:26 -07001165 progress_bar_empty_ = LoadBitmap("progress_empty");
1166 progress_bar_fill_ = LoadBitmap("progress_fill");
1167 stage_marker_empty_ = LoadBitmap("stage_empty");
1168 stage_marker_fill_ = LoadBitmap("stage_fill");
Elliott Hughes498cda62016-04-14 16:49:04 -07001169
Tao Baoda409fb2018-10-21 23:36:26 -07001170 erasing_text_ = LoadLocalizedBitmap("erasing_text");
1171 no_command_text_ = LoadLocalizedBitmap("no_command_text");
1172 error_text_ = LoadLocalizedBitmap("error_text");
Doug Zongker211aebc2011-10-28 15:13:10 -07001173
Alessandro Astone0ff12c62020-03-09 23:17:50 +01001174 back_icon_ = LoadBitmap("ic_back");
1175 back_icon_sel_ = LoadBitmap("ic_back_sel");
Alessandro Astone9fe12ca2020-02-26 17:25:54 +01001176 if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false) ||
1177 android::base::GetBoolProperty("ro.fastbootd.available", false)) {
Alessandro Astoneae01d492020-03-29 15:08:09 +02001178 lineage_logo_ = LoadBitmap("logo_image_switch");
David Anderson983e2d52019-01-02 11:35:38 -08001179 fastbootd_logo_ = LoadBitmap("fastbootd");
Alessandro Astoneae01d492020-03-29 15:08:09 +02001180 } else {
1181 lineage_logo_ = LoadBitmap("logo_image");
David Anderson983e2d52019-01-02 11:35:38 -08001182 }
1183
Tao Baoda409fb2018-10-21 23:36:26 -07001184 // Background text for "installing_update" could be "installing update" or
1185 // "installing security update". It will be set after Init() according to the commands in BCB.
1186 installing_text_.reset();
Elliott Hughes498cda62016-04-14 16:49:04 -07001187
Tianjie Xub99e6062018-10-16 15:13:09 -07001188 LoadWipeDataMenuText();
1189
Tao Bao736d59c2017-01-03 10:15:33 -08001190 LoadAnimation();
Doug Zongker02ec6b82012-08-22 17:26:40 -07001191
Chippa-a6c967e12023-10-19 00:06:41 +03001192 // Keep the battery capacity updated.
1193 batt_monitor_thread_ = std::thread(&ScreenRecoveryUI::BattMonitorThreadLoop, this);
1194
Tao Bao26ea9592018-05-09 16:32:02 -07001195 // Keep the progress bar updated, even when the process is otherwise busy.
1196 progress_thread_ = std::thread(&ScreenRecoveryUI::ProgressThreadLoop, this);
Sen Jiangd5304492016-12-09 16:20:49 -08001197
fredchioub44702a2022-02-11 16:39:53 +08001198 // set the callback for hall sensor event
1199 (void)ev_sync_sw_state([this](auto&& a, auto&& b) { return this->SetSwCallback(a, b);});
1200
Tao Bao736d59c2017-01-03 10:15:33 -08001201 return true;
Doug Zongker211aebc2011-10-28 15:13:10 -07001202}
1203
Tao Bao551d2c32018-05-09 20:53:13 -07001204std::string ScreenRecoveryUI::GetLocale() const {
Jerry Zhang2dea53e2018-05-02 17:15:03 -07001205 return locale_;
1206}
1207
Elliott Hughes498cda62016-04-14 16:49:04 -07001208void ScreenRecoveryUI::LoadAnimation() {
Tao Bao6cd81682018-05-03 21:53:11 -07001209 std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(Paths::Get().resource_dir().c_str()),
1210 closedir);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001211 dirent* de;
1212 std::vector<std::string> intro_frame_names;
Joshua Lambert94a83be2019-12-09 20:24:18 +00001213 std::vector<std::string> loop_frame_names;
Damien Bargiacchi5e7cfb92016-08-24 18:28:43 -07001214
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001215 while ((de = readdir(dir.get())) != nullptr) {
1216 int value, num_chars;
1217 if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) {
1218 intro_frame_names.emplace_back(de->d_name, num_chars);
1219 } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) {
Joshua Lambert94a83be2019-12-09 20:24:18 +00001220 loop_frame_names.emplace_back(de->d_name, num_chars);
Elliott Hughes498cda62016-04-14 16:49:04 -07001221 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001222 }
Elliott Hughes498cda62016-04-14 16:49:04 -07001223
Tao Baoda409fb2018-10-21 23:36:26 -07001224 size_t intro_frames = intro_frame_names.size();
1225 size_t loop_frames = loop_frame_names.size();
Damien Bargiacchi5e7cfb92016-08-24 18:28:43 -07001226
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001227 // It's okay to not have an intro.
Tao Baoda409fb2018-10-21 23:36:26 -07001228 if (intro_frames == 0) intro_done_ = true;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001229 // But you must have an animation.
1230 if (loop_frames == 0) abort();
Elliott Hughes498cda62016-04-14 16:49:04 -07001231
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001232 std::sort(intro_frame_names.begin(), intro_frame_names.end());
1233 std::sort(loop_frame_names.begin(), loop_frame_names.end());
Damien Bargiacchi5e7cfb92016-08-24 18:28:43 -07001234
Tao Baoda409fb2018-10-21 23:36:26 -07001235 intro_frames_.clear();
1236 intro_frames_.reserve(intro_frames);
1237 for (const auto& frame_name : intro_frame_names) {
1238 intro_frames_.emplace_back(LoadBitmap(frame_name));
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001239 }
Elliott Hughes498cda62016-04-14 16:49:04 -07001240
Tao Baoda409fb2018-10-21 23:36:26 -07001241 loop_frames_.clear();
1242 loop_frames_.reserve(loop_frames);
1243 for (const auto& frame_name : loop_frame_names) {
1244 loop_frames_.emplace_back(LoadBitmap(frame_name));
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001245 }
Elliott Hughes498cda62016-04-14 16:49:04 -07001246}
1247
Elliott Hughes8de52072015-04-08 20:06:50 -07001248void ScreenRecoveryUI::SetBackground(Icon icon) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001249 std::lock_guard<std::mutex> lg(updateMutex);
Doug Zongker02ec6b82012-08-22 17:26:40 -07001250
Tao Baoda409fb2018-10-21 23:36:26 -07001251 current_icon_ = icon;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001252 update_screen_locked();
Doug Zongker211aebc2011-10-28 15:13:10 -07001253}
1254
Elliott Hughes8de52072015-04-08 20:06:50 -07001255void ScreenRecoveryUI::SetProgressType(ProgressType type) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001256 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001257 if (progressBarType != type) {
1258 progressBarType = type;
1259 }
1260 progressScopeStart = 0;
1261 progressScopeSize = 0;
1262 progress = 0;
1263 update_progress_locked();
Doug Zongker211aebc2011-10-28 15:13:10 -07001264}
1265
Elliott Hughes8de52072015-04-08 20:06:50 -07001266void ScreenRecoveryUI::ShowProgress(float portion, float seconds) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001267 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001268 progressBarType = DETERMINATE;
1269 progressScopeStart += progressScopeSize;
1270 progressScopeSize = portion;
1271 progressScopeTime = now();
1272 progressScopeDuration = seconds;
1273 progress = 0;
1274 update_progress_locked();
Doug Zongker211aebc2011-10-28 15:13:10 -07001275}
1276
Elliott Hughes8de52072015-04-08 20:06:50 -07001277void ScreenRecoveryUI::SetProgress(float fraction) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001278 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001279 if (fraction < 0.0) fraction = 0.0;
1280 if (fraction > 1.0) fraction = 1.0;
1281 if (progressBarType == DETERMINATE && fraction > progress) {
1282 // Skip updates that aren't visibly different.
Tao Baoda409fb2018-10-21 23:36:26 -07001283 int width = gr_get_width(progress_bar_empty_.get());
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001284 float scale = width * progressScopeSize;
1285 if ((int)(progress * scale) != (int)(fraction * scale)) {
1286 progress = fraction;
1287 update_progress_locked();
Doug Zongker211aebc2011-10-28 15:13:10 -07001288 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001289 }
Doug Zongker211aebc2011-10-28 15:13:10 -07001290}
1291
Doug Zongkerc87bab12013-11-25 13:53:25 -08001292void ScreenRecoveryUI::SetStage(int current, int max) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001293 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001294 stage = current;
1295 max_stage = max;
Doug Zongkerc87bab12013-11-25 13:53:25 -08001296}
1297
Tao Baob6918c72015-05-19 17:02:16 -07001298void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001299 std::string str;
1300 android::base::StringAppendV(&str, fmt, ap);
Doug Zongker211aebc2011-10-28 15:13:10 -07001301
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001302 if (copy_to_stdout) {
1303 fputs(str.c_str(), stdout);
1304 }
Doug Zongker211aebc2011-10-28 15:13:10 -07001305
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001306 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001307 if (text_rows_ > 0 && text_cols_ > 0) {
1308 for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) {
1309 if (*ptr == '\n' || text_col_ >= text_cols_) {
Elliott Hughesc0491632015-05-06 12:40:05 -07001310 text_[text_row_][text_col_] = '\0';
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001311 text_col_ = 0;
1312 text_row_ = (text_row_ + 1) % text_rows_;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001313 }
1314 if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
Doug Zongker211aebc2011-10-28 15:13:10 -07001315 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001316 text_[text_row_][text_col_] = '\0';
1317 update_screen_locked();
1318 }
Doug Zongker211aebc2011-10-28 15:13:10 -07001319}
1320
Tao Baob6918c72015-05-19 17:02:16 -07001321void ScreenRecoveryUI::Print(const char* fmt, ...) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001322 va_list ap;
1323 va_start(ap, fmt);
1324 PrintV(fmt, true, ap);
1325 va_end(ap);
Tao Baob6918c72015-05-19 17:02:16 -07001326}
1327
Tianjie Xu8f397302018-08-20 13:40:47 -07001328void ScreenRecoveryUI::PrintOnScreenOnly(const char* fmt, ...) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001329 va_list ap;
1330 va_start(ap, fmt);
1331 PrintV(fmt, false, ap);
1332 va_end(ap);
Tao Baob6918c72015-05-19 17:02:16 -07001333}
1334
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001335void ScreenRecoveryUI::PutChar(char ch) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001336 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001337 if (ch != '\n') text_[text_row_][text_col_++] = ch;
1338 if (ch == '\n' || text_col_ >= text_cols_) {
1339 text_col_ = 0;
1340 ++text_row_;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001341 }
Elliott Hughes8de52072015-04-08 20:06:50 -07001342}
1343
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001344void ScreenRecoveryUI::ClearText() {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001345 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001346 text_col_ = 0;
1347 text_row_ = 0;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001348 for (size_t i = 0; i < text_rows_; ++i) {
1349 memset(text_[i], 0, text_cols_ + 1);
1350 }
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001351}
1352
1353void ScreenRecoveryUI::ShowFile(FILE* fp) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001354 std::vector<off_t> offsets;
1355 offsets.push_back(ftello(fp));
1356 ClearText();
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001357
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001358 struct stat sb;
1359 fstat(fileno(fp), &sb);
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001360
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001361 bool show_prompt = false;
1362 while (true) {
1363 if (show_prompt) {
1364 PrintOnScreenOnly("--(%d%% of %d bytes)--",
1365 static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))),
1366 static_cast<int>(sb.st_size));
1367 Redraw();
1368 while (show_prompt) {
1369 show_prompt = false;
Alessandro Astone2dfa4522020-10-04 18:11:40 +02001370 InputEvent evt = WaitInputEvent();
1371 if (evt.type() == EventType::EXTRA) {
1372 if (evt.key() == static_cast<int>(KeyError::INTERRUPTED)) {
1373 return;
1374 }
1375 }
1376 if (evt.type() != EventType::KEY) {
1377 show_prompt = true;
1378 continue;
1379 }
1380 if (evt.key() == KEY_POWER || evt.key() == KEY_ENTER || evt.key() == KEY_BACKSPACE ||
1381 evt.key() == KEY_BACK || evt.key() == KEY_HOME || evt.key() == KEY_HOMEPAGE) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001382 return;
Alessandro Astone2dfa4522020-10-04 18:11:40 +02001383 } else if (evt.key() == KEY_UP || evt.key() == KEY_VOLUMEUP || evt.key() == KEY_SCROLLUP) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001384 if (offsets.size() <= 1) {
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001385 show_prompt = true;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001386 } else {
1387 offsets.pop_back();
1388 fseek(fp, offsets.back(), SEEK_SET);
1389 }
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001390 } else {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001391 if (feof(fp)) {
1392 return;
1393 }
1394 offsets.push_back(ftello(fp));
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001395 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001396 }
1397 ClearText();
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001398 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001399
1400 int ch = getc(fp);
1401 if (ch == EOF) {
1402 while (text_row_ < text_rows_ - 1) PutChar('\n');
1403 show_prompt = true;
1404 } else {
1405 PutChar(ch);
1406 if (text_col_ == 0 && text_row_ >= text_rows_ - 1) {
1407 show_prompt = true;
1408 }
1409 }
1410 }
Elliott Hughes95fc63e2015-04-10 19:12:01 -07001411}
1412
Tao Bao1d156b92018-05-02 12:43:18 -07001413void ScreenRecoveryUI::ShowFile(const std::string& filename) {
1414 std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(filename.c_str(), "re"), fclose);
1415 if (!fp) {
1416 Print(" Unable to open %s: %s\n", filename.c_str(), strerror(errno));
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001417 return;
1418 }
Elliott Hughesc0491632015-05-06 12:40:05 -07001419
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001420 char** old_text = text_;
1421 size_t old_text_col = text_col_;
1422 size_t old_text_row = text_row_;
Elliott Hughesc0491632015-05-06 12:40:05 -07001423
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001424 // Swap in the alternate screen and clear it.
1425 text_ = file_viewer_text_;
1426 ClearText();
Elliott Hughesc0491632015-05-06 12:40:05 -07001427
Tao Bao1d156b92018-05-02 12:43:18 -07001428 ShowFile(fp.get());
Elliott Hughesc0491632015-05-06 12:40:05 -07001429
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001430 text_ = old_text;
1431 text_col_ = old_text_col;
1432 text_row_ = old_text_row;
Elliott Hughes8de52072015-04-08 20:06:50 -07001433}
1434
Tao Baoda409fb2018-10-21 23:36:26 -07001435std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu(
1436 const GRSurface* graphic_header, const std::vector<const GRSurface*>& graphic_items,
1437 const std::vector<std::string>& text_headers, const std::vector<std::string>& text_items,
1438 size_t initial_selection) const {
Tianjie Xub99e6062018-10-16 15:13:09 -07001439 // horizontal unusable area: margin width + menu indent
1440 size_t max_width = ScreenWidth() - margin_width_ - kMenuIndent;
1441 // vertical unusable area: margin height + title lines + helper message + high light bar.
1442 // It is safe to reserve more space.
1443 size_t max_height = ScreenHeight() - margin_height_ - char_height_ * (title_lines_.size() + 3);
1444 if (GraphicMenu::Validate(max_width, max_height, graphic_header, graphic_items)) {
1445 return std::make_unique<GraphicMenu>(graphic_header, graphic_items, initial_selection, *this);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001446 }
Tianjie Xub99e6062018-10-16 15:13:09 -07001447
1448 fprintf(stderr, "Failed to initialize graphic menu, falling back to use the text menu.\n");
1449
1450 return CreateMenu(text_headers, text_items, initial_selection);
1451}
1452
1453std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu(const std::vector<std::string>& text_headers,
1454 const std::vector<std::string>& text_items,
1455 size_t initial_selection) const {
Alessandro Astone0ff12c62020-03-09 23:17:50 +01001456 int menu_char_width = MenuCharWidth();
1457 int menu_char_height = MenuCharHeight();
Alessandro Astone0ff12c62020-03-09 23:17:50 +01001458 int menu_cols = (ScreenWidth() - margin_width_*2 - kMenuIndent) / menu_char_width;
1459 bool wrap_selection = !HasThreeButtons() && !HasTouchScreen();
Alessandro Astoneac09fbd2020-06-25 19:54:18 +02001460 return std::make_unique<TextMenu>(wrap_selection, menu_cols, text_headers, text_items,
Alessandro Astone0ff12c62020-03-09 23:17:50 +01001461 initial_selection, menu_char_height, *menu_draw_funcs_);
Doug Zongker211aebc2011-10-28 15:13:10 -07001462}
1463
1464int ScreenRecoveryUI::SelectMenu(int sel) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001465 std::lock_guard<std::mutex> lg(updateMutex);
Tianjie Xu5fe5eb62018-03-20 16:07:39 -07001466 if (menu_) {
1467 int old_sel = menu_->selection();
1468 sel = menu_->Select(sel);
Elliott Hughesfc06f872015-03-23 13:45:31 -07001469
Tianjie Xu5fe5eb62018-03-20 16:07:39 -07001470 if (sel != old_sel) {
1471 update_screen_locked();
1472 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001473 }
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001474 return sel;
Doug Zongker211aebc2011-10-28 15:13:10 -07001475}
1476
Alessandro Astone5351cd12022-03-21 17:14:47 +01001477int ScreenRecoveryUI::SelectMenu(const Point& p) {
1478 // Correct position for overscan
1479 const Point point(p.x() - gr_overscan_offset_x(), p.y() - gr_overscan_offset_y());
1480
Alessandro Astone2dfa4522020-10-04 18:11:40 +02001481 int new_sel = Device::kNoAction;
1482 std::lock_guard<std::mutex> lg(updateMutex);
1483 if (menu_) {
1484 if (!menu_->IsMain()) {
1485 // Back arrow hitbox
1486 const static int logo_width = gr_get_width(lineage_logo_.get());
1487 const static int logo_height = gr_get_height(lineage_logo_.get());
1488 const static int icon_w = gr_get_width(back_icon_.get());
1489 const static int icon_h = gr_get_height(back_icon_.get());
1490 const static int centered_x = ScreenWidth() / 2 - logo_width / 2;
1491 const static int icon_x = centered_x / 2 - icon_w / 2;
1492 const static int icon_y = margin_height_ + logo_height / 2 - icon_h / 2;
1493
1494 if (point.x() >= icon_x && point.x() <= icon_x + icon_w &&
1495 point.y() >= icon_y && point.y() <= icon_y + icon_h) {
1496 return Device::kGoBack;
1497 }
1498 }
1499
1500 if (point.y() >= menu_start_y_ &&
1501 point.y() < menu_start_y_ + menu_->ItemsCount() * MenuItemHeight()) {
1502 int old_sel = menu_->selection();
1503 int relative_sel = (point.y() - menu_start_y_) / MenuItemHeight();
1504 new_sel = menu_->SelectVisible(relative_sel);
1505 if (new_sel != -1 && new_sel != old_sel) {
1506 update_screen_locked();
1507 }
1508 }
1509 }
1510 return new_sel;
1511}
1512
1513int ScreenRecoveryUI::ScrollMenu(int updown) {
1514 std::lock_guard<std::mutex> lg(updateMutex);
1515 int sel = Device::kNoAction;
1516 if (menu_) {
1517 sel = menu_->Scroll(updown);
1518 update_screen_locked();
1519 }
1520 return sel;
1521}
1522
Tianjie Xub99e6062018-10-16 15:13:09 -07001523size_t ScreenRecoveryUI::ShowMenu(std::unique_ptr<Menu>&& menu, bool menu_only,
Tom Marshallfd57f0b2019-01-04 14:37:31 -08001524 const std::function<int(int, bool)>& key_handler,
1525 bool refreshable) {
Tao Bao3aec6962018-04-20 09:24:58 -07001526 // Throw away keys pressed previously, so user doesn't accidentally trigger menu items.
1527 FlushKeys();
1528
Jerry Zhangb76af932018-05-22 12:08:35 -07001529 // If there is a key interrupt in progress, return KeyError::INTERRUPTED without starting the
1530 // menu.
1531 if (IsKeyInterrupted()) return static_cast<size_t>(KeyError::INTERRUPTED);
1532
Tianjie Xub99e6062018-10-16 15:13:09 -07001533 CHECK(menu != nullptr);
Tao Bao3aec6962018-04-20 09:24:58 -07001534
Tianjie Xub99e6062018-10-16 15:13:09 -07001535 // Starts and displays the menu
1536 menu_ = std::move(menu);
1537 Redraw();
1538
1539 int selected = menu_->selection();
Tao Bao3aec6962018-04-20 09:24:58 -07001540 int chosen_item = -1;
1541 while (chosen_item < 0) {
Alessandro Astone2dfa4522020-10-04 18:11:40 +02001542 InputEvent evt = WaitInputEvent();
1543 if (evt.type() == EventType::EXTRA) {
1544 if (evt.key() == static_cast<int>(KeyError::INTERRUPTED)) {
1545 // WaitKey() was interrupted.
1546 return static_cast<size_t>(KeyError::INTERRUPTED);
1547 }
1548 if (evt.key() == static_cast<int>(KeyError::TIMED_OUT)) { // WaitKey() timed out.
1549 if (WasTextEverVisible()) {
1550 continue;
1551 } else {
1552 LOG(INFO) << "Timed out waiting for key input; rebooting.";
1553 menu_.reset();
1554 Redraw();
1555 return static_cast<size_t>(KeyError::TIMED_OUT);
1556 }
Tao Bao3aec6962018-04-20 09:24:58 -07001557 }
1558 }
1559
Alessandro Astone2dfa4522020-10-04 18:11:40 +02001560 int action = Device::kNoAction;
1561 if (evt.type() == EventType::TOUCH) {
1562 int touch_sel = SelectMenu(evt.pos());
1563 if (touch_sel < 0) {
1564 action = touch_sel;
1565 } else {
1566 action = Device::kInvokeItem;
1567 selected = touch_sel;
1568 }
1569 } else {
1570 bool visible = IsTextVisible();
1571 action = key_handler(evt.key(), visible);
1572 }
Tom Marshallfd57f0b2019-01-04 14:37:31 -08001573
Tao Bao3aec6962018-04-20 09:24:58 -07001574 if (action < 0) {
1575 switch (action) {
1576 case Device::kHighlightUp:
1577 selected = SelectMenu(--selected);
1578 break;
1579 case Device::kHighlightDown:
1580 selected = SelectMenu(++selected);
1581 break;
Alessandro Astone2dfa4522020-10-04 18:11:40 +02001582 case Device::kScrollUp:
1583 selected = ScrollMenu(-1);
1584 break;
1585 case Device::kScrollDown:
1586 selected = ScrollMenu(1);
1587 break;
Tao Bao3aec6962018-04-20 09:24:58 -07001588 case Device::kInvokeItem:
Alessandro Astone0ff12c62020-03-09 23:17:50 +01001589 if (selected < 0) {
1590 chosen_item = Device::kGoBack;
1591 } else {
1592 chosen_item = selected;
1593 }
Tao Bao3aec6962018-04-20 09:24:58 -07001594 break;
1595 case Device::kNoAction:
1596 break;
Tom Marshallb37abf22017-08-24 13:50:01 +00001597 case Device::kGoBack:
1598 chosen_item = Device::kGoBack;
1599 break;
1600 case Device::kGoHome:
1601 chosen_item = Device::kGoHome;
1602 break;
Tom Marshall6336a662018-12-17 15:57:44 -08001603 case Device::kDoSideload:
1604 chosen_item = Device::kDoSideload;
1605 break;
Tom Marshallfd57f0b2019-01-04 14:37:31 -08001606 case Device::kRefresh:
1607 if (refreshable) {
1608 chosen_item = Device::kRefresh;
1609 }
1610 break;
Tao Bao3aec6962018-04-20 09:24:58 -07001611 }
1612 } else if (!menu_only) {
1613 chosen_item = action;
1614 }
Tom Marshall6336a662018-12-17 15:57:44 -08001615
1616 if (chosen_item == Device::kGoBack || chosen_item == Device::kGoHome ||
Tom Marshallfd57f0b2019-01-04 14:37:31 -08001617 chosen_item == Device::kDoSideload || chosen_item == Device::kRefresh) {
Tom Marshallb37abf22017-08-24 13:50:01 +00001618 break;
1619 }
Tao Bao3aec6962018-04-20 09:24:58 -07001620 }
1621
Tianjie Xub99e6062018-10-16 15:13:09 -07001622 menu_.reset();
Tianjie Xub99e6062018-10-16 15:13:09 -07001623
Tao Bao3aec6962018-04-20 09:24:58 -07001624 return chosen_item;
1625}
1626
Tianjie Xub99e6062018-10-16 15:13:09 -07001627size_t ScreenRecoveryUI::ShowMenu(const std::vector<std::string>& headers,
1628 const std::vector<std::string>& items, size_t initial_selection,
1629 bool menu_only,
Tom Marshallfd57f0b2019-01-04 14:37:31 -08001630 const std::function<int(int, bool)>& key_handler,
1631 bool refreshable) {
Tianjie Xub99e6062018-10-16 15:13:09 -07001632 auto menu = CreateMenu(headers, items, initial_selection);
1633 if (menu == nullptr) {
1634 return initial_selection;
1635 }
1636
Tom Marshallfd57f0b2019-01-04 14:37:31 -08001637 return ShowMenu(std::move(menu), menu_only, key_handler, refreshable);
Tianjie Xub99e6062018-10-16 15:13:09 -07001638}
1639
1640size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector<std::string>& backup_headers,
1641 const std::vector<std::string>& backup_items,
1642 const std::function<int(int, bool)>& key_handler) {
Alessandro Astonee77c4212020-10-05 15:11:36 +02001643 auto wipe_data_menu = CreateMenu(backup_headers, backup_items, 0);
Tianjie Xub99e6062018-10-16 15:13:09 -07001644 if (wipe_data_menu == nullptr) {
1645 return 0;
1646 }
1647
1648 return ShowMenu(std::move(wipe_data_menu), true, key_handler);
1649}
1650
Tianjie Xu1a0a30a2018-10-25 15:22:07 -07001651size_t ScreenRecoveryUI::ShowPromptWipeDataConfirmationMenu(
1652 const std::vector<std::string>& backup_headers, const std::vector<std::string>& backup_items,
1653 const std::function<int(int, bool)>& key_handler) {
1654 auto confirmation_menu =
1655 CreateMenu(wipe_data_confirmation_text_.get(),
1656 { cancel_wipe_data_text_.get(), factory_data_reset_text_.get() }, backup_headers,
1657 backup_items, 0);
1658 if (confirmation_menu == nullptr) {
1659 return 0;
1660 }
1661
1662 return ShowMenu(std::move(confirmation_menu), true, key_handler);
1663}
1664
Elliott Hughes8de52072015-04-08 20:06:50 -07001665bool ScreenRecoveryUI::IsTextVisible() {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001666 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001667 int visible = show_text;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001668 return visible;
Doug Zongker211aebc2011-10-28 15:13:10 -07001669}
1670
Elliott Hughes8de52072015-04-08 20:06:50 -07001671bool ScreenRecoveryUI::WasTextEverVisible() {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001672 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001673 int ever_visible = show_text_ever;
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001674 return ever_visible;
Doug Zongker211aebc2011-10-28 15:13:10 -07001675}
1676
Elliott Hughes8de52072015-04-08 20:06:50 -07001677void ScreenRecoveryUI::ShowText(bool visible) {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001678 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001679 show_text = visible;
1680 if (show_text) show_text_ever = true;
1681 update_screen_locked();
Doug Zongker211aebc2011-10-28 15:13:10 -07001682}
Doug Zongkerc0441d12013-07-31 11:28:24 -07001683
Elliott Hughes8de52072015-04-08 20:06:50 -07001684void ScreenRecoveryUI::Redraw() {
Jerry Zhangb31f9ce2018-05-21 16:04:57 -07001685 std::lock_guard<std::mutex> lg(updateMutex);
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001686 update_screen_locked();
Doug Zongkerc0441d12013-07-31 11:28:24 -07001687}
Elliott Hughes642aaa72015-04-10 12:47:46 -07001688
1689void ScreenRecoveryUI::KeyLongPress(int) {
Tao Bao5d2e3bd2017-06-23 22:23:50 -07001690 // Redraw so that if we're in the menu, the highlight
1691 // will change color to indicate a successful long press.
1692 Redraw();
Elliott Hughes642aaa72015-04-10 12:47:46 -07001693}
Tao Baoefb49ad2017-01-31 23:03:10 -08001694
1695void ScreenRecoveryUI::SetLocale(const std::string& new_locale) {
1696 locale_ = new_locale;
1697 rtl_locale_ = false;
1698
1699 if (!new_locale.empty()) {
Tao Bao347a6592018-05-08 15:58:29 -07001700 size_t separator = new_locale.find('-');
1701 // lang has the language prefix prior to the separator, or full string if none exists.
1702 std::string lang = new_locale.substr(0, separator);
Tao Baoefb49ad2017-01-31 23:03:10 -08001703
1704 // A bit cheesy: keep an explicit list of supported RTL languages.
1705 if (lang == "ar" || // Arabic
1706 lang == "fa" || // Persian (Farsi)
1707 lang == "he" || // Hebrew (new language code)
1708 lang == "iw" || // Hebrew (old language code)
1709 lang == "ur") { // Urdu
1710 rtl_locale_ = true;
1711 }
1712 }
1713}
fredchioub44702a2022-02-11 16:39:53 +08001714
1715int ScreenRecoveryUI::SetSwCallback(int code, int value) {
1716 if (!is_graphics_available) { return -1; }
1717 if (code > SW_MAX) { return -1; }
1718 if (code != SW_LID) { return 0; }
1719
1720 /* detect dual display */
1721 if (!gr_has_multiple_connectors()) { return -1; }
1722
1723 /* turn off all screen */
1724 gr_fb_blank(true, DirectRenderManager::DRM_INNER);
1725 gr_fb_blank(true, DirectRenderManager::DRM_OUTER);
1726 gr_color(0, 0, 0, 255);
1727 gr_clear();
1728
1729 /* turn on the screen */
1730 gr_fb_blank(false, value);
1731 gr_flip();
1732
1733 /* set the retation */
1734 std::string rotation_str;
1735 if (value == DirectRenderManager::DRM_OUTER) {
1736 rotation_str =
1737 android::base::GetProperty("ro.minui.second_rotation", "ROTATION_NONE");
1738 } else {
1739 rotation_str =
1740 android::base::GetProperty("ro.minui.default_rotation", "ROTATION_NONE");
1741 }
1742
1743 if (rotation_str == "ROTATION_RIGHT") {
1744 gr_rotate(GRRotation::RIGHT);
1745 } else if (rotation_str == "ROTATION_DOWN") {
1746 gr_rotate(GRRotation::DOWN);
1747 } else if (rotation_str == "ROTATION_LEFT") {
1748 gr_rotate(GRRotation::LEFT);
1749 } else { // "ROTATION_NONE" or unknown string
1750 gr_rotate(GRRotation::NONE);
1751 }
1752 Redraw();
1753
1754 return 0;
1755}