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