Home | History | Annotate | Download | only in recovery
      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       key_queue_len(0),
     58       key_last_down(-1),
     59       key_long_press(false),
     60       key_down_count(0),
     61       enable_reboot(true),
     62       consecutive_power_keys(0),
     63       last_key(-1),
     64       has_power_key(false),
     65       has_up_key(false),
     66       has_down_key(false),
     67       screensaver_state_(ScreensaverState::DISABLED) {
     68   pthread_mutex_init(&key_queue_mutex, nullptr);
     69   pthread_cond_init(&key_queue_cond, nullptr);
     70   memset(key_pressed, 0, sizeof(key_pressed));
     71 }
     72 
     73 void RecoveryUI::OnKeyDetected(int key_code) {
     74     if (key_code == KEY_POWER) {
     75         has_power_key = true;
     76     } else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) {
     77         has_down_key = true;
     78     } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) {
     79         has_up_key = true;
     80     }
     81 }
     82 
     83 // Reads input events, handles special hot keys, and adds to the key queue.
     84 static void* InputThreadLoop(void*) {
     85     while (true) {
     86         if (!ev_wait(-1)) {
     87             ev_dispatch();
     88         }
     89     }
     90     return nullptr;
     91 }
     92 
     93 bool RecoveryUI::InitScreensaver() {
     94   // Disabled.
     95   if (brightness_normal_ == 0 || brightness_dimmed_ > brightness_normal_) {
     96     return false;
     97   }
     98 
     99   // Set the initial brightness level based on the max brightness. Note that reading the initial
    100   // value from BRIGHTNESS_FILE doesn't give the actual brightness value (bullhead, sailfish), so
    101   // we don't have a good way to query the default value.
    102   std::string content;
    103   if (!android::base::ReadFileToString(MAX_BRIGHTNESS_FILE, &content)) {
    104     PLOG(WARNING) << "Failed to read max brightness";
    105     return false;
    106   }
    107 
    108   unsigned int max_value;
    109   if (!android::base::ParseUint(android::base::Trim(content), &max_value)) {
    110     LOG(WARNING) << "Failed to parse max brightness: " << content;
    111     return false;
    112   }
    113 
    114   brightness_normal_value_ = max_value * brightness_normal_ / 100.0;
    115   brightness_dimmed_value_ = max_value * brightness_dimmed_ / 100.0;
    116   if (!android::base::WriteStringToFile(std::to_string(brightness_normal_value_),
    117                                         BRIGHTNESS_FILE)) {
    118     PLOG(WARNING) << "Failed to set brightness";
    119     return false;
    120   }
    121 
    122   LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ << "%)";
    123   screensaver_state_ = ScreensaverState::NORMAL;
    124   return true;
    125 }
    126 
    127 bool RecoveryUI::Init(const std::string& locale) {
    128   // Set up the locale info.
    129   SetLocale(locale);
    130 
    131   ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2));
    132 
    133   ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
    134 
    135   if (!InitScreensaver()) {
    136     LOG(INFO) << "Screensaver disabled";
    137   }
    138 
    139   pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr);
    140   return true;
    141 }
    142 
    143 int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
    144     struct input_event ev;
    145     if (ev_get_input(fd, epevents, &ev) == -1) {
    146         return -1;
    147     }
    148 
    149     if (ev.type == EV_SYN) {
    150         return 0;
    151     } else if (ev.type == EV_REL) {
    152         if (ev.code == REL_Y) {
    153             // accumulate the up or down motion reported by
    154             // the trackball.  When it exceeds a threshold
    155             // (positive or negative), fake an up/down
    156             // key event.
    157             rel_sum += ev.value;
    158             if (rel_sum > 3) {
    159                 ProcessKey(KEY_DOWN, 1);   // press down key
    160                 ProcessKey(KEY_DOWN, 0);   // and release it
    161                 rel_sum = 0;
    162             } else if (rel_sum < -3) {
    163                 ProcessKey(KEY_UP, 1);     // press up key
    164                 ProcessKey(KEY_UP, 0);     // and release it
    165                 rel_sum = 0;
    166             }
    167         }
    168     } else {
    169         rel_sum = 0;
    170     }
    171 
    172     if (ev.type == EV_KEY && ev.code <= KEY_MAX) {
    173         ProcessKey(ev.code, ev.value);
    174     }
    175 
    176     return 0;
    177 }
    178 
    179 // Process a key-up or -down event.  A key is "registered" when it is
    180 // pressed and then released, with no other keypresses or releases in
    181 // between.  Registered keys are passed to CheckKey() to see if it
    182 // should trigger a visibility toggle, an immediate reboot, or be
    183 // queued to be processed next time the foreground thread wants a key
    184 // (eg, for the menu).
    185 //
    186 // We also keep track of which keys are currently down so that
    187 // CheckKey can call IsKeyPressed to see what other keys are held when
    188 // a key is registered.
    189 //
    190 // updown == 1 for key down events; 0 for key up events
    191 void RecoveryUI::ProcessKey(int key_code, int updown) {
    192     bool register_key = false;
    193     bool long_press = false;
    194     bool reboot_enabled;
    195 
    196     pthread_mutex_lock(&key_queue_mutex);
    197     key_pressed[key_code] = updown;
    198     if (updown) {
    199         ++key_down_count;
    200         key_last_down = key_code;
    201         key_long_press = false;
    202         key_timer_t* info = new key_timer_t;
    203         info->ui = this;
    204         info->key_code = key_code;
    205         info->count = key_down_count;
    206         pthread_t thread;
    207         pthread_create(&thread, nullptr, &RecoveryUI::time_key_helper, info);
    208         pthread_detach(thread);
    209     } else {
    210         if (key_last_down == key_code) {
    211             long_press = key_long_press;
    212             register_key = true;
    213         }
    214         key_last_down = -1;
    215     }
    216     reboot_enabled = enable_reboot;
    217     pthread_mutex_unlock(&key_queue_mutex);
    218 
    219     if (register_key) {
    220         switch (CheckKey(key_code, long_press)) {
    221           case RecoveryUI::IGNORE:
    222             break;
    223 
    224           case RecoveryUI::TOGGLE:
    225             ShowText(!IsTextVisible());
    226             break;
    227 
    228           case RecoveryUI::REBOOT:
    229             if (reboot_enabled) {
    230                 reboot("reboot,");
    231                 while (true) { pause(); }
    232             }
    233             break;
    234 
    235           case RecoveryUI::ENQUEUE:
    236             EnqueueKey(key_code);
    237             break;
    238         }
    239     }
    240 }
    241 
    242 void* RecoveryUI::time_key_helper(void* cookie) {
    243     key_timer_t* info = static_cast<key_timer_t*>(cookie);
    244     info->ui->time_key(info->key_code, info->count);
    245     delete info;
    246     return nullptr;
    247 }
    248 
    249 void RecoveryUI::time_key(int key_code, int count) {
    250     usleep(750000);  // 750 ms == "long"
    251     bool long_press = false;
    252     pthread_mutex_lock(&key_queue_mutex);
    253     if (key_last_down == key_code && key_down_count == count) {
    254         long_press = key_long_press = true;
    255     }
    256     pthread_mutex_unlock(&key_queue_mutex);
    257     if (long_press) KeyLongPress(key_code);
    258 }
    259 
    260 void RecoveryUI::EnqueueKey(int key_code) {
    261     pthread_mutex_lock(&key_queue_mutex);
    262     const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
    263     if (key_queue_len < queue_max) {
    264         key_queue[key_queue_len++] = key_code;
    265         pthread_cond_signal(&key_queue_cond);
    266     }
    267     pthread_mutex_unlock(&key_queue_mutex);
    268 }
    269 
    270 int RecoveryUI::WaitKey() {
    271   pthread_mutex_lock(&key_queue_mutex);
    272 
    273   // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is
    274   // plugged in.
    275   do {
    276     struct timeval now;
    277     struct timespec timeout;
    278     gettimeofday(&now, nullptr);
    279     timeout.tv_sec = now.tv_sec;
    280     timeout.tv_nsec = now.tv_usec * 1000;
    281     timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC;
    282 
    283     int rc = 0;
    284     while (key_queue_len == 0 && rc != ETIMEDOUT) {
    285       rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout);
    286     }
    287 
    288     if (screensaver_state_ != ScreensaverState::DISABLED) {
    289       if (rc == ETIMEDOUT) {
    290         // Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF.
    291         if (screensaver_state_ == ScreensaverState::NORMAL) {
    292           if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_),
    293                                                BRIGHTNESS_FILE)) {
    294             LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_
    295                       << "%)";
    296             screensaver_state_ = ScreensaverState::DIMMED;
    297           }
    298         } else if (screensaver_state_ == ScreensaverState::DIMMED) {
    299           if (android::base::WriteStringToFile("0", BRIGHTNESS_FILE)) {
    300             LOG(INFO) << "Brightness: 0 (off)";
    301             screensaver_state_ = ScreensaverState::OFF;
    302           }
    303         }
    304       } else if (screensaver_state_ != ScreensaverState::NORMAL) {
    305         // Drop the first key if it's changing from OFF to NORMAL.
    306         if (screensaver_state_ == ScreensaverState::OFF) {
    307           if (key_queue_len > 0) {
    308             memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
    309           }
    310         }
    311 
    312         // Reset the brightness to normal.
    313         if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_),
    314                                              BRIGHTNESS_FILE)) {
    315           screensaver_state_ = ScreensaverState::NORMAL;
    316           LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_
    317                     << "%)";
    318         }
    319       }
    320     }
    321   } while (IsUsbConnected() && key_queue_len == 0);
    322 
    323   int key = -1;
    324   if (key_queue_len > 0) {
    325     key = key_queue[0];
    326     memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
    327   }
    328   pthread_mutex_unlock(&key_queue_mutex);
    329   return key;
    330 }
    331 
    332 bool RecoveryUI::IsUsbConnected() {
    333     int fd = open("/sys/class/android_usb/android0/state", O_RDONLY);
    334     if (fd < 0) {
    335         printf("failed to open /sys/class/android_usb/android0/state: %s\n",
    336                strerror(errno));
    337         return 0;
    338     }
    339 
    340     char buf;
    341     // USB is connected if android_usb state is CONNECTED or CONFIGURED.
    342     int connected = (TEMP_FAILURE_RETRY(read(fd, &buf, 1)) == 1) && (buf == 'C');
    343     if (close(fd) < 0) {
    344         printf("failed to close /sys/class/android_usb/android0/state: %s\n",
    345                strerror(errno));
    346     }
    347     return connected;
    348 }
    349 
    350 bool RecoveryUI::IsKeyPressed(int key) {
    351     pthread_mutex_lock(&key_queue_mutex);
    352     int pressed = key_pressed[key];
    353     pthread_mutex_unlock(&key_queue_mutex);
    354     return pressed;
    355 }
    356 
    357 bool RecoveryUI::IsLongPress() {
    358     pthread_mutex_lock(&key_queue_mutex);
    359     bool result = key_long_press;
    360     pthread_mutex_unlock(&key_queue_mutex);
    361     return result;
    362 }
    363 
    364 bool RecoveryUI::HasThreeButtons() {
    365     return has_power_key && has_up_key && has_down_key;
    366 }
    367 
    368 void RecoveryUI::FlushKeys() {
    369     pthread_mutex_lock(&key_queue_mutex);
    370     key_queue_len = 0;
    371     pthread_mutex_unlock(&key_queue_mutex);
    372 }
    373 
    374 RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) {
    375     pthread_mutex_lock(&key_queue_mutex);
    376     key_long_press = false;
    377     pthread_mutex_unlock(&key_queue_mutex);
    378 
    379     // If we have power and volume up keys, that chord is the signal to toggle the text display.
    380     if (HasThreeButtons()) {
    381         if (key == KEY_VOLUMEUP && IsKeyPressed(KEY_POWER)) {
    382             return TOGGLE;
    383         }
    384     } else {
    385         // Otherwise long press of any button toggles to the text display,
    386         // and there's no way to toggle back (but that's pretty useless anyway).
    387         if (is_long_press && !IsTextVisible()) {
    388             return TOGGLE;
    389         }
    390 
    391         // Also, for button-limited devices, a long press is translated to KEY_ENTER.
    392         if (is_long_press && IsTextVisible()) {
    393             EnqueueKey(KEY_ENTER);
    394             return IGNORE;
    395         }
    396     }
    397 
    398     // Press power seven times in a row to reboot.
    399     if (key == KEY_POWER) {
    400         pthread_mutex_lock(&key_queue_mutex);
    401         bool reboot_enabled = enable_reboot;
    402         pthread_mutex_unlock(&key_queue_mutex);
    403 
    404         if (reboot_enabled) {
    405             ++consecutive_power_keys;
    406             if (consecutive_power_keys >= 7) {
    407                 return REBOOT;
    408             }
    409         }
    410     } else {
    411         consecutive_power_keys = 0;
    412     }
    413 
    414     last_key = key;
    415     return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE;
    416 }
    417 
    418 void RecoveryUI::KeyLongPress(int) {
    419 }
    420 
    421 void RecoveryUI::SetEnableReboot(bool enabled) {
    422     pthread_mutex_lock(&key_queue_mutex);
    423     enable_reboot = enabled;
    424     pthread_mutex_unlock(&key_queue_mutex);
    425 }
    426 
    427 void RecoveryUI::SetLocale(const std::string& new_locale) {
    428   this->locale_ = new_locale;
    429   this->rtl_locale_ = false;
    430 
    431   if (!new_locale.empty()) {
    432     size_t underscore = new_locale.find('_');
    433     // lang has the language prefix prior to '_', or full string if '_' doesn't exist.
    434     std::string lang = new_locale.substr(0, underscore);
    435 
    436     // A bit cheesy: keep an explicit list of supported RTL languages.
    437     if (lang == "ar" ||  // Arabic
    438         lang == "fa" ||  // Persian (Farsi)
    439         lang == "he" ||  // Hebrew (new language code)
    440         lang == "iw" ||  // Hebrew (old language code)
    441         lang == "ur") {  // Urdu
    442       rtl_locale_ = true;
    443     }
    444   }
    445 }
    446