CHERRY-PICK: power: Add global hover support for touchpads

This CL expands powerd's understanding of how "hover detection" can work
on ChromeOS touchpads.  Previously powerd has been set up to look for
events coming out of the touchpad which are reporting specific fingers
before they actually touch the touchpad (hovering over it) and turning
on the keyboard backlight when this happens.  This currently only works
if the touchpad can sense and report individual fingers as usualy before
they even touch the pad.

Not all "hover-enabled" touchpads report this way.  Specifically, some
may simply report a global hover state, that simply indicates if
something is hovering over the pad -- as opposed to having the hover
state tied to a specific finger.

This CL expands the logic of powerd to include this situation.  It now
keeps track of this global ABS_DISTANCE value and takes it into
consideration in addition to the ABS_MT_DISTANCE values that are tied
to specific fingers.

BUG=chrome-os-partner:53064
TEST=manually built and deployed on a Cave with customized FW that
generates the events in question.  With this patch applied the keyboard
backlight behaved as expected.

Change-Id: Ia97150a85c35426cfd959b9636b9d5f01167503a
Signed-off-by: Charlie Mooney <charliemooney@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/323840
Reviewed-by: Dan Erat <derat@chromium.org>
(cherry picked from commit a3e6bf450289617f8f6ac45008f2d01e4659ac60)
Reviewed-on: https://chromium-review.googlesource.com/362741
Commit-Queue: Shasha Zhao <Sarah_Zhao@asus.com>
Tested-by: Shasha Zhao <Sarah_Zhao@asus.com>
diff --git a/power_manager/powerd/system/event_device.cc b/power_manager/powerd/system/event_device.cc
index f18d3e5..0d4b651 100644
--- a/power_manager/powerd/system/event_device.cc
+++ b/power_manager/powerd/system/event_device.cc
@@ -66,7 +66,19 @@
 }
 
 bool EventDevice::HoverSupported() {
-  return HasEventBit(0, EV_ABS) && HasEventBit(EV_ABS, ABS_MT_DISTANCE);
+  // Multitouch hover uses just the ABS_MT_DISTANCE event in addition to
+  // the normal multi-touch events.
+  if (HasEventBit(0, EV_ABS) && HasEventBit(EV_ABS, ABS_MT_DISTANCE))
+    return true;
+
+  // Simple single-touch hover presence-only detection uses 3 events:
+  // ABS_DISTANCE, BTN_TOUCH, and BTN_TOOL_FINGER.
+  if (HasEventBit(0, EV_ABS) && HasEventBit(EV_ABS, ABS_DISTANCE) &&
+      HasEventBit(0, EV_KEY) && HasEventBit(EV_KEY, BTN_TOUCH) &&
+      HasEventBit(EV_KEY, BTN_TOOL_FINGER))
+    return true;
+
+  return false;
 }
 
 bool EventDevice::HasLeftButton() {
diff --git a/power_manager/powerd/system/input_watcher.cc b/power_manager/powerd/system/input_watcher.cc
index 76adc9e..f7ee948 100644
--- a/power_manager/powerd/system/input_watcher.cc
+++ b/power_manager/powerd/system/input_watcher.cc
@@ -116,6 +116,8 @@
       hovering_(false),
       current_multitouch_slot_(0),
       multitouch_slots_hover_state_(0),
+      single_touch_hover_valid_(false),
+      single_touch_hover_distance_nonzero_(false),
       power_button_to_skip_(kPowerButtonToSkip),
       console_fd_(-1),
       udev_(nullptr),
@@ -373,12 +375,26 @@
       else
         multitouch_slots_hover_state_ &= ~slot_bit;
     }
+  } else if (event.type == EV_ABS && event.code == ABS_DISTANCE) {
+    // For single-touch presence-only hover touchpads, ABS_DISTANCE indicates
+    // the distance above the pad the single-touch finger is hovering
+    VLOG(2) << "ABS_DISTANCE " << event.value;
+    single_touch_hover_distance_nonzero_ = (event.value > 0);
+  } else if (event.type == EV_KEY && event.code == BTN_TOOL_FINGER) {
+    // For single-touch presence-only hover touchpads, BTN_TOOL_FINGER tells
+    // us if the single-touch contact is valid (if we should believe the
+    // value in ABS_DISTANCE)
+    VLOG(2) << "BTN_TOOL_FINGER " << event.value;
+    single_touch_hover_valid_ = (event.value == 1);
   } else if (event.type == EV_SYN && event.code == SYN_REPORT) {
     // SYN_REPORT events indicate the end of the current set of multitouch data.
     // Check whether the overall hovering state is different from before and
     // notify observers if so.
     VLOG(2) << "SYN_REPORT";
-    bool hovering = multitouch_slots_hover_state_ != 0;
+    bool multi_touch_hovering = multitouch_slots_hover_state_ != 0;
+    bool single_touch_hovering = (single_touch_hover_distance_nonzero_ &&
+                                  single_touch_hover_valid_);
+    bool hovering = multi_touch_hovering || single_touch_hovering;
     if (hovering != hovering_) {
       VLOG(1) << "Notifying observers about hover state change to "
               << (hovering ? "on" : "off");
diff --git a/power_manager/powerd/system/input_watcher.h b/power_manager/powerd/system/input_watcher.h
index de4d46a..b1e4177 100644
--- a/power_manager/powerd/system/input_watcher.h
+++ b/power_manager/powerd/system/input_watcher.h
@@ -173,6 +173,13 @@
   // hover event above the touchpad or a touch event on the touchpad.
   uint64_t multitouch_slots_hover_state_;
 
+  // Some touch devices only provide a binary hover value for the whole sensor
+  // instead of the signals by finger.  These variables track that hover
+  // state when it's not tied to a specific slot by using btn_tool_finger to
+  // confirm that the abs_distance value is valid.
+  bool single_touch_hover_valid_;
+  bool single_touch_hover_distance_nonzero_;
+
   // (Events, DeviceType-bitfield) tuples read from |lid_device_| by
   // QueryLidState() that haven't yet been sent to observers.
   std::vector<std::pair<input_event, uint32_t>> queued_events_;
diff --git a/power_manager/powerd/system/input_watcher_unittest.cc b/power_manager/powerd/system/input_watcher_unittest.cc
index 7d1006b..effd473 100644
--- a/power_manager/powerd/system/input_watcher_unittest.cc
+++ b/power_manager/powerd/system/input_watcher_unittest.cc
@@ -316,7 +316,7 @@
   EXPECT_EQ(TABLET_MODE_ON, input_watcher_->GetTabletMode());
 }
 
-TEST_F(InputWatcherTest, Hover) {
+TEST_F(InputWatcherTest, HoverMultitouch) {
   linked_ptr<EventDeviceStub> touchpad(new EventDeviceStub);
   touchpad->set_debug_name("touchpad");
   touchpad->set_hover_supported(true);
@@ -390,6 +390,72 @@
   EXPECT_EQ(kNoActions, observer_->GetActions());
 }
 
+TEST_F(InputWatcherTest, HoverSingletouch) {
+  linked_ptr<EventDeviceStub> touchpad(new EventDeviceStub);
+  touchpad->set_debug_name("touchpad");
+  touchpad->set_hover_supported(true);
+  touchpad->set_has_left_button(true);
+  AddDevice("event0", touchpad);
+
+  linked_ptr<EventDeviceStub> touchscreen(new EventDeviceStub);
+  touchscreen->set_debug_name("touchscreen");
+  touchscreen->set_hover_supported(true);
+  touchscreen->set_has_left_button(false);
+  AddDevice("event1", touchscreen);
+
+  detect_hover_pref_ = 1;
+  Init();
+
+  // Show a finger approaching the pad, but not touching it yet.
+  touchpad->AppendEvent(EV_ABS, ABS_DISTANCE, 1);
+  touchpad->AppendEvent(EV_KEY, BTN_TOOL_FINGER, 1);
+  touchpad->NotifyAboutEvents();
+  EXPECT_EQ(kNoActions, observer_->GetActions());
+
+  // After a SYN_REPORT indicating the end of the current report, hovering
+  // should be reported.
+  touchpad->AppendEvent(EV_SYN, SYN_REPORT, 0);
+  touchpad->NotifyAboutEvents();
+  EXPECT_EQ(kHoverOnAction, observer_->GetActions());
+
+  // Invalidate the hovering finger (leaving without touching the pad at
+  // all) which should turn hover off.
+  touchpad->AppendEvent(EV_KEY, BTN_TOOL_FINGER, 0);
+  touchpad->AppendEvent(EV_SYN, SYN_REPORT, 0);
+  touchpad->NotifyAboutEvents();
+  EXPECT_EQ(kHoverOffAction, observer_->GetActions());
+
+  // Report The finger coming back, but actually touching this time.
+  touchpad->AppendEvent(EV_ABS, ABS_MT_SLOT, 1);
+  touchpad->AppendEvent(EV_ABS, ABS_MT_TRACKING_ID, 1);
+  touchpad->AppendEvent(EV_ABS, ABS_MT_POSITION_X, 100);
+  touchpad->AppendEvent(EV_ABS, ABS_MT_POSITION_Y, 100);
+  touchpad->AppendEvent(EV_ABS, ABS_MT_PRESSURE, 50);
+  touchpad->AppendEvent(EV_KEY, BTN_TOOL_FINGER, 1);
+  touchpad->AppendEvent(EV_KEY, BTN_TOUCH, 1);
+  touchpad->AppendEvent(EV_SYN, SYN_REPORT, 0);
+  touchpad->AppendEvent(EV_ABS, ABS_DISTANCE, 0);
+  touchpad->AppendEvent(EV_SYN, SYN_REPORT, 0);
+  touchpad->NotifyAboutEvents();
+  EXPECT_EQ(kHoverOnAction, observer_->GetActions());
+
+  // Finger leaving the pad now.
+  touchpad->AppendEvent(EV_ABS, ABS_MT_TRACKING_ID, -1);
+  touchpad->AppendEvent(EV_KEY, BTN_TOOL_FINGER, 0);
+  touchpad->AppendEvent(EV_SYN, SYN_REPORT, 0);
+  touchpad->NotifyAboutEvents();
+  EXPECT_EQ(kHoverOffAction, observer_->GetActions());
+
+  // The touchpad should be ignored when the pref is set to false.
+  detect_hover_pref_ = 0;
+  Init();
+  touchpad->AppendEvent(EV_ABS, ABS_DISTANCE, 1);
+  touchpad->AppendEvent(EV_KEY, BTN_TOOL_FINGER, 1);
+  touchpad->AppendEvent(EV_SYN, SYN_REPORT, 0);
+  touchpad->NotifyAboutEvents();
+  EXPECT_EQ(kNoActions, observer_->GetActions());
+}
+
 TEST_F(InputWatcherTest, IgnoreDevices) {
   // Create a device that looks like a power button but that doesn't follow the
   // expected device naming scheme. InputWatcher shouldn't request eents from