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_;