1 /* 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 17 #include "recovery_ui/ui.h" 18 19 #include <errno.h> 20 #include <fcntl.h> 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <string.h> 24 #include <sys/time.h> 25 #include <sys/types.h> 26 #include <time.h> 27 #include <unistd.h> 28 29 #include <chrono> 30 #include <functional> 31 #include <string> 32 #include <thread> 33 34 #include <android-base/file.h> 35 #include <android-base/logging.h> 36 #include <android-base/parseint.h> 37 #include <android-base/properties.h> 38 #include <android-base/strings.h> 39 40 #include "minui/minui.h" 41 #include "otautil/sysutil.h" 42 43 using namespace std::chrono_literals; 44 45 constexpr int UI_WAIT_KEY_TIMEOUT_SEC = 120; 46 constexpr const char* BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/brightness"; 47 constexpr const char* MAX_BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/max_brightness"; 48 constexpr const char* BRIGHTNESS_FILE_SDM = "/sys/class/backlight/panel0-backlight/brightness"; 49 constexpr const char* MAX_BRIGHTNESS_FILE_SDM = 50 "/sys/class/backlight/panel0-backlight/max_brightness"; 51 52 constexpr int kDefaultTouchLowThreshold = 50; 53 constexpr int kDefaultTouchHighThreshold = 90; 54 55 RecoveryUI::RecoveryUI() 56 : brightness_normal_(50), 57 brightness_dimmed_(25), 58 brightness_file_(BRIGHTNESS_FILE), 59 max_brightness_file_(MAX_BRIGHTNESS_FILE), 60 touch_screen_allowed_(false), 61 fastbootd_logo_enabled_(false), 62 touch_low_threshold_(android::base::GetIntProperty("ro.recovery.ui.touch_low_threshold", 63 kDefaultTouchLowThreshold)), 64 touch_high_threshold_(android::base::GetIntProperty("ro.recovery.ui.touch_high_threshold", 65 kDefaultTouchHighThreshold)), 66 key_interrupted_(false), 67 key_queue_len(0), 68 key_last_down(-1), 69 key_long_press(false), 70 key_down_count(0), 71 enable_reboot(true), 72 consecutive_power_keys(0), 73 last_key(-1), 74 has_power_key(false), 75 has_up_key(false), 76 has_down_key(false), 77 has_touch_screen(false), 78 touch_slot_(0), 79 is_bootreason_recovery_ui_(false), 80 screensaver_state_(ScreensaverState::DISABLED) { 81 memset(key_pressed, 0, sizeof(key_pressed)); 82 } 83 84 RecoveryUI::~RecoveryUI() { 85 ev_exit(); 86 input_thread_stopped_ = true; 87 if (input_thread_.joinable()) { 88 input_thread_.join(); 89 } 90 } 91 92 void RecoveryUI::OnKeyDetected(int key_code) { 93 if (key_code == KEY_POWER) { 94 has_power_key = true; 95 } else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) { 96 has_down_key = true; 97 } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) { 98 has_up_key = true; 99 } else if (key_code == ABS_MT_POSITION_X || key_code == ABS_MT_POSITION_Y) { 100 has_touch_screen = true; 101 } 102 } 103 104 bool RecoveryUI::InitScreensaver() { 105 // Disabled. 106 if (brightness_normal_ == 0 || brightness_dimmed_ > brightness_normal_) { 107 return false; 108 } 109 if (access(brightness_file_.c_str(), R_OK | W_OK)) { 110 brightness_file_ = BRIGHTNESS_FILE_SDM; 111 } 112 if (access(max_brightness_file_.c_str(), R_OK)) { 113 max_brightness_file_ = MAX_BRIGHTNESS_FILE_SDM; 114 } 115 // Set the initial brightness level based on the max brightness. Note that reading the initial 116 // value from BRIGHTNESS_FILE doesn't give the actual brightness value (bullhead, sailfish), so 117 // we don't have a good way to query the default value. 118 std::string content; 119 if (!android::base::ReadFileToString(max_brightness_file_, &content)) { 120 PLOG(WARNING) << "Failed to read max brightness"; 121 return false; 122 } 123 124 unsigned int max_value; 125 if (!android::base::ParseUint(android::base::Trim(content), &max_value)) { 126 LOG(WARNING) << "Failed to parse max brightness: " << content; 127 return false; 128 } 129 130 brightness_normal_value_ = max_value * brightness_normal_ / 100.0; 131 brightness_dimmed_value_ = max_value * brightness_dimmed_ / 100.0; 132 if (!android::base::WriteStringToFile(std::to_string(brightness_normal_value_), 133 brightness_file_)) { 134 PLOG(WARNING) << "Failed to set brightness"; 135 return false; 136 } 137 138 LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ << "%)"; 139 screensaver_state_ = ScreensaverState::NORMAL; 140 return true; 141 } 142 143 bool RecoveryUI::Init(const std::string& /* locale */) { 144 ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2), 145 touch_screen_allowed_); 146 147 ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); 148 149 if (touch_screen_allowed_) { 150 ev_iterate_touch_inputs(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); 151 152 // Parse /proc/cmdline to determine if it's booting into recovery with a bootreason of 153 // "recovery_ui". This specific reason is set by some (wear) bootloaders, to allow an easier way 154 // to turn on text mode. It will only be set if the recovery boot is triggered from fastboot, or 155 // with 'adb reboot recovery'. Note that this applies to all build variants. Otherwise the text 156 // mode will be turned on automatically on debuggable builds, even without a swipe. 157 std::string cmdline; 158 if (android::base::ReadFileToString("/proc/cmdline", &cmdline)) { 159 is_bootreason_recovery_ui_ = cmdline.find("bootreason=recovery_ui") != std::string::npos; 160 } else { 161 // Non-fatal, and won't affect Init() result. 162 PLOG(WARNING) << "Failed to read /proc/cmdline"; 163 } 164 } 165 166 if (!InitScreensaver()) { 167 LOG(INFO) << "Screensaver disabled"; 168 } 169 170 // Create a separate thread that handles input events. 171 input_thread_ = std::thread([this]() { 172 while (!this->input_thread_stopped_) { 173 if (!ev_wait(500)) { 174 ev_dispatch(); 175 } 176 } 177 }); 178 179 return true; 180 } 181 182 void RecoveryUI::OnTouchDetected(int dx, int dy) { 183 enum SwipeDirection { UP, DOWN, RIGHT, LEFT } direction; 184 185 // We only consider a valid swipe if: 186 // - the delta along one axis is below touch_low_threshold_; 187 // - and the delta along the other axis is beyond touch_high_threshold_. 188 if (abs(dy) < touch_low_threshold_ && abs(dx) > touch_high_threshold_) { 189 direction = dx < 0 ? SwipeDirection::LEFT : SwipeDirection::RIGHT; 190 } else if (abs(dx) < touch_low_threshold_ && abs(dy) > touch_high_threshold_) { 191 direction = dy < 0 ? SwipeDirection::UP : SwipeDirection::DOWN; 192 } else { 193 LOG(DEBUG) << "Ignored " << dx << " " << dy << " (low: " << touch_low_threshold_ 194 << ", high: " << touch_high_threshold_ << ")"; 195 return; 196 } 197 198 // Allow turning on text mode with any swipe, if bootloader has set a bootreason of recovery_ui. 199 if (is_bootreason_recovery_ui_ && !IsTextVisible()) { 200 ShowText(true); 201 return; 202 } 203 204 LOG(DEBUG) << "Swipe direction=" << direction; 205 switch (direction) { 206 case SwipeDirection::UP: 207 ProcessKey(KEY_UP, 1); // press up key 208 ProcessKey(KEY_UP, 0); // and release it 209 break; 210 211 case SwipeDirection::DOWN: 212 ProcessKey(KEY_DOWN, 1); // press down key 213 ProcessKey(KEY_DOWN, 0); // and release it 214 break; 215 216 case SwipeDirection::LEFT: 217 case SwipeDirection::RIGHT: 218 ProcessKey(KEY_POWER, 1); // press power key 219 ProcessKey(KEY_POWER, 0); // and release it 220 break; 221 }; 222 } 223 224 int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { 225 struct input_event ev; 226 if (ev_get_input(fd, epevents, &ev) == -1) { 227 return -1; 228 } 229 230 // Touch inputs handling. 231 // 232 // We handle the touch inputs by tracking the position changes between initial contacting and 233 // upon lifting. touch_start_X/Y record the initial positions, with touch_finger_down set. Upon 234 // detecting the lift, we unset touch_finger_down and detect a swipe based on position changes. 235 // 236 // Per the doc Multi-touch Protocol at below, there are two protocols. 237 // https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt 238 // 239 // The main difference between the stateless type A protocol and the stateful type B slot protocol 240 // lies in the usage of identifiable contacts to reduce the amount of data sent to userspace. The 241 // slot protocol (i.e. type B) sends ABS_MT_TRACKING_ID with a unique id on initial contact, and 242 // sends ABS_MT_TRACKING_ID -1 upon lifting the contact. Protocol A doesn't send 243 // ABS_MT_TRACKING_ID -1 on lifting, but the driver may additionally report BTN_TOUCH event. 244 // 245 // For protocol A, we rely on BTN_TOUCH to recognize lifting, while for protocol B we look for 246 // ABS_MT_TRACKING_ID being -1. 247 // 248 // Touch input events will only be available if touch_screen_allowed_ is set. 249 250 if (ev.type == EV_SYN) { 251 if (touch_screen_allowed_ && ev.code == SYN_REPORT) { 252 // There might be multiple SYN_REPORT events. We should only detect a swipe after lifting the 253 // contact. 254 if (touch_finger_down_ && !touch_swiping_) { 255 touch_start_X_ = touch_X_; 256 touch_start_Y_ = touch_Y_; 257 touch_swiping_ = true; 258 } else if (!touch_finger_down_ && touch_swiping_) { 259 touch_swiping_ = false; 260 OnTouchDetected(touch_X_ - touch_start_X_, touch_Y_ - touch_start_Y_); 261 } 262 } 263 return 0; 264 } 265 266 if (ev.type == EV_REL) { 267 if (ev.code == REL_Y) { 268 // accumulate the up or down motion reported by 269 // the trackball. When it exceeds a threshold 270 // (positive or negative), fake an up/down 271 // key event. 272 rel_sum += ev.value; 273 if (rel_sum > 3) { 274 ProcessKey(KEY_DOWN, 1); // press down key 275 ProcessKey(KEY_DOWN, 0); // and release it 276 rel_sum = 0; 277 } else if (rel_sum < -3) { 278 ProcessKey(KEY_UP, 1); // press up key 279 ProcessKey(KEY_UP, 0); // and release it 280 rel_sum = 0; 281 } 282 } 283 } else { 284 rel_sum = 0; 285 } 286 287 if (touch_screen_allowed_ && ev.type == EV_ABS) { 288 if (ev.code == ABS_MT_SLOT) { 289 touch_slot_ = ev.value; 290 } 291 // Ignore other fingers. 292 if (touch_slot_ > 0) return 0; 293 294 switch (ev.code) { 295 case ABS_MT_POSITION_X: 296 touch_X_ = ev.value; 297 touch_finger_down_ = true; 298 break; 299 300 case ABS_MT_POSITION_Y: 301 touch_Y_ = ev.value; 302 touch_finger_down_ = true; 303 break; 304 305 case ABS_MT_TRACKING_ID: 306 // Protocol B: -1 marks lifting the contact. 307 if (ev.value < 0) touch_finger_down_ = false; 308 break; 309 } 310 return 0; 311 } 312 313 if (ev.type == EV_KEY && ev.code <= KEY_MAX) { 314 if (touch_screen_allowed_) { 315 if (ev.code == BTN_TOUCH) { 316 // A BTN_TOUCH with value 1 indicates the start of contact (protocol A), with 0 means 317 // lifting the contact. 318 touch_finger_down_ = (ev.value == 1); 319 } 320 321 // Intentionally ignore BTN_TOUCH and BTN_TOOL_FINGER, which would otherwise trigger 322 // additional scrolling (because in ScreenRecoveryUI::ShowFile(), we consider keys other than 323 // KEY_POWER and KEY_UP as KEY_DOWN). 324 if (ev.code == BTN_TOUCH || ev.code == BTN_TOOL_FINGER) { 325 return 0; 326 } 327 } 328 329 ProcessKey(ev.code, ev.value); 330 } 331 332 return 0; 333 } 334 335 // Processes a key-up or -down event. A key is "registered" when it is pressed and then released, 336 // with no other keypresses or releases in between. Registered keys are passed to CheckKey() to 337 // see if it should trigger a visibility toggle, an immediate reboot, or be queued to be processed 338 // next time the foreground thread wants a key (eg, for the menu). 339 // 340 // We also keep track of which keys are currently down so that CheckKey() can call IsKeyPressed() 341 // to see what other keys are held when a key is registered. 342 // 343 // updown == 1 for key down events; 0 for key up events 344 void RecoveryUI::ProcessKey(int key_code, int updown) { 345 bool register_key = false; 346 bool long_press = false; 347 348 { 349 std::lock_guard<std::mutex> lg(key_queue_mutex); 350 key_pressed[key_code] = updown; 351 if (updown) { 352 ++key_down_count; 353 key_last_down = key_code; 354 key_long_press = false; 355 std::thread time_key_thread(&RecoveryUI::TimeKey, this, key_code, key_down_count); 356 time_key_thread.detach(); 357 } else { 358 if (key_last_down == key_code) { 359 long_press = key_long_press; 360 register_key = true; 361 } 362 key_last_down = -1; 363 } 364 } 365 366 bool reboot_enabled = enable_reboot; 367 if (register_key) { 368 switch (CheckKey(key_code, long_press)) { 369 case RecoveryUI::IGNORE: 370 break; 371 372 case RecoveryUI::TOGGLE: 373 ShowText(!IsTextVisible()); 374 break; 375 376 case RecoveryUI::REBOOT: 377 if (reboot_enabled) { 378 reboot("reboot,"); 379 while (true) { 380 pause(); 381 } 382 } 383 break; 384 385 case RecoveryUI::ENQUEUE: 386 EnqueueKey(key_code); 387 break; 388 } 389 } 390 } 391 392 void RecoveryUI::TimeKey(int key_code, int count) { 393 std::this_thread::sleep_for(750ms); // 750 ms == "long" 394 bool long_press = false; 395 { 396 std::lock_guard<std::mutex> lg(key_queue_mutex); 397 if (key_last_down == key_code && key_down_count == count) { 398 long_press = key_long_press = true; 399 } 400 } 401 if (long_press) KeyLongPress(key_code); 402 } 403 404 void RecoveryUI::EnqueueKey(int key_code) { 405 std::lock_guard<std::mutex> lg(key_queue_mutex); 406 const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); 407 if (key_queue_len < queue_max) { 408 key_queue[key_queue_len++] = key_code; 409 key_queue_cond.notify_one(); 410 } 411 } 412 413 void RecoveryUI::SetScreensaverState(ScreensaverState state) { 414 switch (state) { 415 case ScreensaverState::NORMAL: 416 if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_), 417 brightness_file_)) { 418 screensaver_state_ = ScreensaverState::NORMAL; 419 LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ 420 << "%)"; 421 } else { 422 LOG(ERROR) << "Unable to set brightness to normal"; 423 } 424 break; 425 case ScreensaverState::DIMMED: 426 if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_), 427 brightness_file_)) { 428 LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_ 429 << "%)"; 430 screensaver_state_ = ScreensaverState::DIMMED; 431 } else { 432 LOG(ERROR) << "Unable to set brightness to dim"; 433 } 434 break; 435 case ScreensaverState::OFF: 436 if (android::base::WriteStringToFile("0", brightness_file_)) { 437 LOG(INFO) << "Brightness: 0 (off)"; 438 screensaver_state_ = ScreensaverState::OFF; 439 } else { 440 LOG(ERROR) << "Unable to set brightness to off"; 441 } 442 break; 443 default: 444 LOG(ERROR) << "Invalid screensaver state"; 445 } 446 } 447 448 int RecoveryUI::WaitKey() { 449 std::unique_lock<std::mutex> lk(key_queue_mutex); 450 451 // Check for a saved key queue interruption. 452 if (key_interrupted_) { 453 SetScreensaverState(ScreensaverState::NORMAL); 454 return static_cast<int>(KeyError::INTERRUPTED); 455 } 456 457 // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is plugged in. 458 do { 459 bool rc = key_queue_cond.wait_for(lk, std::chrono::seconds(UI_WAIT_KEY_TIMEOUT_SEC), [this] { 460 return this->key_queue_len != 0 || key_interrupted_; 461 }); 462 if (key_interrupted_) { 463 SetScreensaverState(ScreensaverState::NORMAL); 464 return static_cast<int>(KeyError::INTERRUPTED); 465 } 466 if (screensaver_state_ != ScreensaverState::DISABLED) { 467 if (!rc) { 468 // Must be after a timeout. Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF. 469 if (screensaver_state_ == ScreensaverState::NORMAL) { 470 SetScreensaverState(ScreensaverState::DIMMED); 471 } else if (screensaver_state_ == ScreensaverState::DIMMED) { 472 SetScreensaverState(ScreensaverState::OFF); 473 } 474 } else if (screensaver_state_ != ScreensaverState::NORMAL) { 475 // Drop the first key if it's changing from OFF to NORMAL. 476 if (screensaver_state_ == ScreensaverState::OFF) { 477 if (key_queue_len > 0) { 478 memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); 479 } 480 } 481 482 // Reset the brightness to normal. 483 SetScreensaverState(ScreensaverState::NORMAL); 484 } 485 } 486 } while (IsUsbConnected() && key_queue_len == 0); 487 488 int key = static_cast<int>(KeyError::TIMED_OUT); 489 if (key_queue_len > 0) { 490 key = key_queue[0]; 491 memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); 492 } 493 return key; 494 } 495 496 void RecoveryUI::InterruptKey() { 497 { 498 std::lock_guard<std::mutex> lg(key_queue_mutex); 499 key_interrupted_ = true; 500 } 501 key_queue_cond.notify_one(); 502 } 503 504 bool RecoveryUI::IsUsbConnected() { 505 int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); 506 if (fd < 0) { 507 printf("failed to open /sys/class/android_usb/android0/state: %s\n", strerror(errno)); 508 return 0; 509 } 510 511 char buf; 512 // USB is connected if android_usb state is CONNECTED or CONFIGURED. 513 int connected = (TEMP_FAILURE_RETRY(read(fd, &buf, 1)) == 1) && (buf == 'C'); 514 if (close(fd) < 0) { 515 printf("failed to close /sys/class/android_usb/android0/state: %s\n", strerror(errno)); 516 } 517 return connected; 518 } 519 520 bool RecoveryUI::IsKeyPressed(int key) { 521 std::lock_guard<std::mutex> lg(key_queue_mutex); 522 int pressed = key_pressed[key]; 523 return pressed; 524 } 525 526 bool RecoveryUI::IsLongPress() { 527 std::lock_guard<std::mutex> lg(key_queue_mutex); 528 bool result = key_long_press; 529 return result; 530 } 531 532 bool RecoveryUI::HasThreeButtons() { 533 return has_power_key && has_up_key && has_down_key; 534 } 535 536 bool RecoveryUI::HasPowerKey() const { 537 return has_power_key; 538 } 539 540 bool RecoveryUI::HasTouchScreen() const { 541 return has_touch_screen; 542 } 543 544 void RecoveryUI::FlushKeys() { 545 std::lock_guard<std::mutex> lg(key_queue_mutex); 546 key_queue_len = 0; 547 } 548 549 RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { 550 { 551 std::lock_guard<std::mutex> lg(key_queue_mutex); 552 key_long_press = false; 553 } 554 555 // If we have power and volume up keys, that chord is the signal to toggle the text display. 556 if (HasThreeButtons() || (HasPowerKey() && HasTouchScreen() && touch_screen_allowed_)) { 557 if ((key == KEY_VOLUMEUP || key == KEY_UP) && IsKeyPressed(KEY_POWER)) { 558 return TOGGLE; 559 } 560 } else { 561 // Otherwise long press of any button toggles to the text display, 562 // and there's no way to toggle back (but that's pretty useless anyway). 563 if (is_long_press && !IsTextVisible()) { 564 return TOGGLE; 565 } 566 567 // Also, for button-limited devices, a long press is translated to KEY_ENTER. 568 if (is_long_press && IsTextVisible()) { 569 EnqueueKey(KEY_ENTER); 570 return IGNORE; 571 } 572 } 573 574 // Press power seven times in a row to reboot. 575 if (key == KEY_POWER) { 576 bool reboot_enabled = enable_reboot; 577 578 if (reboot_enabled) { 579 ++consecutive_power_keys; 580 if (consecutive_power_keys >= 7) { 581 return REBOOT; 582 } 583 } 584 } else { 585 consecutive_power_keys = 0; 586 } 587 588 last_key = key; 589 return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE; 590 } 591 592 void RecoveryUI::KeyLongPress(int) {} 593 594 void RecoveryUI::SetEnableReboot(bool enabled) { 595 std::lock_guard<std::mutex> lg(key_queue_mutex); 596 enable_reboot = enabled; 597 } 598