Merge "recovery: Add screensaver mode."
diff --git a/ui.cpp b/ui.cpp
index a0f741e..5efdc5a 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -32,7 +32,11 @@
 
 #include <string>
 
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
 #include <android-base/properties.h>
+#include <android-base/strings.h>
 #include <cutils/android_reboot.h>
 #include <minui/minui.h>
 
@@ -40,11 +44,15 @@
 #include "roots.h"
 #include "device.h"
 
-#define UI_WAIT_KEY_TIMEOUT_SEC    120
+static constexpr int UI_WAIT_KEY_TIMEOUT_SEC = 120;
+static constexpr const char* BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/brightness";
+static constexpr const char* MAX_BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/max_brightness";
 
 RecoveryUI::RecoveryUI()
     : locale_(""),
       rtl_locale_(false),
+      brightness_normal_(50),
+      brightness_dimmed_(25),
       key_queue_len(0),
       key_last_down(-1),
       key_long_press(false),
@@ -54,7 +62,8 @@
       last_key(-1),
       has_power_key(false),
       has_up_key(false),
-      has_down_key(false) {
+      has_down_key(false),
+      screensaver_state_(ScreensaverState::DISABLED) {
   pthread_mutex_init(&key_queue_mutex, nullptr);
   pthread_cond_init(&key_queue_cond, nullptr);
   memset(key_pressed, 0, sizeof(key_pressed));
@@ -80,6 +89,40 @@
     return nullptr;
 }
 
+bool RecoveryUI::InitScreensaver() {
+  // Disabled.
+  if (brightness_normal_ == 0 || brightness_dimmed_ > brightness_normal_) {
+    return false;
+  }
+
+  // Set the initial brightness level based on the max brightness. Note that reading the initial
+  // value from BRIGHTNESS_FILE doesn't give the actual brightness value (bullhead, sailfish), so
+  // we don't have a good way to query the default value.
+  std::string content;
+  if (!android::base::ReadFileToString(MAX_BRIGHTNESS_FILE, &content)) {
+    LOG(WARNING) << "Failed to read max brightness: " << content;
+    return false;
+  }
+
+  unsigned int max_value;
+  if (!android::base::ParseUint(android::base::Trim(content), &max_value)) {
+    LOG(WARNING) << "Failed to parse max brightness: " << content;
+    return false;
+  }
+
+  brightness_normal_value_ = max_value * brightness_normal_ / 100.0;
+  brightness_dimmed_value_ = max_value * brightness_dimmed_ / 100.0;
+  if (!android::base::WriteStringToFile(std::to_string(brightness_normal_value_),
+                                        BRIGHTNESS_FILE)) {
+    PLOG(WARNING) << "Failed to set brightness";
+    return false;
+  }
+
+  LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ << "%)";
+  screensaver_state_ = ScreensaverState::NORMAL;
+  return true;
+}
+
 bool RecoveryUI::Init(const std::string& locale) {
   // Set up the locale info.
   SetLocale(locale);
@@ -88,6 +131,10 @@
 
   ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
 
+  if (!InitScreensaver()) {
+    LOG(INFO) << "Screensaver disabled";
+  }
+
   pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr);
   return true;
 }
@@ -220,31 +267,65 @@
 }
 
 int RecoveryUI::WaitKey() {
-    pthread_mutex_lock(&key_queue_mutex);
+  pthread_mutex_lock(&key_queue_mutex);
 
-    // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is
-    // plugged in.
-    do {
-        struct timeval now;
-        struct timespec timeout;
-        gettimeofday(&now, nullptr);
-        timeout.tv_sec = now.tv_sec;
-        timeout.tv_nsec = now.tv_usec * 1000;
-        timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC;
+  // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is
+  // plugged in.
+  do {
+    struct timeval now;
+    struct timespec timeout;
+    gettimeofday(&now, nullptr);
+    timeout.tv_sec = now.tv_sec;
+    timeout.tv_nsec = now.tv_usec * 1000;
+    timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC;
 
-        int rc = 0;
-        while (key_queue_len == 0 && rc != ETIMEDOUT) {
-            rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout);
-        }
-    } while (IsUsbConnected() && key_queue_len == 0);
-
-    int key = -1;
-    if (key_queue_len > 0) {
-        key = key_queue[0];
-        memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+    int rc = 0;
+    while (key_queue_len == 0 && rc != ETIMEDOUT) {
+      rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout);
     }
-    pthread_mutex_unlock(&key_queue_mutex);
-    return key;
+
+    if (screensaver_state_ != ScreensaverState::DISABLED) {
+      if (rc == ETIMEDOUT) {
+        // Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF.
+        if (screensaver_state_ == ScreensaverState::NORMAL) {
+          if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_),
+                                               BRIGHTNESS_FILE)) {
+            LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_
+                      << "%)";
+            screensaver_state_ = ScreensaverState::DIMMED;
+          }
+        } else if (screensaver_state_ == ScreensaverState::DIMMED) {
+          if (android::base::WriteStringToFile("0", BRIGHTNESS_FILE)) {
+            LOG(INFO) << "Brightness: 0 (off)";
+            screensaver_state_ = ScreensaverState::OFF;
+          }
+        }
+      } else if (screensaver_state_ != ScreensaverState::NORMAL) {
+        // Drop the first key if it's changing from OFF to NORMAL.
+        if (screensaver_state_ == ScreensaverState::OFF) {
+          if (key_queue_len > 0) {
+            memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+          }
+        }
+
+        // Reset the brightness to normal.
+        if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_),
+                                             BRIGHTNESS_FILE)) {
+          screensaver_state_ = ScreensaverState::NORMAL;
+          LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_
+                    << "%)";
+        }
+      }
+    }
+  } while (IsUsbConnected() && key_queue_len == 0);
+
+  int key = -1;
+  if (key_queue_len > 0) {
+    key = key_queue[0];
+    memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+  }
+  pthread_mutex_unlock(&key_queue_mutex);
+  return key;
 }
 
 bool RecoveryUI::IsUsbConnected() {
@@ -330,7 +411,7 @@
     }
 
     last_key = key;
-    return IsTextVisible() ? ENQUEUE : IGNORE;
+    return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE;
 }
 
 void RecoveryUI::KeyLongPress(int) {
diff --git a/ui.h b/ui.h
index 53ce060..823eb65 100644
--- a/ui.h
+++ b/ui.h
@@ -130,6 +130,13 @@
     std::string locale_;
     bool rtl_locale_;
 
+    // The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25%
+    // of the max_brightness). Because the absolute values may vary across devices. These two
+    // values can be configured via subclassing. Setting brightness_normal_ to 0 to disable
+    // screensaver.
+    unsigned int brightness_normal_;
+    unsigned int brightness_dimmed_;
+
   private:
     // Key event input queue
     pthread_mutex_t key_queue_mutex;
@@ -167,6 +174,14 @@
     void time_key(int key_code, int count);
 
     void SetLocale(const std::string&);
+
+    enum class ScreensaverState { DISABLED, NORMAL, DIMMED, OFF };
+    ScreensaverState screensaver_state_;
+    // The following two contain the absolute values computed from brightness_normal_ and
+    // brightness_dimmed_ respectively.
+    unsigned int brightness_normal_value_;
+    unsigned int brightness_dimmed_value_;
+    bool InitScreensaver();
 };
 
 #endif  // RECOVERY_UI_H