recovery: ui: Support hardware virtual keys

 * Also swipe left -> KEY_BACK

Change-Id: I6bd8054485d680df35abb86cb79f1dda683e4459
diff --git a/recovery_ui/device.cpp b/recovery_ui/device.cpp
index e7ae1a3..cee07a4 100644
--- a/recovery_ui/device.cpp
+++ b/recovery_ui/device.cpp
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -77,18 +78,31 @@
   }
 
   switch (key) {
+    case KEY_RIGHTSHIFT:
     case KEY_DOWN:
     case KEY_VOLUMEDOWN:
+    case KEY_MENU:
       return kHighlightDown;
 
     case KEY_UP:
     case KEY_VOLUMEUP:
+    case KEY_SEARCH:
       return kHighlightUp;
 
     case KEY_ENTER:
     case KEY_POWER:
+    case BTN_MOUSE:
+    case KEY_SEND:
       return kInvokeItem;
 
+    case KEY_HOME:
+    case KEY_HOMEPAGE:
+      return kGoHome;
+
+    case KEY_BACKSPACE:
+    case KEY_BACK:
+      return kGoBack;
+
     default:
       // If you have all of the above buttons, any other buttons
       // are ignored. Otherwise, any button cycles the highlight.
diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h
index 7c76cdb..7efa075 100644
--- a/recovery_ui/include/recovery_ui/device.h
+++ b/recovery_ui/include/recovery_ui/device.h
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -32,6 +33,8 @@
   static constexpr const int kHighlightUp = -2;
   static constexpr const int kHighlightDown = -3;
   static constexpr const int kInvokeItem = -4;
+  static constexpr const int kGoBack = -5;
+  static constexpr const int kGoHome = -6;
 
   // ENTER vs REBOOT: The latter will trigger a reboot that goes through bootloader, which allows
   // using a new bootloader / recovery image if applicable. For example, REBOOT_RESCUE goes from
diff --git a/recovery_ui/include/recovery_ui/ui.h b/recovery_ui/include/recovery_ui/ui.h
index c2c012b..a28e15d 100644
--- a/recovery_ui/include/recovery_ui/ui.h
+++ b/recovery_ui/include/recovery_ui/ui.h
@@ -264,6 +264,7 @@
   const int touch_low_threshold_;
   const int touch_high_threshold_;
 
+  void OnTouchDeviceDetected(int fd);
   void OnKeyDetected(int key_code);
   void OnTouchEvent();
   int OnInputEvent(int fd, uint32_t epevents);
@@ -294,12 +295,19 @@
   bool has_down_key;
   bool has_touch_screen;
 
+  struct vkey_t {
+    int keycode;
+    Point min_;
+    Point max_;
+  };
+
   // Touch event related variables. See the comments in RecoveryUI::OnInputEvent().
   int touch_slot_;
   Point touch_pos_;
   Point touch_start_;
   bool touch_finger_down_;
   bool touch_swiping_;
+  std::vector<vkey_t> virtual_keys_;
   bool is_bootreason_recovery_ui_;
 
   std::thread input_thread_;
diff --git a/recovery_ui/screen_ui.cpp b/recovery_ui/screen_ui.cpp
index 870db62..17b0890 100644
--- a/recovery_ui/screen_ui.cpp
+++ b/recovery_ui/screen_ui.cpp
@@ -1232,10 +1232,19 @@
           break;
         case Device::kNoAction:
           break;
+        case Device::kGoBack:
+          chosen_item = Device::kGoBack;
+          break;
+        case Device::kGoHome:
+          chosen_item = Device::kGoHome;
+          break;
       }
     } else if (!menu_only) {
       chosen_item = action;
     }
+    if (chosen_item == Device::kGoBack || chosen_item == Device::kGoHome) {
+      break;
+    }
   }
 
   menu_.reset();
diff --git a/recovery_ui/ui.cpp b/recovery_ui/ui.cpp
index 6e239ef..aed6f49 100644
--- a/recovery_ui/ui.cpp
+++ b/recovery_ui/ui.cpp
@@ -89,6 +89,48 @@
   }
 }
 
+void RecoveryUI::OnTouchDeviceDetected(int fd) {
+  char name[256];
+  char path[PATH_MAX];
+  char buf[4096];
+
+  memset(name, 0, sizeof(name));
+  if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) {
+    return;
+  }
+  sprintf(path, "/sys/board_properties/virtualkeys.%s", name);
+  int vkfd = open(path, O_RDONLY);
+  if (vkfd < 0) {
+    LOG(INFO) << "vkeys: could not open " << path;
+    return;
+  }
+  ssize_t len = read(vkfd, buf, sizeof(buf));
+  close(vkfd);
+  if (len <= 0) {
+    LOG(ERROR) << "vkeys: could not read " << path;
+    return;
+  }
+  buf[len] = '\0';
+
+  char* p = buf;
+  char* endp;
+  for (size_t n = 0; p < buf + len && *p == '0'; ++n) {
+    int val[6];
+    int f;
+    for (f = 0; *p && f < 6; ++f) {
+      val[f] = strtol(p, &endp, 0);
+      if (p == endp) break;
+      p = endp + 1;
+    }
+    if (f != 6 || val[0] != 0x01) break;
+    vkey_t vk;
+    vk.keycode = val[1];
+    vk.min_ = Point(val[2] - val[4] / 2, val[3] - val[5] / 2);
+    vk.max_ = Point(val[2] + val[4] / 2, val[3] + val[5] / 2);
+    virtual_keys_.push_back(vk);
+  }
+}
+
 void RecoveryUI::OnKeyDetected(int key_code) {
   if (key_code == KEY_POWER) {
     has_power_key = true;
@@ -147,7 +189,9 @@
   ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
 
   if (touch_screen_allowed_) {
-    ev_iterate_touch_inputs(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
+    ev_iterate_touch_inputs(
+        std::bind(&RecoveryUI::OnTouchDeviceDetected, this, std::placeholders::_1),
+        std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
 
     // Parse /proc/cmdline to determine if it's booting into recovery with a bootreason of
     // "recovery_ui". This specific reason is set by some (wear) bootloaders, to allow an easier way
@@ -191,6 +235,14 @@
   } else if (abs(delta.x()) < touch_low_threshold_ && abs(delta.y()) > touch_high_threshold_) {
     direction = delta.y() < 0 ? SwipeDirection::UP : SwipeDirection::DOWN;
   } else {
+    for (const auto& vk : virtual_keys_) {
+      if (touch_start_.x() >= vk.min_.x() && touch_start_.x() < vk.max_.x() &&
+          touch_start_.y() >= vk.min_.y() && touch_start_.y() < vk.max_.y()) {
+        ProcessKey(vk.keycode, 1);  // press key
+        ProcessKey(vk.keycode, 0);  // and release it
+        return;
+      }
+    }
     LOG(DEBUG) << "Ignored " << delta.x() << " " << delta.y() << " (low: " << touch_low_threshold_
                << ", high: " << touch_high_threshold_ << ")";
     return;
@@ -215,6 +267,9 @@
       break;
 
     case SwipeDirection::LEFT:
+      ProcessKey(KEY_BACK, 1);  // press back key
+      ProcessKey(KEY_BACK, 0);  // and release it
+      break;
     case SwipeDirection::RIGHT:
       ProcessKey(KEY_POWER, 1);  // press power key
       ProcessKey(KEY_POWER, 0);  // and release it