recovery: ui: Support hardware virtual keys

 * Also swipe left -> KEY_BACK

Change-Id: I6bd8054485d680df35abb86cb79f1dda683e4459
diff --git a/install/fuse_sdcard_install.cpp b/install/fuse_sdcard_install.cpp
index 1aa8768..e528e48 100644
--- a/install/fuse_sdcard_install.cpp
+++ b/install/fuse_sdcard_install.cpp
@@ -97,13 +97,16 @@
     if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
       return "";
     }
-
-    const std::string& item = entries[chosen_item];
-    if (chosen_item == 0) {
-      // Go up but continue browsing (if the caller is BrowseDirectory).
+    if (chosen_item == Device::kGoHome) {
+      return "@";
+    }
+    if (chosen_item == Device::kGoBack || chosen_item == 0) {
+      // Go up but continue browsing (if the caller is browse_directory).
       return "";
     }
 
+    const std::string& item = entries[chosen_item];
+
     std::string new_path = path + "/" + item;
     if (new_path.back() == '/') {
       // Recurse down into a subdirectory.
@@ -140,6 +143,9 @@
   }
 
   std::string path = BrowseDirectory(SDCARD_ROOT, device, ui);
+  if (path == "@") {
+    return INSTALL_NONE;
+  }
   if (path.empty()) {
     LOG(ERROR) << "\n-- No package file selected.\n";
     ensure_path_unmounted(SDCARD_ROOT);
diff --git a/minui/events.cpp b/minui/events.cpp
index 0eb8f72..c0f64f1 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -237,7 +237,8 @@
   }
 }
 
-void ev_iterate_touch_inputs(const std::function<void(int)>& key_detected) {
+void ev_iterate_touch_inputs(const std::function<void(int)>& touch_device_detected,
+                             const std::function<void(int)>& key_detected) {
   for (size_t i = 0; i < g_ev_dev_count; ++i) {
     // Use unsigned long to match ioctl's parameter type.
     unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)] = {};  // NOLINT
@@ -253,6 +254,8 @@
       continue;
     }
 
+    touch_device_detected(ev_fdinfo[i].fd);
+
     for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
       if (test_bit(key_code, key_bits)) {
         key_detected(key_code);
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index 5c7302c..a6b7b20 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -156,7 +156,8 @@
 void ev_exit();
 int ev_add_fd(android::base::unique_fd&& fd, ev_callback cb);
 void ev_iterate_available_keys(const std::function<void(int)>& key_detected);
-void ev_iterate_touch_inputs(const std::function<void(int)>& key_detected);
+void ev_iterate_touch_inputs(const std::function<void(int)>& touch_device_detected,
+                             const std::function<void(int)>& key_detected);
 int ev_sync_key_state(const ev_set_key_callback& set_key_cb);
 
 // 'timeout' has the same semantics as poll(2).
diff --git a/recovery.cpp b/recovery.cpp
index 5fc673e..9cf81e8 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -424,8 +424,10 @@
     if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
       break;
     }
-    if (entries[chosen_item] == "Back") break;
-
+    if (chosen_item == Device::kGoHome || chosen_item == Device::kGoBack ||
+        chosen_item == entries.size() - 1) {
+      break;
+    }
     ui->ShowFile(entries[chosen_item]);
   }
 }
@@ -498,6 +500,11 @@
     if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
       return Device::KEY_INTERRUPTED;
     }
+    // We are already in the main menu
+    if (chosen_item == Device::kGoBack || chosen_item == Device::kGoHome) {
+      continue;
+    }
+
     // Device-specific code may take some action here. It may return one of the core actions
     // handled in the switch statement below.
     Device::BuiltinAction chosen_action =
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