ui: Move the support for touch inputs into RecoveryUI.

- Added detection for EV_ABS events in minui/events.cpp, if it's
  allowed;
- Added listening and processing touch inputs in ui.cpp;
- Fixed an issue in recognizing swipe with multi-touch protocol A;
- Changed the logic in RecoveryUI::ProcessKey() to be swipe-aware. It
  now allows turning on text mode with <power> + <swipe-up>.

The last change also fixed an issue on devices with protocol A: prior
to this CL, user may accidentally toggle the text mode during an OTA.
Because it was considered as a single-button device, a long tap that
sent BTN_TOUCH event would turn on text mode.

Test: Allow detecting touch inputs. Swiping (up, down, enter) works on
      angler, angelfish, dorado respectively.
Bug: 36169090
Change-Id: I4bc882b99114ce4ab414f8bdb8f4f7a525b8a8fd
diff --git a/Android.mk b/Android.mk
index 3eed7a6..967b9df 100644
--- a/Android.mk
+++ b/Android.mk
@@ -108,6 +108,18 @@
 LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_WIDTH=0
 endif
 
+ifneq ($(TARGET_RECOVERY_UI_TOUCH_LOW_THRESHOLD),)
+LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_LOW_THRESHOLD=$(TARGET_RECOVERY_UI_TOUCH_LOW_THRESHOLD)
+else
+LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_LOW_THRESHOLD=50
+endif
+
+ifneq ($(TARGET_RECOVERY_UI_TOUCH_HIGH_THRESHOLD),)
+LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_HIGH_THRESHOLD=$(TARGET_RECOVERY_UI_TOUCH_HIGH_THRESHOLD)
+else
+LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_HIGH_THRESHOLD=90
+endif
+
 ifneq ($(TARGET_RECOVERY_UI_VR_STEREO_OFFSET),)
 LOCAL_CFLAGS += -DRECOVERY_UI_VR_STEREO_OFFSET=$(TARGET_RECOVERY_UI_VR_STEREO_OFFSET)
 else
diff --git a/minui/events.cpp b/minui/events.cpp
index 0e1fd44..24c2a82 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -53,36 +53,37 @@
     return (array[bit/BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
 }
 
-int ev_init(ev_callback input_cb) {
-  bool epollctlfail = false;
-
+int ev_init(ev_callback input_cb, bool allow_touch_inputs) {
   g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
   if (g_epoll_fd == -1) {
     return -1;
   }
 
+  bool epollctlfail = false;
   DIR* dir = opendir("/dev/input");
-  if (dir != NULL) {
+  if (dir != nullptr) {
     dirent* de;
     while ((de = readdir(dir))) {
-      // Use unsigned long to match ioctl's parameter type.
-      unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];  // NOLINT
-
-      //            fprintf(stderr,"/dev/input/%s\n", de->d_name);
       if (strncmp(de->d_name, "event", 5)) continue;
       int fd = openat(dirfd(dir), de->d_name, O_RDONLY);
       if (fd == -1) continue;
 
+      // Use unsigned long to match ioctl's parameter type.
+      unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];  // NOLINT
+
       // Read the evbits of the input device.
       if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
         close(fd);
         continue;
       }
 
-      // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed.
+      // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also
+      // allowed if allow_touch_inputs is set.
       if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
-        close(fd);
-        continue;
+        if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) {
+          close(fd);
+          continue;
+        }
       }
 
       epoll_event ev;
@@ -231,3 +232,27 @@
         }
     }
 }
+
+void ev_iterate_touch_inputs(const std::function<void(int)>& action) {
+  for (size_t i = 0; i < ev_dev_count; ++i) {
+    // Use unsigned long to match ioctl's parameter type.
+    unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)] = {};  // NOLINT
+    if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+      continue;
+    }
+    if (!test_bit(EV_ABS, ev_bits)) {
+      continue;
+    }
+
+    unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)] = {};  // NOLINT
+    if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_ABS, KEY_MAX), key_bits) == -1) {
+      continue;
+    }
+
+    for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
+      if (test_bit(key_code, key_bits)) {
+        action(key_code);
+      }
+    }
+  }
+}
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index 78dd4cb..017ddde 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -74,10 +74,11 @@
 using ev_callback = std::function<int(int fd, uint32_t epevents)>;
 using ev_set_key_callback = std::function<int(int code, int value)>;
 
-int ev_init(ev_callback input_cb);
+int ev_init(ev_callback input_cb, bool allow_touch_inputs = false);
 void ev_exit();
 int ev_add_fd(int fd, ev_callback cb);
 void ev_iterate_available_keys(const std::function<void(int)>& f);
+void ev_iterate_touch_inputs(const std::function<void(int)>& action);
 int ev_sync_key_state(const ev_set_key_callback& set_key_cb);
 
 // 'timeout' has the same semantics as poll(2).
diff --git a/ui.cpp b/ui.cpp
index 30b42a1..eadcdd4 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -54,6 +54,9 @@
       rtl_locale_(false),
       brightness_normal_(50),
       brightness_dimmed_(25),
+      touch_screen_allowed_(false),
+      kTouchLowThreshold(RECOVERY_UI_TOUCH_LOW_THRESHOLD),
+      kTouchHighThreshold(RECOVERY_UI_TOUCH_HIGH_THRESHOLD),
       key_queue_len(0),
       key_last_down(-1),
       key_long_press(false),
@@ -64,6 +67,8 @@
       has_power_key(false),
       has_up_key(false),
       has_down_key(false),
+      has_touch_screen(false),
+      touch_slot_(0),
       screensaver_state_(ScreensaverState::DISABLED) {
   pthread_mutex_init(&key_queue_mutex, nullptr);
   pthread_cond_init(&key_queue_cond, nullptr);
@@ -77,6 +82,8 @@
     has_down_key = true;
   } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) {
     has_up_key = true;
+  } else if (key_code == ABS_MT_POSITION_X || key_code == ABS_MT_POSITION_Y) {
+    has_touch_screen = true;
   }
 }
 
@@ -128,10 +135,15 @@
   // Set up the locale info.
   SetLocale(locale);
 
-  ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2));
+  ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2),
+          touch_screen_allowed_);
 
   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));
+  }
+
   if (!InitScreensaver()) {
     LOG(INFO) << "Screensaver disabled";
   }
@@ -140,15 +152,85 @@
   return true;
 }
 
+void RecoveryUI::OnTouchDetected(int dx, int dy) {
+  enum SwipeDirection { UP, DOWN, RIGHT, LEFT } direction;
+
+  // We only consider a valid swipe if:
+  // - the delta along one axis is below kTouchLowThreshold;
+  // - and the delta along the other axis is beyond kTouchHighThreshold.
+  if (abs(dy) < kTouchLowThreshold && abs(dx) > kTouchHighThreshold) {
+    direction = dx < 0 ? SwipeDirection::LEFT : SwipeDirection::RIGHT;
+  } else if (abs(dx) < kTouchLowThreshold && abs(dy) > kTouchHighThreshold) {
+    direction = dy < 0 ? SwipeDirection::UP : SwipeDirection::DOWN;
+  } else {
+    LOG(DEBUG) << "Ignored " << dx << " " << dy << " (low: " << kTouchLowThreshold
+               << ", high: " << kTouchHighThreshold << ")";
+    return;
+  }
+
+  LOG(DEBUG) << "Swipe direction=" << direction;
+  switch (direction) {
+    case SwipeDirection::UP:
+      ProcessKey(KEY_UP, 1);  // press up key
+      ProcessKey(KEY_UP, 0);  // and release it
+      break;
+
+    case SwipeDirection::DOWN:
+      ProcessKey(KEY_DOWN, 1);  // press down key
+      ProcessKey(KEY_DOWN, 0);  // and release it
+      break;
+
+    case SwipeDirection::LEFT:
+    case SwipeDirection::RIGHT:
+      ProcessKey(KEY_POWER, 1);  // press power key
+      ProcessKey(KEY_POWER, 0);  // and release it
+      break;
+  };
+}
+
 int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
   struct input_event ev;
   if (ev_get_input(fd, epevents, &ev) == -1) {
     return -1;
   }
 
+  // Touch inputs handling.
+  //
+  // We handle the touch inputs by tracking the position changes between initial contacting and
+  // upon lifting. touch_start_X/Y record the initial positions, with touch_finger_down set. Upon
+  // detecting the lift, we unset touch_finger_down and detect a swipe based on position changes.
+  //
+  // Per the doc Multi-touch Protocol at below, there are two protocols.
+  // https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt
+  //
+  // The main difference between the stateless type A protocol and the stateful type B slot protocol
+  // lies in the usage of identifiable contacts to reduce the amount of data sent to userspace. The
+  // slot protocol (i.e. type B) sends ABS_MT_TRACKING_ID with a unique id on initial contact, and
+  // sends ABS_MT_TRACKING_ID -1 upon lifting the contact. Protocol A doesn't send
+  // ABS_MT_TRACKING_ID -1 on lifting, but the driver may additionally report BTN_TOUCH event.
+  //
+  // For protocol A, we rely on BTN_TOUCH to recognize lifting, while for protocol B we look for
+  // ABS_MT_TRACKING_ID being -1.
+  //
+  // Touch input events will only be available if touch_screen_allowed_ is set.
+
   if (ev.type == EV_SYN) {
+    if (touch_screen_allowed_ && ev.code == SYN_REPORT) {
+      // There might be multiple SYN_REPORT events. We should only detect a swipe after lifting the
+      // contact.
+      if (touch_finger_down_ && !touch_swiping_) {
+        touch_start_X_ = touch_X_;
+        touch_start_Y_ = touch_Y_;
+        touch_swiping_ = true;
+      } else if (!touch_finger_down_ && touch_swiping_) {
+        touch_swiping_ = false;
+        OnTouchDetected(touch_X_ - touch_start_X_, touch_Y_ - touch_start_Y_);
+      }
+    }
     return 0;
-  } else if (ev.type == EV_REL) {
+  }
+
+  if (ev.type == EV_REL) {
     if (ev.code == REL_Y) {
       // accumulate the up or down motion reported by
       // the trackball.  When it exceeds a threshold
@@ -169,7 +251,48 @@
     rel_sum = 0;
   }
 
+  if (touch_screen_allowed_ && ev.type == EV_ABS) {
+    if (ev.code == ABS_MT_SLOT) {
+      touch_slot_ = ev.value;
+    }
+    // Ignore other fingers.
+    if (touch_slot_ > 0) return 0;
+
+    switch (ev.code) {
+      case ABS_MT_POSITION_X:
+        touch_X_ = ev.value;
+        touch_finger_down_ = true;
+        break;
+
+      case ABS_MT_POSITION_Y:
+        touch_Y_ = ev.value;
+        touch_finger_down_ = true;
+        break;
+
+      case ABS_MT_TRACKING_ID:
+        // Protocol B: -1 marks lifting the contact.
+        if (ev.value < 0) touch_finger_down_ = false;
+        break;
+    }
+    return 0;
+  }
+
   if (ev.type == EV_KEY && ev.code <= KEY_MAX) {
+    if (touch_screen_allowed_) {
+      if (ev.code == BTN_TOUCH) {
+        // A BTN_TOUCH with value 1 indicates the start of contact (protocol A), with 0 means
+        // lifting the contact.
+        touch_finger_down_ = (ev.value == 1);
+      }
+
+      // Intentionally ignore BTN_TOUCH and BTN_TOOL_FINGER, which would otherwise trigger
+      // additional scrolling (because in ScreenRecoveryUI::ShowFile(), we consider keys other than
+      // KEY_POWER and KEY_UP as KEY_DOWN).
+      if (ev.code == BTN_TOUCH || ev.code == BTN_TOOL_FINGER) {
+        return 0;
+      }
+    }
+
     ProcessKey(ev.code, ev.value);
   }
 
@@ -365,6 +488,14 @@
   return has_power_key && has_up_key && has_down_key;
 }
 
+bool RecoveryUI::HasPowerKey() const {
+  return has_power_key;
+}
+
+bool RecoveryUI::HasTouchScreen() const {
+  return has_touch_screen;
+}
+
 void RecoveryUI::FlushKeys() {
   pthread_mutex_lock(&key_queue_mutex);
   key_queue_len = 0;
@@ -377,8 +508,8 @@
   pthread_mutex_unlock(&key_queue_mutex);
 
   // If we have power and volume up keys, that chord is the signal to toggle the text display.
-  if (HasThreeButtons()) {
-    if (key == KEY_VOLUMEUP && IsKeyPressed(KEY_POWER)) {
+  if (HasThreeButtons() || (HasPowerKey() && HasTouchScreen() && touch_screen_allowed_)) {
+    if ((key == KEY_VOLUMEUP || key == KEY_UP) && IsKeyPressed(KEY_POWER)) {
       return TOGGLE;
     }
   } else {
diff --git a/ui.h b/ui.h
index 7eb04ae..5cda7af 100644
--- a/ui.h
+++ b/ui.h
@@ -82,6 +82,12 @@
   // otherwise.
   virtual bool HasThreeButtons();
 
+  // Returns true if it has a power key.
+  virtual bool HasPowerKey() const;
+
+  // Returns true if it supports touch inputs.
+  virtual bool HasTouchScreen() const;
+
   // Erases any queued-up keys.
   virtual void FlushKeys();
 
@@ -129,7 +135,14 @@
   unsigned int brightness_normal_;
   unsigned int brightness_dimmed_;
 
+  // Whether we should listen for touch inputs (default: false).
+  bool touch_screen_allowed_;
+
  private:
+  // The sensitivity when detecting a swipe.
+  const int kTouchLowThreshold;
+  const int kTouchHighThreshold;
+
   // Key event input queue
   pthread_mutex_t key_queue_mutex;
   pthread_cond_t key_queue_cond;
@@ -147,6 +160,16 @@
   bool has_power_key;
   bool has_up_key;
   bool has_down_key;
+  bool has_touch_screen;
+
+  // Touch event related variables. See the comments in RecoveryUI::OnInputEvent().
+  int touch_slot_;
+  int touch_X_;
+  int touch_Y_;
+  int touch_start_X_;
+  int touch_start_Y_;
+  bool touch_finger_down_;
+  bool touch_swiping_;
 
   struct key_timer_t {
     RecoveryUI* ui;
@@ -157,6 +180,7 @@
   pthread_t input_thread_;
 
   void OnKeyDetected(int key_code);
+  void OnTouchDetected(int dx, int dy);
   int OnInputEvent(int fd, uint32_t epevents);
   void ProcessKey(int key_code, int updown);