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