Add synthetic back button to virtual touchpad/stylus.

Also add a bit more error checking and documentation.

Test: added to VirtualTouchpad_test.cpp
Bug: 34673438
Change-Id: I3851a2ad79c5338cdd1db0c7d460aecfff082cc3
diff --git a/services/vr/virtual_touchpad/VirtualTouchpad.cpp b/services/vr/virtual_touchpad/VirtualTouchpad.cpp
index b137dd7..f3936fc 100644
--- a/services/vr/virtual_touchpad/VirtualTouchpad.cpp
+++ b/services/vr/virtual_touchpad/VirtualTouchpad.cpp
@@ -1,20 +1,29 @@
 #include "VirtualTouchpad.h"
 
+#include <android/input.h>
 #include <cutils/log.h>
 #include <inttypes.h>
 #include <linux/input.h>
 
+// References:
+//  [0] Multi-touch (MT) Protocol,
+//      https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt
+
 namespace android {
 namespace dvr {
 
 namespace {
 
-// Virtual evdev device properties.
+// Virtual evdev device properties. The name is arbitrary, but Android can
+// use it to look up device configuration, so it must be unique. Vendor and
+// product values must be 0 to indicate an internal device and prevent a
+// similar lookup that could conflict with a physical device.
 static const char* const kDeviceName = "vr window manager virtual touchpad";
 static constexpr int16_t kDeviceBusType = BUS_VIRTUAL;
-static constexpr int16_t kDeviceVendor = 0x18D1;   // Google USB vendor ID.
-static constexpr int16_t kDeviceProduct = 0x5652;  // 'VR'
+static constexpr int16_t kDeviceVendor = 0;
+static constexpr int16_t kDeviceProduct = 0;
 static constexpr int16_t kDeviceVersion = 0x0001;
+
 static constexpr int32_t kWidth = 0x10000;
 static constexpr int32_t kHeight = 0x10000;
 static constexpr int32_t kSlots = 2;
@@ -32,18 +41,24 @@
   injector_->ConfigureMultiTouchXY(0, 0, kWidth - 1, kHeight - 1);
   injector_->ConfigureAbsSlots(kSlots);
   injector_->ConfigureKey(BTN_TOUCH);
+  injector_->ConfigureKey(BTN_BACK);
   injector_->ConfigureEnd();
   return injector_->GetError();
 }
 
 int VirtualTouchpad::Touch(float x, float y, float pressure) {
-  int error = 0;
+  if ((x < 0.0f) || (x >= 1.0f) || (y < 0.0f) || (y >= 1.0f)) {
+    return EINVAL;
+  }
   int32_t device_x = x * kWidth;
   int32_t device_y = y * kHeight;
   touches_ = ((touches_ & 1) << 1) | (pressure > 0);
   ALOGV("(%f,%f) %f -> (%" PRId32 ",%" PRId32 ") %d",
         x, y, pressure, device_x, device_y, touches_);
 
+  if (!injector_) {
+    return EvdevInjector::ERROR_SEQUENCING;
+  }
   injector_->ResetError();
   switch (touches_) {
     case 0b00:  // Hover continues.
@@ -76,5 +91,30 @@
   return injector_->GetError();
 }
 
+int VirtualTouchpad::ButtonState(int buttons) {
+  const int changes = last_motion_event_buttons_ ^ buttons;
+  if (!changes) {
+    return 0;
+  }
+  if (buttons & ~AMOTION_EVENT_BUTTON_BACK) {
+    return ENOTSUP;
+  }
+  ALOGV("change %X from %X to %X", changes, last_motion_event_buttons_,
+        buttons);
+
+  if (!injector_) {
+    return EvdevInjector::ERROR_SEQUENCING;
+  }
+  injector_->ResetError();
+  if (changes & AMOTION_EVENT_BUTTON_BACK) {
+    injector_->SendKey(BTN_BACK,
+                       (buttons & AMOTION_EVENT_BUTTON_BACK)
+                           ? EvdevInjector::KEY_PRESS
+                           : EvdevInjector::KEY_RELEASE);
+  }
+  last_motion_event_buttons_ = buttons;
+  return injector_->GetError();
+}
+
 }  // namespace dvr
 }  // namespace android
diff --git a/services/vr/virtual_touchpad/VirtualTouchpad.h b/services/vr/virtual_touchpad/VirtualTouchpad.h
index 7e7801e..17aeb35 100644
--- a/services/vr/virtual_touchpad/VirtualTouchpad.h
+++ b/services/vr/virtual_touchpad/VirtualTouchpad.h
@@ -10,12 +10,39 @@
 
 class EvdevInjector;
 
+// Provides a virtual touchpad for injecting events into the input system.
+//
 class VirtualTouchpad {
  public:
   VirtualTouchpad() {}
+  ~VirtualTouchpad() {}
+
+  // |Intialize()| must be called once on a VirtualTouchpad before
+  // and other public method. Returns zero on success.
   int Initialize();
+
+  // Generate a simulated touch event.
+  //
+  // @param x Horizontal touch position.
+  // @param y Vertical touch position.
+  //            Values must be in the range [0.0, 1.0).
+  // @param pressure Touch pressure.
+  //            Positive values represent contact; use 1.0f if contact
+  //            is binary. Use 0.0f for no contact.
+  // @returns Zero on success.
+  //
   int Touch(float x, float y, float pressure);
 
+  // Generate a simulated touchpad button state.
+  //
+  // @param buttons A union of MotionEvent BUTTON_* values.
+  // @returns Zero on success.
+  //
+  // Currently only BUTTON_BACK is supported, as the implementation
+  // restricts itself to operations actually required by VrWindowManager.
+  //
+  int ButtonState(int buttons);
+
  protected:
   // Must be called only between construction and Initialize().
   inline void SetEvdevInjectorForTesting(EvdevInjector* injector) {
@@ -23,17 +50,23 @@
   }
 
  private:
-  // Active pointer to |owned_injector_| or to a testing injector.
-  EvdevInjector* injector_ = nullptr;
+  // Except for testing, the |EvdevInjector| used to inject evdev events.
   std::unique_ptr<EvdevInjector> owned_injector_;
 
-  // Previous (x,y) position to suppress redundant events.
+  // Active pointer to |owned_injector_| or to a testing injector.
+  EvdevInjector* injector_ = nullptr;
+
+  // Previous (x, y) position in device space, to suppress redundant events.
   int32_t last_device_x_ = INT32_MIN;
   int32_t last_device_y_ = INT32_MIN;
 
-  // Records current touch state in bit 0 and previous state in bit 1.
+  // Records current touch state (0=up 1=down) in bit 0, and previous state
+  // in bit 1, to track transitions.
   int touches_ = 0;
 
+  // Previous injected button state, to detect changes.
+  int32_t last_motion_event_buttons_ = 0;
+
   VirtualTouchpad(const VirtualTouchpad&) = delete;
   void operator=(const VirtualTouchpad&) = delete;
 };
diff --git a/services/vr/virtual_touchpad/VirtualTouchpadService.cpp b/services/vr/virtual_touchpad/VirtualTouchpadService.cpp
index e5ead0e..5e3321f 100644
--- a/services/vr/virtual_touchpad/VirtualTouchpadService.cpp
+++ b/services/vr/virtual_touchpad/VirtualTouchpadService.cpp
@@ -13,11 +13,16 @@
 }
 
 binder::Status VirtualTouchpadService::touch(float x, float y, float pressure) {
-  // Permissions check added and removed here :^)
   const int error = touchpad_.Touch(x, y, pressure);
   return error ? binder::Status::fromServiceSpecificError(error)
                : binder::Status::ok();
 }
 
+binder::Status VirtualTouchpadService::buttonState(int buttons) {
+  const int error = touchpad_.ButtonState(buttons);
+  return error ? binder::Status::fromServiceSpecificError(error)
+               : binder::Status::ok();
+}
+
 }  // namespace dvr
 }  // namespace android
diff --git a/services/vr/virtual_touchpad/VirtualTouchpadService.h b/services/vr/virtual_touchpad/VirtualTouchpadService.h
index 05a2a50..e2426e3 100644
--- a/services/vr/virtual_touchpad/VirtualTouchpadService.h
+++ b/services/vr/virtual_touchpad/VirtualTouchpadService.h
@@ -8,6 +8,9 @@
 namespace android {
 namespace dvr {
 
+// VirtualTouchpadService implements the service side of
+// the Binder interface defined in VirtualTouchpadService.aidl.
+//
 class VirtualTouchpadService : public BnVirtualTouchpadService {
  public:
   VirtualTouchpadService(VirtualTouchpad& touchpad)
@@ -22,6 +25,7 @@
  protected:
   // Implements IVirtualTouchpadService.
   ::android::binder::Status touch(float x, float y, float pressure) override;
+  ::android::binder::Status buttonState(int buttons) override;
 
  private:
   VirtualTouchpad& touchpad_;
diff --git a/services/vr/virtual_touchpad/aidl/android/dvr/VirtualTouchpadService.aidl b/services/vr/virtual_touchpad/aidl/android/dvr/VirtualTouchpadService.aidl
index da4de94..e048837 100644
--- a/services/vr/virtual_touchpad/aidl/android/dvr/VirtualTouchpadService.aidl
+++ b/services/vr/virtual_touchpad/aidl/android/dvr/VirtualTouchpadService.aidl
@@ -13,4 +13,11 @@
    * Position values in the range [0.0, 1.0) map to the screen.
    */
   void touch(float x, float y, float pressure);
+
+  /**
+   * Generate a simulated touchpad button state event.
+   *
+   * @param buttons A union of MotionEvent BUTTON_* values.
+   */
+  void buttonState(int buttons);
 }
diff --git a/services/vr/virtual_touchpad/tests/VirtualTouchpad_test.cpp b/services/vr/virtual_touchpad/tests/VirtualTouchpad_test.cpp
index 874ef80..256c6bc 100644
--- a/services/vr/virtual_touchpad/tests/VirtualTouchpad_test.cpp
+++ b/services/vr/virtual_touchpad/tests/VirtualTouchpad_test.cpp
@@ -1,3 +1,4 @@
+#include <android/input.h>
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
@@ -200,6 +201,28 @@
 
   expect.Reset();
   record.Reset();
+  touch_status = touchpad.ButtonState(AMOTION_EVENT_BUTTON_BACK);
+  EXPECT_EQ(0, touch_status);
+  expect.WriteInputEvent(EV_KEY, BTN_BACK, EvdevInjector::KEY_PRESS);
+  expect.WriteInputEvent(EV_SYN, SYN_REPORT, 0);
+  EXPECT_EQ(expect.GetString(), record.GetString());
+
+  expect.Reset();
+  record.Reset();
+  touch_status = touchpad.ButtonState(AMOTION_EVENT_BUTTON_BACK);
+  EXPECT_EQ(0, touch_status);
+  EXPECT_EQ(expect.GetString(), record.GetString());
+
+  expect.Reset();
+  record.Reset();
+  touch_status = touchpad.ButtonState(0);
+  EXPECT_EQ(0, touch_status);
+  expect.WriteInputEvent(EV_KEY, BTN_BACK, EvdevInjector::KEY_RELEASE);
+  expect.WriteInputEvent(EV_SYN, SYN_REPORT, 0);
+  EXPECT_EQ(expect.GetString(), record.GetString());
+
+  expect.Reset();
+  record.Reset();
 }
 
 TEST_F(VirtualTouchpadTest, Badness) {
@@ -216,6 +239,14 @@
   EXPECT_NE(0, touch_status);
   EXPECT_EQ(expect.GetString(), record.GetString());
 
+  // Button change before initialization should return an error,
+  // and should not result in any system calls.
+  expect.Reset();
+  record.Reset();
+  touch_status = touchpad.ButtonState(AMOTION_EVENT_BUTTON_BACK);
+  EXPECT_NE(0, touch_status);
+  EXPECT_EQ(expect.GetString(), record.GetString());
+
   expect.Reset();
   record.Reset();
   touchpad.Initialize();
@@ -227,6 +258,28 @@
   const int initialization_status = touchpad.Initialize();
   EXPECT_NE(0, initialization_status);
   EXPECT_EQ(expect.GetString(), record.GetString());
+
+  // Touch off-screen should return an error,
+  // and should not result in any system calls.
+  expect.Reset();
+  record.Reset();
+  touch_status = touchpad.Touch(-0.25f, 0.75f, 1.0f);
+  EXPECT_NE(0, touch_status);
+  touch_status = touchpad.Touch(0.25f, -0.75f, 1.0f);
+  EXPECT_NE(0, touch_status);
+  touch_status = touchpad.Touch(1.25f, 0.75f, 1.0f);
+  EXPECT_NE(0, touch_status);
+  touch_status = touchpad.Touch(0.25f, 1.75f, 1.0f);
+  EXPECT_NE(0, touch_status);
+  EXPECT_EQ(expect.GetString(), record.GetString());
+
+  // Unsupported button should return an error,
+  // and should not result in any system calls.
+  expect.Reset();
+  record.Reset();
+  touch_status = touchpad.ButtonState(AMOTION_EVENT_BUTTON_FORWARD);
+  EXPECT_NE(0, touch_status);
+  EXPECT_EQ(expect.GetString(), record.GetString());
 }
 
 }  // namespace dvr
diff --git a/services/vr/vr_window_manager/shell_view.cpp b/services/vr/vr_window_manager/shell_view.cpp
index cef111c..b9a4f86 100644
--- a/services/vr/vr_window_manager/shell_view.cpp
+++ b/services/vr/vr_window_manager/shell_view.cpp
@@ -1,5 +1,6 @@
 #include "shell_view.h"
 
+#include <android/input.h>
 #include <binder/IServiceManager.h>
 #include <cutils/log.h>
 #include <EGL/eglext.h>
@@ -583,6 +584,12 @@
 
     if (controller_state_->GetButtonUp(gvr::kControllerButtonClick))
       OnClick(false);
+
+    if (controller_state_->GetButtonDown(gvr::kControllerButtonApp))
+      OnTouchpadButton(true, AMOTION_EVENT_BUTTON_BACK);
+
+    if (controller_state_->GetButtonUp(gvr::kControllerButtonApp))
+      OnTouchpadButton(false, AMOTION_EVENT_BUTTON_BACK);
   }
 
   vec3 view_direction = vec3(view_quaternion * vec3(0, 0, -1));
@@ -665,5 +672,32 @@
   }
 }
 
+bool ShellView::OnTouchpadButton(bool down, int button) {
+  int buttons = touchpad_buttons_;
+  if (down) {
+    if (allow_input_) {
+      buttons |= button;
+    }
+  } else {
+    buttons &= ~button;
+  }
+  if (buttons == touchpad_buttons_) {
+    return true;
+  }
+  touchpad_buttons_ = buttons;
+  if (!virtual_touchpad_.get()) {
+    ALOGE("missing virtual touchpad");
+    return false;
+  }
+
+  const android::binder::Status status =
+      virtual_touchpad_->buttonState(touchpad_buttons_);
+  if (!status.isOk()) {
+    ALOGE("touchpad button failed: %d %s", touchpad_buttons_,
+          status.toString8().string());
+  }
+  return true;
+}
+
 }  // namespace dvr
 }  // namespace android
diff --git a/services/vr/vr_window_manager/shell_view.h b/services/vr/vr_window_manager/shell_view.h
index 0688c94..589902e 100644
--- a/services/vr/vr_window_manager/shell_view.h
+++ b/services/vr/vr_window_manager/shell_view.h
@@ -50,6 +50,7 @@
                 vec3 *hit_location);
   bool InitializeTouch();
   void Touch();
+  bool OnTouchpadButton(bool down, int button);
 
   void OnDrawFrame() override;
   void DrawWithTransform(const mat4& transform, const ShaderProgram& program);
@@ -81,6 +82,7 @@
 
   bool is_touching_ = false;
   bool allow_input_ = false;
+  int touchpad_buttons_ = 0;
   vec2 hit_location_in_window_coord_;
   vec2 ime_top_left_;
   vec2 ime_size_;