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