Add DaydreamVR native libraries and services
Upstreaming the main VR system components from master-dreamos-dev
into goog/master.
Bug: None
Test: `m -j32` succeeds. Sailfish boots and basic_vr sample app works
Change-Id: I853015872afc443aecee10411ef2d6b79184d051
diff --git a/services/vr/virtual_touchpad/Android.mk b/services/vr/virtual_touchpad/Android.mk
new file mode 100644
index 0000000..4224aaa
--- /dev/null
+++ b/services/vr/virtual_touchpad/Android.mk
@@ -0,0 +1,76 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+
+# Touchpad implementation.
+
+src := \
+ EvdevInjector.cpp \
+ VirtualTouchpad.cpp
+
+shared_libs := \
+ libbase
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(src)
+LOCAL_SHARED_LIBRARIES := $(shared_libs)
+LOCAL_CPPFLAGS += -std=c++11
+LOCAL_CFLAGS += -DLOG_TAG=\"VrVirtualTouchpad\"
+LOCAL_MODULE := libvirtualtouchpad
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_STATIC_LIBRARY)
+
+
+# Touchpad unit tests.
+
+test_src_files := \
+ tests/VirtualTouchpad_test.cpp
+
+static_libs := \
+ libbase \
+ libcutils \
+ libvirtualtouchpad
+
+$(foreach file,$(test_src_files), \
+ $(eval include $(CLEAR_VARS)) \
+ $(eval LOCAL_SRC_FILES := $(file)) \
+ $(eval LOCAL_STATIC_LIBRARIES := $(static_libs)) \
+ $(eval LOCAL_SHARED_LIBRARIES := $(shared_libs)) \
+ $(eval LOCAL_CPPFLAGS += -std=c++11) \
+ $(eval LOCAL_LDLIBS := -llog) \
+ $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
+ $(eval LOCAL_MODULE_TAGS := optional) \
+ $(eval LOCAL_CXX_STL := libc++_static) \
+ $(eval include $(BUILD_NATIVE_TEST)) \
+)
+
+
+# Service.
+
+src := \
+ main.cpp \
+ VirtualTouchpadService.cpp \
+ aidl/android/dvr/VirtualTouchpadService.aidl
+
+static_libs := \
+ libcutils \
+ libvirtualtouchpad
+
+shared_libs := \
+ libbase \
+ libbinder \
+ libutils
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(src)
+LOCAL_STATIC_LIBRARIES := $(static_libs)
+LOCAL_SHARED_LIBRARIES := $(shared_libs)
+LOCAL_CPPFLAGS += -std=c++11
+LOCAL_CFLAGS += -DLOG_TAG=\"VrVirtualTouchpad\"
+LOCAL_LDLIBS := -llog
+LOCAL_MODULE := virtual_touchpad
+LOCAL_MODULE_TAGS := optional
+LOCAL_INIT_RC := virtual_touchpad.rc
+LOCAL_MULTILIB := 64
+LOCAL_CXX_STL := libc++_static
+include $(BUILD_EXECUTABLE)
diff --git a/services/vr/virtual_touchpad/EvdevInjector.cpp b/services/vr/virtual_touchpad/EvdevInjector.cpp
new file mode 100644
index 0000000..be20c6c
--- /dev/null
+++ b/services/vr/virtual_touchpad/EvdevInjector.cpp
@@ -0,0 +1,311 @@
+#include "EvdevInjector.h"
+
+#include <cutils/log.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <linux/input.h>
+#include <string.h>
+#include <sys/fcntl.h>
+#include <unistd.h>
+
+namespace android {
+namespace dvr {
+
+int EvdevInjector::UInput::Open() {
+ errno = 0;
+ fd_.reset(open("/dev/uinput", O_WRONLY | O_NONBLOCK));
+ if (fd_.get() < 0) {
+ ALOGE("couldn't open uinput (r=%d errno=%d)", fd_.get(), errno);
+ }
+ return errno;
+}
+
+int EvdevInjector::UInput::Close() {
+ errno = 0;
+ fd_.reset();
+ return errno;
+}
+
+int EvdevInjector::UInput::Write(const void* buf, size_t count) {
+ ALOGV("UInput::Write(%zu, %02X...)", count, *static_cast<const char*>(buf));
+ errno = 0;
+ ssize_t r = write(fd_.get(), buf, count);
+ if (r != static_cast<ssize_t>(count)) {
+ ALOGE("write(%zu) failed (r=%zd errno=%d)", count, r, errno);
+ }
+ return errno;
+}
+
+int EvdevInjector::UInput::IoctlSetInt(int request, int value) {
+ ALOGV("UInput::IoctlSetInt(0x%X, 0x%X)", request, value);
+ errno = 0;
+ if (const int status = ioctl(fd_.get(), request, value)) {
+ ALOGE("ioctl(%d, 0x%X, 0x%X) failed (r=%d errno=%d)", fd_.get(), request,
+ value, status, errno);
+ }
+ return errno;
+}
+
+int EvdevInjector::UInput::IoctlVoid(int request) {
+ ALOGV("UInput::IoctlVoid(0x%X)", request);
+ errno = 0;
+ if (const int status = ioctl(fd_.get(), request)) {
+ ALOGE("ioctl(%d, 0x%X) failed (r=%d errno=%d)", fd_.get(), request, status,
+ errno);
+ }
+ return errno;
+}
+
+void EvdevInjector::Close() {
+ uinput_->Close();
+ state_ = State::CLOSED;
+}
+
+int EvdevInjector::ConfigureBegin(const char* device_name, int16_t bustype,
+ int16_t vendor, int16_t product,
+ int16_t version) {
+ ALOGV("ConfigureBegin %s 0x%04" PRIX16 " 0x%04" PRIX16 " 0x%04" PRIX16
+ " 0x%04" PRIX16 "",
+ device_name, bustype, vendor, product, version);
+ if (!device_name || strlen(device_name) >= UINPUT_MAX_NAME_SIZE) {
+ return Error(ERROR_DEVICE_NAME);
+ }
+ if (const int status = RequireState(State::NEW)) {
+ return status;
+ }
+ if (!uinput_) {
+ owned_uinput_.reset(new EvdevInjector::UInput());
+ uinput_ = owned_uinput_.get();
+ }
+ if (const int status = uinput_->Open()) {
+ // Without uinput we're dead in the water.
+ state_ = State::CLOSED;
+ return Error(status);
+ }
+ state_ = State::CONFIGURING;
+ // Initialize device setting structure.
+ memset(&uidev_, 0, sizeof(uidev_));
+ strncpy(uidev_.name, device_name, UINPUT_MAX_NAME_SIZE);
+ uidev_.id.bustype = bustype;
+ uidev_.id.vendor = vendor;
+ uidev_.id.product = product;
+ uidev_.id.version = version;
+ return 0;
+}
+
+int EvdevInjector::ConfigureInputProperty(int property) {
+ ALOGV("ConfigureInputProperty %d", property);
+ if (property < 0 || property >= INPUT_PROP_CNT) {
+ ALOGE("property 0x%X out of range [0,0x%X)", property, INPUT_PROP_CNT);
+ return Error(ERROR_PROPERTY_RANGE);
+ }
+ if (const int status = RequireState(State::CONFIGURING)) {
+ return status;
+ }
+ if (const int status = uinput_->IoctlSetInt(UI_SET_PROPBIT, property)) {
+ ALOGE("failed to set property %d", property);
+ return Error(status);
+ }
+ return 0;
+}
+
+int EvdevInjector::ConfigureKey(uint16_t key) {
+ ALOGV("ConfigureKey 0x%02" PRIX16 "", key);
+ if (key < 0 || key >= KEY_CNT) {
+ ALOGE("key 0x%X out of range [0,0x%X)", key, KEY_CNT);
+ return Error(ERROR_KEY_RANGE);
+ }
+ if (const int status = RequireState(State::CONFIGURING)) {
+ return status;
+ }
+ if (const int status = EnableEventType(EV_KEY)) {
+ return status;
+ }
+ if (const int status = uinput_->IoctlSetInt(UI_SET_KEYBIT, key)) {
+ ALOGE("failed to enable EV_KEY 0x%02" PRIX16 "", key);
+ return Error(status);
+ }
+ return 0;
+}
+
+int EvdevInjector::ConfigureAbs(uint16_t abs_type, int32_t min, int32_t max,
+ int32_t fuzz, int32_t flat) {
+ ALOGV("ConfigureAbs 0x%" PRIX16 " %" PRId32 " %" PRId32 " %" PRId32
+ " %" PRId32 "",
+ abs_type, min, max, fuzz, flat);
+ if (abs_type < 0 || abs_type >= ABS_CNT) {
+ ALOGE("EV_ABS type 0x%" PRIX16 " out of range [0,0x%X)", abs_type, ABS_CNT);
+ return Error(ERROR_ABS_RANGE);
+ }
+ if (const int status = RequireState(State::CONFIGURING)) {
+ return status;
+ }
+ if (const int status = EnableEventType(EV_ABS)) {
+ return status;
+ }
+ if (const int status = uinput_->IoctlSetInt(UI_SET_ABSBIT, abs_type)) {
+ ALOGE("failed to enable EV_ABS 0x%" PRIX16 "", abs_type);
+ return Error(status);
+ }
+ uidev_.absmin[abs_type] = min;
+ uidev_.absmax[abs_type] = max;
+ uidev_.absfuzz[abs_type] = fuzz;
+ uidev_.absflat[abs_type] = flat;
+ return 0;
+}
+
+int EvdevInjector::ConfigureMultiTouchXY(int x0, int y0, int x1, int y1) {
+ if (const int status = ConfigureAbs(ABS_MT_POSITION_X, x0, x1, 0, 0)) {
+ return status;
+ }
+ if (const int status = ConfigureAbs(ABS_MT_POSITION_Y, y0, y1, 0, 0)) {
+ return status;
+ }
+ return 0;
+}
+
+int EvdevInjector::ConfigureAbsSlots(int slots) {
+ return ConfigureAbs(ABS_MT_SLOT, 0, slots, 0, 0);
+}
+
+int EvdevInjector::ConfigureEnd() {
+ ALOGV("ConfigureEnd:");
+ ALOGV(" name=\"%s\"", uidev_.name);
+ ALOGV(" id.bustype=0x%04" PRIX16, uidev_.id.bustype);
+ ALOGV(" id.vendor=0x%04" PRIX16, uidev_.id.vendor);
+ ALOGV(" id.product=0x%04" PRIX16, uidev_.id.product);
+ ALOGV(" id.version=0x%04" PRIX16, uidev_.id.version);
+ ALOGV(" ff_effects_max=%" PRIu32, uidev_.ff_effects_max);
+ for (int i = 0; i < ABS_CNT; ++i) {
+ if (uidev_.absmin[i]) {
+ ALOGV(" absmin[%d]=%" PRId32, i, uidev_.absmin[i]);
+ }
+ if (uidev_.absmax[i]) {
+ ALOGV(" absmax[%d]=%" PRId32, i, uidev_.absmax[i]);
+ }
+ if (uidev_.absfuzz[i]) {
+ ALOGV(" absfuzz[%d]=%" PRId32, i, uidev_.absfuzz[i]);
+ }
+ if (uidev_.absflat[i]) {
+ ALOGV(" absflat[%d]=%" PRId32, i, uidev_.absflat[i]);
+ }
+ }
+
+ if (const int status = RequireState(State::CONFIGURING)) {
+ return status;
+ }
+ // Write out device settings.
+ if (const int status = uinput_->Write(&uidev_, sizeof uidev_)) {
+ ALOGE("failed to write device settings");
+ return Error(status);
+ }
+ // Create device node.
+ if (const int status = uinput_->IoctlVoid(UI_DEV_CREATE)) {
+ ALOGE("failed to create device node");
+ return Error(status);
+ }
+ state_ = State::READY;
+ return 0;
+}
+
+int EvdevInjector::Send(uint16_t type, uint16_t code, int32_t value) {
+ ALOGV("Send(0x%" PRIX16 ", 0x%" PRIX16 ", 0x%" PRIX32 ")", type, code, value);
+ if (const int status = RequireState(State::READY)) {
+ return status;
+ }
+ struct input_event event;
+ memset(&event, 0, sizeof(event));
+ event.type = type;
+ event.code = code;
+ event.value = value;
+ if (const int status = uinput_->Write(&event, sizeof(event))) {
+ ALOGE("failed to write event 0x%" PRIX16 ", 0x%" PRIX16 ", 0x%" PRIX32,
+ type, code, value);
+ return Error(status);
+ }
+ return 0;
+}
+
+int EvdevInjector::SendSynReport() { return Send(EV_SYN, SYN_REPORT, 0); }
+
+int EvdevInjector::SendKey(uint16_t code, int32_t value) {
+ return Send(EV_KEY, code, value);
+}
+
+int EvdevInjector::SendAbs(uint16_t code, int32_t value) {
+ return Send(EV_ABS, code, value);
+}
+
+int EvdevInjector::SendMultiTouchSlot(int32_t slot) {
+ if (latest_slot_ != slot) {
+ if (const int status = SendAbs(ABS_MT_SLOT, slot)) {
+ return status;
+ }
+ latest_slot_ = slot;
+ }
+ return 0;
+}
+
+int EvdevInjector::SendMultiTouchXY(int32_t slot, int32_t id, int32_t x,
+ int32_t y) {
+ if (const int status = SendMultiTouchSlot(slot)) {
+ return status;
+ }
+ if (const int status = SendAbs(ABS_MT_TRACKING_ID, id)) {
+ return status;
+ }
+ if (const int status = SendAbs(ABS_MT_POSITION_X, x)) {
+ return status;
+ }
+ if (const int status = SendAbs(ABS_MT_POSITION_Y, y)) {
+ return status;
+ }
+ return 0;
+}
+
+int EvdevInjector::SendMultiTouchLift(int32_t slot) {
+ if (const int status = SendMultiTouchSlot(slot)) {
+ return status;
+ }
+ if (const int status = SendAbs(ABS_MT_TRACKING_ID, -1)) {
+ return status;
+ }
+ return 0;
+}
+
+int EvdevInjector::Error(int code) {
+ if (!error_) {
+ error_ = code;
+ }
+ return code;
+}
+
+int EvdevInjector::RequireState(State required_state) {
+ if (error_) {
+ return error_;
+ }
+ if (state_ != required_state) {
+ ALOGE("in state %d but require state %d", static_cast<int>(state_),
+ static_cast<int>(required_state));
+ return Error(ERROR_SEQUENCING);
+ }
+ return 0;
+}
+
+int EvdevInjector::EnableEventType(uint16_t type) {
+ if (const int status = RequireState(State::CONFIGURING)) {
+ return status;
+ }
+ if (enabled_event_types_.count(type) > 0) {
+ return 0;
+ }
+ if (const int status = uinput_->IoctlSetInt(UI_SET_EVBIT, type)) {
+ ALOGE("failed to enable event type 0x%X", type);
+ return Error(status);
+ }
+ enabled_event_types_.insert(type);
+ return 0;
+}
+
+} // namespace dvr
+} // namespace android
diff --git a/services/vr/virtual_touchpad/EvdevInjector.h b/services/vr/virtual_touchpad/EvdevInjector.h
new file mode 100644
index 0000000..1b1c4da
--- /dev/null
+++ b/services/vr/virtual_touchpad/EvdevInjector.h
@@ -0,0 +1,139 @@
+#ifndef ANDROID_DVR_EVDEV_INJECTOR_H
+#define ANDROID_DVR_EVDEV_INJECTOR_H
+
+#include <android-base/unique_fd.h>
+#include <linux/uinput.h>
+
+#include <cstdint>
+#include <memory>
+#include <unordered_set>
+
+namespace android {
+namespace dvr {
+
+// Simulated evdev input device.
+//
+class EvdevInjector {
+ public:
+ // EvdevInjector-specific error codes are negative integers; other non-zero
+ // values returned from public routines are |errno| codes from underlying I/O.
+ // EvdevInjector maintains a 'sticky' error state, similar to |errno|, so that
+ // a caller can perform a sequence of operations and check for errors at the
+ // end using |GetError()|. In general, the first such error will be recorded
+ // and will suppress effects of further device operations until |ResetError()|
+ // is called.
+ //
+ enum : int {
+ ERROR_DEVICE_NAME = -1, // Invalid device name.
+ ERROR_PROPERTY_RANGE = -2, // |INPUT_PROP_*| code out of range.
+ ERROR_KEY_RANGE = -3, // |KEY_*|/|BTN_*| code out of range.
+ ERROR_ABS_RANGE = -4, // |ABS_*| code out of range.
+ ERROR_SEQUENCING = -5, // Configure/Send out of order.
+ };
+
+ // Key event |value| is not defined in <linux/input.h>.
+ enum : int32_t { KEY_RELEASE = 0, KEY_PRESS = 1, KEY_REPEAT = 2 };
+
+ // UInput provides a shim to intercept /dev/uinput operations
+ // just above the system call level, for testing.
+ //
+ class UInput {
+ public:
+ UInput() {}
+ virtual ~UInput() {}
+ virtual int Open();
+ virtual int Close();
+ virtual int Write(const void* buf, size_t count);
+ virtual int IoctlVoid(int request);
+ virtual int IoctlSetInt(int request, int value);
+
+ private:
+ base::unique_fd fd_;
+ };
+
+ EvdevInjector() {}
+ ~EvdevInjector() { Close(); }
+ void Close();
+
+ int GetError() const { return error_; }
+ void ResetError() { error_ = 0; }
+
+ // Configuration must be performed before sending any events.
+ // |ConfigureBegin()| must be called first, and |ConfigureEnd()| last,
+ // with zero or more other |Configure...()| calls in between in any order.
+
+ // Configure the basic evdev device properties; must be called first.
+ int ConfigureBegin(const char* device_name, int16_t bustype, int16_t vendor,
+ int16_t product, int16_t version);
+
+ // Configure an optional input device property.
+ // @param property One of the |INPUT_PROP_*| constants from <linux/input.h>.
+ int ConfigureInputProperty(int property);
+
+ // Configure an input key.
+ // @param key One of the |KEY_*| or |BTN_*| constants from <linux/input.h>.
+ int ConfigureKey(uint16_t key);
+
+ // Configure an absolute axis.
+ // @param abs_type One of the |KEY_*| or |BTN_*| constants from
+ // <linux/input.h>.
+ int ConfigureAbs(uint16_t abs_type, int32_t min, int32_t max, int32_t fuzz,
+ int32_t flat);
+
+ // Configure the number of multitouch slots.
+ int ConfigureAbsSlots(int slots);
+
+ // Configure multitouch coordinate range.
+ int ConfigureMultiTouchXY(int32_t x0, int32_t y0, int32_t x1, int32_t y1);
+
+ // Complete configuration and create the input device.
+ int ConfigureEnd();
+
+ // Send various events.
+ //
+ int Send(uint16_t type, uint16_t code, int32_t value);
+ int SendSynReport();
+ int SendKey(uint16_t code, int32_t value);
+ int SendAbs(uint16_t code, int32_t value);
+ int SendMultiTouchSlot(int32_t slot);
+ int SendMultiTouchXY(int32_t slot, int32_t id, int32_t x, int32_t y);
+ int SendMultiTouchLift(int32_t slot);
+
+ protected:
+ // Must be called only between construction and ConfigureBegin().
+ inline void SetUInputForTesting(UInput* uinput) { uinput_ = uinput; }
+ // Caller must not retain pointer longer than EvdevInjector.
+ inline const uinput_user_dev* GetUiDevForTesting() const { return &uidev_; }
+
+ private:
+ // Phase to enforce that configuration is complete before events are sent.
+ enum class State { NEW, CONFIGURING, READY, CLOSED };
+
+ // Sets |error_| if it is not already set; returns |code|.
+ int Error(int code);
+
+ // Returns a nonzero error if the injector is not in the required |state|.
+ int RequireState(State state);
+
+ // Configures an event type if necessary.
+ // @param type One of the |EV_*| constants from <linux/input.h>.
+ int EnableEventType(uint16_t type);
+
+ // Active pointer to owned or testing UInput.
+ UInput* uinput_ = nullptr;
+ std::unique_ptr<UInput> owned_uinput_;
+
+ State state_ = State::NEW;
+ int error_ = 0;
+ uinput_user_dev uidev_;
+ std::unordered_set<uint16_t> enabled_event_types_;
+ int32_t latest_slot_ = -1;
+
+ EvdevInjector(const EvdevInjector&) = delete;
+ void operator=(const EvdevInjector&) = delete;
+};
+
+} // namespace dvr
+} // namespace android
+
+#endif // ANDROID_DVR_EVDEV_INJECTOR_H
diff --git a/services/vr/virtual_touchpad/VirtualTouchpad.cpp b/services/vr/virtual_touchpad/VirtualTouchpad.cpp
new file mode 100644
index 0000000..b137dd7
--- /dev/null
+++ b/services/vr/virtual_touchpad/VirtualTouchpad.cpp
@@ -0,0 +1,80 @@
+#include "VirtualTouchpad.h"
+
+#include <cutils/log.h>
+#include <inttypes.h>
+#include <linux/input.h>
+
+namespace android {
+namespace dvr {
+
+namespace {
+
+// Virtual evdev device properties.
+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 kDeviceVersion = 0x0001;
+static constexpr int32_t kWidth = 0x10000;
+static constexpr int32_t kHeight = 0x10000;
+static constexpr int32_t kSlots = 2;
+
+} // anonymous namespace
+
+int VirtualTouchpad::Initialize() {
+ if (!injector_) {
+ owned_injector_.reset(new EvdevInjector());
+ injector_ = owned_injector_.get();
+ }
+ injector_->ConfigureBegin(kDeviceName, kDeviceBusType, kDeviceVendor,
+ kDeviceProduct, kDeviceVersion);
+ injector_->ConfigureInputProperty(INPUT_PROP_DIRECT);
+ injector_->ConfigureMultiTouchXY(0, 0, kWidth - 1, kHeight - 1);
+ injector_->ConfigureAbsSlots(kSlots);
+ injector_->ConfigureKey(BTN_TOUCH);
+ injector_->ConfigureEnd();
+ return injector_->GetError();
+}
+
+int VirtualTouchpad::Touch(float x, float y, float pressure) {
+ int error = 0;
+ 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_);
+
+ injector_->ResetError();
+ switch (touches_) {
+ case 0b00: // Hover continues.
+ if (device_x != last_device_x_ || device_y != last_device_y_) {
+ injector_->SendMultiTouchXY(0, 0, device_x, device_y);
+ injector_->SendSynReport();
+ }
+ break;
+ case 0b01: // Touch begins.
+ // Press.
+ injector_->SendMultiTouchXY(0, 0, device_x, device_y);
+ injector_->SendKey(BTN_TOUCH, EvdevInjector::KEY_PRESS);
+ injector_->SendSynReport();
+ break;
+ case 0b10: // Touch ends.
+ injector_->SendKey(BTN_TOUCH, EvdevInjector::KEY_RELEASE);
+ injector_->SendMultiTouchLift(0);
+ injector_->SendSynReport();
+ break;
+ case 0b11: // Touch continues.
+ if (device_x != last_device_x_ || device_y != last_device_y_) {
+ injector_->SendMultiTouchXY(0, 0, device_x, device_y);
+ injector_->SendSynReport();
+ }
+ break;
+ }
+ last_device_x_ = device_x;
+ last_device_y_ = device_y;
+
+ return injector_->GetError();
+}
+
+} // namespace dvr
+} // namespace android
diff --git a/services/vr/virtual_touchpad/VirtualTouchpad.h b/services/vr/virtual_touchpad/VirtualTouchpad.h
new file mode 100644
index 0000000..7e7801e
--- /dev/null
+++ b/services/vr/virtual_touchpad/VirtualTouchpad.h
@@ -0,0 +1,44 @@
+#ifndef ANDROID_DVR_VIRTUAL_TOUCHPAD_H
+#define ANDROID_DVR_VIRTUAL_TOUCHPAD_H
+
+#include <memory>
+
+#include "EvdevInjector.h"
+
+namespace android {
+namespace dvr {
+
+class EvdevInjector;
+
+class VirtualTouchpad {
+ public:
+ VirtualTouchpad() {}
+ int Initialize();
+ int Touch(float x, float y, float pressure);
+
+ protected:
+ // Must be called only between construction and Initialize().
+ inline void SetEvdevInjectorForTesting(EvdevInjector* injector) {
+ injector_ = injector;
+ }
+
+ private:
+ // Active pointer to |owned_injector_| or to a testing injector.
+ EvdevInjector* injector_ = nullptr;
+ std::unique_ptr<EvdevInjector> owned_injector_;
+
+ // Previous (x,y) position 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.
+ int touches_ = 0;
+
+ VirtualTouchpad(const VirtualTouchpad&) = delete;
+ void operator=(const VirtualTouchpad&) = delete;
+};
+
+} // namespace dvr
+} // namespace android
+
+#endif // ANDROID_DVR_VIRTUAL_TOUCHPAD_H
diff --git a/services/vr/virtual_touchpad/VirtualTouchpadService.cpp b/services/vr/virtual_touchpad/VirtualTouchpadService.cpp
new file mode 100644
index 0000000..e5ead0e
--- /dev/null
+++ b/services/vr/virtual_touchpad/VirtualTouchpadService.cpp
@@ -0,0 +1,23 @@
+#include "VirtualTouchpadService.h"
+
+#include <binder/Status.h>
+#include <cutils/log.h>
+#include <linux/input.h>
+#include <utils/Errors.h>
+
+namespace android {
+namespace dvr {
+
+int VirtualTouchpadService::Initialize() {
+ return touchpad_.Initialize();
+}
+
+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();
+}
+
+} // namespace dvr
+} // namespace android
diff --git a/services/vr/virtual_touchpad/VirtualTouchpadService.h b/services/vr/virtual_touchpad/VirtualTouchpadService.h
new file mode 100644
index 0000000..05a2a50
--- /dev/null
+++ b/services/vr/virtual_touchpad/VirtualTouchpadService.h
@@ -0,0 +1,36 @@
+#ifndef ANDROID_DVR_VIRTUAL_TOUCHPAD_SERVICE_H
+#define ANDROID_DVR_VIRTUAL_TOUCHPAD_SERVICE_H
+
+#include <android/dvr/BnVirtualTouchpadService.h>
+
+#include "VirtualTouchpad.h"
+
+namespace android {
+namespace dvr {
+
+class VirtualTouchpadService : public BnVirtualTouchpadService {
+ public:
+ VirtualTouchpadService(VirtualTouchpad& touchpad)
+ : touchpad_(touchpad) {}
+
+ // Must be called before clients can connect.
+ // Returns 0 if initialization is successful.
+ int Initialize();
+
+ static char const* getServiceName() { return "virtual_touchpad"; }
+
+ protected:
+ // Implements IVirtualTouchpadService.
+ ::android::binder::Status touch(float x, float y, float pressure) override;
+
+ private:
+ VirtualTouchpad& touchpad_;
+
+ VirtualTouchpadService(const VirtualTouchpadService&) = delete;
+ void operator=(const VirtualTouchpadService&) = delete;
+};
+
+} // namespace dvr
+} // namespace android
+
+#endif // ANDROID_DVR_VIRTUAL_TOUCHPAD_SERVICE_H
diff --git a/services/vr/virtual_touchpad/aidl/android/dvr/VirtualTouchpadService.aidl b/services/vr/virtual_touchpad/aidl/android/dvr/VirtualTouchpadService.aidl
new file mode 100644
index 0000000..da4de94
--- /dev/null
+++ b/services/vr/virtual_touchpad/aidl/android/dvr/VirtualTouchpadService.aidl
@@ -0,0 +1,16 @@
+package android.dvr;
+
+/** @hide */
+interface VirtualTouchpadService
+{
+ /**
+ * Generate a simulated touch event.
+ *
+ * @param x Horizontal touch position.
+ * @param y Vertical touch position.
+ * @param pressure Touch pressure; use 0.0 for no touch (lift or hover).
+ *
+ * Position values in the range [0.0, 1.0) map to the screen.
+ */
+ void touch(float x, float y, float pressure);
+}
diff --git a/services/vr/virtual_touchpad/main.cpp b/services/vr/virtual_touchpad/main.cpp
new file mode 100644
index 0000000..57471c5
--- /dev/null
+++ b/services/vr/virtual_touchpad/main.cpp
@@ -0,0 +1,36 @@
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cutils/log.h>
+
+#include "VirtualTouchpadService.h"
+
+int main() {
+ ALOGI("Starting");
+ android::dvr::VirtualTouchpad touchpad;
+ android::dvr::VirtualTouchpadService touchpad_service(touchpad);
+ const int touchpad_status = touchpad_service.Initialize();
+ if (touchpad_status) {
+ ALOGE("virtual touchpad initialization failed: %d", touchpad_status);
+ exit(1);
+ }
+
+ signal(SIGPIPE, SIG_IGN);
+ android::sp<android::ProcessState> ps(android::ProcessState::self());
+ ps->setThreadPoolMaxThreadCount(4);
+ ps->startThreadPool();
+ ps->giveThreadPoolName();
+
+ android::sp<android::IServiceManager> sm(android::defaultServiceManager());
+ const android::status_t service_status =
+ sm->addService(android::String16(touchpad_service.getServiceName()),
+ &touchpad_service, false /*allowIsolated*/);
+ if (service_status != android::OK) {
+ ALOGE("virtual touchpad service not added: %d",
+ static_cast<int>(service_status));
+ exit(2);
+ }
+
+ android::IPCThreadState::self()->joinThreadPool();
+ return 0;
+}
diff --git a/services/vr/virtual_touchpad/tests/VirtualTouchpad_test.cpp b/services/vr/virtual_touchpad/tests/VirtualTouchpad_test.cpp
new file mode 100644
index 0000000..874ef80
--- /dev/null
+++ b/services/vr/virtual_touchpad/tests/VirtualTouchpad_test.cpp
@@ -0,0 +1,233 @@
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <gtest/gtest.h>
+#include <linux/input.h>
+
+#include "EvdevInjector.h"
+#include "VirtualTouchpad.h"
+
+namespace android {
+namespace dvr {
+
+namespace {
+
+class UInputForTesting : public EvdevInjector::UInput {
+ public:
+ void WriteInputEvent(uint16_t type, uint16_t code, int32_t value) {
+ struct input_event event;
+ memset(&event, 0, sizeof(event));
+ event.type = type;
+ event.code = code;
+ event.value = value;
+ Write(&event, sizeof (event));
+ }
+};
+
+// Recording test implementation of UInput.
+//
+class UInputRecorder : public UInputForTesting {
+ public:
+ UInputRecorder() {}
+ virtual ~UInputRecorder() {}
+
+ const std::string& GetString() const { return s_; }
+ void Reset() { s_.clear(); }
+
+ // UInput overrides:
+
+ int Open() override {
+ s_ += "o;";
+ return 0;
+ }
+
+ int Close() override {
+ s_ += "c;";
+ return 0;
+ }
+
+ int Write(const void* buf, size_t count) override {
+ s_ += "w(";
+ s_ += Encode(&count, sizeof(count));
+ s_ += ",";
+ s_ += Encode(buf, count);
+ s_ += ");";
+ return 0;
+ }
+
+ int IoctlVoid(int request) override {
+ s_ += "i(";
+ s_ += Encode(&request, sizeof(request));
+ s_ += ");";
+ return 0;
+ }
+
+ int IoctlSetInt(int request, int value) override {
+ s_ += "i(";
+ s_ += Encode(&request, sizeof(request));
+ s_ += ",";
+ s_ += Encode(&value, sizeof(value));
+ s_ += ");";
+ return 0;
+ }
+
+ private:
+ std::string s_;
+
+ std::string Encode(const void* buf, size_t count) {
+ const char* in = static_cast<const char*>(buf);
+ char out[2 * count + 1];
+ for (size_t i = 0; i < count; ++i) {
+ snprintf(&out[2 * i], 3, "%02X", in[i]);
+ }
+ return out;
+ }
+};
+
+class EvdevInjectorForTesting : public EvdevInjector {
+ public:
+ EvdevInjectorForTesting(UInput& uinput) {
+ SetUInputForTesting(&uinput);
+ }
+ const uinput_user_dev* GetUiDev() const { return GetUiDevForTesting(); }
+};
+
+class VirtualTouchpadForTesting : public VirtualTouchpad {
+ public:
+ VirtualTouchpadForTesting(EvdevInjector& injector) {
+ SetEvdevInjectorForTesting(&injector);
+ }
+};
+
+void DumpDifference(const char* expect, const char* actual) {
+ printf(" common: ");
+ while (*expect && *expect == *actual) {
+ putchar(*expect);
+ ++expect;
+ ++actual;
+ }
+ printf("\n expect: %s\n", expect);
+ printf(" actual: %s\n", actual);
+}
+
+} // anonymous namespace
+
+class VirtualTouchpadTest : public testing::Test {
+};
+
+TEST_F(VirtualTouchpadTest, Goodness) {
+ UInputRecorder expect;
+ UInputRecorder record;
+ EvdevInjectorForTesting injector(record);
+ VirtualTouchpadForTesting touchpad(injector);
+
+ const int initialization_status = touchpad.Initialize();
+ EXPECT_EQ(0, initialization_status);
+
+ // Check some aspects of uinput_user_dev.
+ const uinput_user_dev* uidev = injector.GetUiDev();
+ for (int i = 0; i < ABS_CNT; ++i) {
+ EXPECT_EQ(0, uidev->absmin[i]);
+ EXPECT_EQ(0, uidev->absfuzz[i]);
+ EXPECT_EQ(0, uidev->absflat[i]);
+ if (i != ABS_MT_POSITION_X && i != ABS_MT_POSITION_Y && i != ABS_MT_SLOT) {
+ EXPECT_EQ(0, uidev->absmax[i]);
+ }
+ }
+ const int32_t width = 1 + uidev->absmax[ABS_MT_POSITION_X];
+ const int32_t height = 1 + uidev->absmax[ABS_MT_POSITION_Y];
+ const int32_t slots = uidev->absmax[ABS_MT_SLOT];
+
+ // Check the system calls performed by initialization.
+ // From ConfigureBegin():
+ expect.Open();
+ // From ConfigureInputProperty(INPUT_PROP_DIRECT):
+ expect.IoctlSetInt(UI_SET_PROPBIT, INPUT_PROP_DIRECT);
+ // From ConfigureMultiTouchXY(0, 0, kWidth - 1, kHeight - 1):
+ expect.IoctlSetInt(UI_SET_EVBIT, EV_ABS);
+ expect.IoctlSetInt(UI_SET_ABSBIT, ABS_MT_POSITION_X);
+ expect.IoctlSetInt(UI_SET_ABSBIT, ABS_MT_POSITION_Y);
+ // From ConfigureAbsSlots(kSlots):
+ expect.IoctlSetInt(UI_SET_ABSBIT, ABS_MT_SLOT);
+ // From ConfigureKey(BTN_TOUCH):
+ expect.IoctlSetInt(UI_SET_EVBIT, EV_KEY);
+ expect.IoctlSetInt(UI_SET_KEYBIT, BTN_TOUCH);
+ // From ConfigureEnd():
+ expect.Write(uidev, sizeof (uinput_user_dev));
+ expect.IoctlVoid(UI_DEV_CREATE);
+ EXPECT_EQ(expect.GetString(), record.GetString());
+
+ expect.Reset();
+ record.Reset();
+ int touch_status = touchpad.Touch(0, 0, 0);
+ EXPECT_EQ(0, touch_status);
+ expect.WriteInputEvent(EV_ABS, ABS_MT_SLOT, 0);
+ expect.WriteInputEvent(EV_ABS, ABS_MT_TRACKING_ID, 0);
+ expect.WriteInputEvent(EV_ABS, ABS_MT_POSITION_X, 0);
+ expect.WriteInputEvent(EV_ABS, ABS_MT_POSITION_Y, 0);
+ expect.WriteInputEvent(EV_SYN, SYN_REPORT, 0);
+ EXPECT_EQ(expect.GetString(), record.GetString());
+
+ expect.Reset();
+ record.Reset();
+ touch_status = touchpad.Touch(0.25f, 0.75f, 0.5f);
+ EXPECT_EQ(0, touch_status);
+ expect.WriteInputEvent(EV_ABS, ABS_MT_TRACKING_ID, 0);
+ expect.WriteInputEvent(EV_ABS, ABS_MT_POSITION_X, 0.25f * width);
+ expect.WriteInputEvent(EV_ABS, ABS_MT_POSITION_Y, 0.75f * height);
+ expect.WriteInputEvent(EV_KEY, BTN_TOUCH, EvdevInjector::KEY_PRESS);
+ expect.WriteInputEvent(EV_SYN, SYN_REPORT, 0);
+ EXPECT_EQ(expect.GetString(), record.GetString());
+
+ expect.Reset();
+ record.Reset();
+ touch_status = touchpad.Touch(1.0f, 1.0f, 1.0f);
+ EXPECT_EQ(0, touch_status);
+ expect.WriteInputEvent(EV_ABS, ABS_MT_TRACKING_ID, 0);
+ expect.WriteInputEvent(EV_ABS, ABS_MT_POSITION_X, width);
+ expect.WriteInputEvent(EV_ABS, ABS_MT_POSITION_Y, height);
+ expect.WriteInputEvent(EV_SYN, SYN_REPORT, 0);
+ EXPECT_EQ(expect.GetString(), record.GetString());
+
+ expect.Reset();
+ record.Reset();
+ touch_status = touchpad.Touch(0.25f, 0.75f, -0.01f);
+ EXPECT_EQ(0, touch_status);
+ expect.WriteInputEvent(EV_KEY, BTN_TOUCH, EvdevInjector::KEY_RELEASE);
+ expect.WriteInputEvent(EV_ABS, ABS_MT_TRACKING_ID, -1);
+ expect.WriteInputEvent(EV_SYN, SYN_REPORT, 0);
+ EXPECT_EQ(expect.GetString(), record.GetString());
+
+ expect.Reset();
+ record.Reset();
+}
+
+TEST_F(VirtualTouchpadTest, Badness) {
+ UInputRecorder expect;
+ UInputRecorder record;
+ EvdevInjectorForTesting injector(record);
+ VirtualTouchpadForTesting touchpad(injector);
+
+ // Touch before initialization should return an error,
+ // and should not result in any system calls.
+ expect.Reset();
+ record.Reset();
+ int touch_status = touchpad.Touch(0.25f, 0.75f, -0.01f);
+ EXPECT_NE(0, touch_status);
+ EXPECT_EQ(expect.GetString(), record.GetString());
+
+ expect.Reset();
+ record.Reset();
+ touchpad.Initialize();
+
+ // Repeated initialization should return an error,
+ // and should not result in any system calls.
+ expect.Reset();
+ record.Reset();
+ const int initialization_status = touchpad.Initialize();
+ EXPECT_NE(0, initialization_status);
+ EXPECT_EQ(expect.GetString(), record.GetString());
+}
+
+} // namespace dvr
+} // namespace android
diff --git a/services/vr/virtual_touchpad/virtual_touchpad.rc b/services/vr/virtual_touchpad/virtual_touchpad.rc
new file mode 100644
index 0000000..b4f9f00
--- /dev/null
+++ b/services/vr/virtual_touchpad/virtual_touchpad.rc
@@ -0,0 +1,5 @@
+service virtual_touchpad /system/bin/virtual_touchpad
+ class core
+ user system
+ group system input
+ cpuset /system