Loading api/test-current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -5989,6 +5989,7 @@ package android.app { method public void destroy(); method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(java.lang.Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException; method public android.os.ParcelFileDescriptor executeShellCommand(java.lang.String); method public android.os.ParcelFileDescriptor[] executeShellCommandRw(java.lang.String); method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow(); method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo(); cmds/hid/README.md 0 → 100644 +145 −0 Original line number Diff line number Diff line # Usage ## Two options to use the hid command: ### 1. Interactive through stdin: type `hid -` into the terminal, then type/paste commands to send to the binary. Use Ctrl+D to signal end of stream to the binary (EOF). This mode can be also used from an app to send HID events. For an example, see the cts test case at: [InputTestCase.java][2] When using another program to control hid in interactive mode, registering a new input device (for example, a bluetooth joystick) should be the first step. After the device is added, you need to wait for the _onInputDeviceAdded_ (see [InputDeviceListener][1]) notification before issuing commands to the device. Failure to do so will cause missed events and inconsistent behaviour. In the current implementation of the hid command, the hid binary will wait for the file descriptor to the uhid node to send the UHID_START and UHID_OPEN signals before returning. However, this is not sufficient. These signals only notify the readiness of the kernel driver, but do not take into account the inputflinger framework. ### 2. Using a file as an input: type `hid <filename>`, and the file will be used an an input to the binary. You must add a sufficient delay after a "register" command to ensure device is ready. The interactive mode is the recommended method of communicating with the hid binary. All of the input commands should be in pseudo-JSON format as documented below. See examples [here][3]. The file can have multiple commands one after the other (which is not strictly legal JSON format, as this would imply multiple root elements). ## Command description 1. `register` Register a new uhid device | Field | Type | Description | |:-------------:|:-------------:|:--------------------------| | id | integer | Device id | | command | string | Must be set to "register" | | name | string | Device name | | vid | 16-bit integer| Vendor id | | pid | 16-bit integer| Product id | | descriptor | byte array | USB HID report descriptor | Device ID is used for matching the subsequent commands to a specific device to avoid ambiguity when multiple devices are registered. USB HID report descriptor should be generated according the the USB HID spec and can be checked by reverse parsing using a variety of tools, for example [usbdescreqparser][5]. Example: ```json { "id": 1, "command": "register", "name": "Odie (Test)", "vid": 0x18d1, "pid": 0x2c40, "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00, 0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a, 0x23, 0x02, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0b, 0x81, 0x02, 0x75, 0x01, 0x95, 0x01, 0x81, 0x03, 0x05, 0x01, 0x75, 0x04, 0x95, 0x01, 0x25, 0x07, 0x46, 0x3b, 0x01, 0x66, 0x14, 0x00, 0x09, 0x39, 0x81, 0x42, 0x66, 0x00, 0x00, 0x09, 0x01, 0xa1, 0x00, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x05, 0x02, 0x09, 0xc5, 0x09, 0xc4, 0x15, 0x00, 0x26, 0xff, 0x00, 0x35, 0x00, 0x46, 0xff, 0x00, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0x85, 0x02, 0x05, 0x08, 0x0a, 0x01, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x03, 0x00, 0x0a, 0x04, 0x00, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x91, 0x02, 0x75, 0x04, 0x95, 0x01, 0x91, 0x03, 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x03, 0x05, 0x01, 0x09, 0x06, 0xa1, 0x02, 0x05, 0x06, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x01, 0x81, 0x02, 0x06, 0xbc, 0xff, 0x0a, 0xad, 0xbd, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0xc0] } ``` 2. `delay` Add a delay to command processing | Field | Type | Description | |:-------------:|:-------------:|:-------------------------- | | id | integer | Device id | | command | string | Must be set to "delay" | | duration | integer | Delay in milliseconds | Example: ```json { "id": 1, "command": "delay", "duration": 10 } ``` 3. `report` Send a report to the HID device | Field | Type | Description | |:-------------:|:-------------:|:-------------------------- | | id | integer | Device id | | command | string | Must be set to "report" | | report | byte array | Report data to send | Example: ```json { "id": 1, "command": "report", "report": [0x01, 0x01, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x00, 0x00] } ``` ### Sending a joystick button press event To send a button press event on a joystick device: 1. Register the joystick device 2. Send button down event with coordinates ABS_X, ABS_Y, ABS_Z, and ABS_RZ at the center of the range. If the coordinates are not centered, this event will generate a motion event within the input framework, in addition to the button press event. The range can be determined from the uhid report descriptor. 3. Send the button up event with the same coordinates as in 2. 4. Check that the button press event was received. ### Notes 1. As soon as EOF is reached (either in interactive mode, or in file mode), the device that was created will be unregistered. There is no explicit command for unregistering a device. 2. The linux input subsystem does not generate events for those values that remain unchanged. For example, if there are two events sent to the driver, and both events have the same value of ABS_X, then ABS_X coordinate will not be reported. 3. The description of joystick actions is available [here][6]. 4. Joysticks are split axes. When an analog stick is in a resting state, the reported coordinates are at the center of the range. 5. The `getevent` utility can used to print out the key events for debugging purposes. [1]: https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html [2]: ../../../../cts/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java [3]: ../../../../cts/tests/tests/hardware/res/raw/ [4]: https://developer.android.com/training/game-controllers/controller-input.html#button [5]: http://eleccelerator.com/usbdescreqparser/ [6]: https://developer.android.com/training/game-controllers/controller-input.html No newline at end of file cmds/hid/jni/Android.mk +2 −7 Original line number Diff line number Diff line Loading @@ -6,14 +6,9 @@ LOCAL_SRC_FILES := \ com_android_commands_hid_Device.cpp LOCAL_C_INCLUDES := \ $(JNI_H_INCLUDE) \ frameworks/base/core/jni $(JNI_H_INCLUDE) LOCAL_SHARED_LIBRARIES := \ libandroid_runtime \ liblog \ libnativehelper \ libutils LOCAL_LDLIBS += -landroid -llog -lnativehelper LOCAL_MODULE := libhidcommand_jni LOCAL_MODULE_TAGS := optional Loading cmds/hid/jni/com_android_commands_hid_Device.cpp +77 −48 Original line number Diff line number Diff line Loading @@ -26,17 +26,17 @@ #include <memory> #include <unistd.h> #include <android_runtime/AndroidRuntime.h> #include <android_runtime/Log.h> #include <android_os_MessageQueue.h> #include <core_jni_helpers.h> #include <jni.h> #include <JNIHelp.h> #include <ScopedPrimitiveArray.h> #include <ScopedUtfChars.h> #include <utils/Log.h> #include <utils/Looper.h> #include <utils/StrongPointer.h> #include <android/looper.h> #include <android/log.h> #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) namespace android { namespace uhid { Loading @@ -56,59 +56,67 @@ static int handleLooperEvents(int /* fd */, int events, void* data) { static void checkAndClearException(JNIEnv* env, const char* methodName) { if (env->ExceptionCheck()) { ALOGE("An exception was thrown by callback '%s'.", methodName); LOGE_EX(env); LOGE("An exception was thrown by callback '%s'.", methodName); env->ExceptionClear(); } } DeviceCallback::DeviceCallback(JNIEnv* env, jobject callback) : mCallbackObject(env->NewGlobalRef(callback)) { } mCallbackObject(env->NewGlobalRef(callback)) { env->GetJavaVM(&mJavaVM); } DeviceCallback::~DeviceCallback() { JNIEnv* env = AndroidRuntime::getJNIEnv(); JNIEnv* env = getJNIEnv(); env->DeleteGlobalRef(mCallbackObject); } void DeviceCallback::onDeviceError() { JNIEnv* env = AndroidRuntime::getJNIEnv(); JNIEnv* env = getJNIEnv(); env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceError); checkAndClearException(env, "onDeviceError"); } void DeviceCallback::onDeviceOpen() { JNIEnv* env = AndroidRuntime::getJNIEnv(); JNIEnv* env = getJNIEnv(); env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOpen); checkAndClearException(env, "onDeviceOpen"); } JNIEnv* DeviceCallback::getJNIEnv() { JNIEnv* env; mJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); return env; } Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid, std::unique_ptr<uint8_t[]> descriptor, size_t descriptorSize, std::unique_ptr<DeviceCallback> callback, sp<Looper> looper) { std::unique_ptr<DeviceCallback> callback) { int fd = ::open(UHID_PATH, O_RDWR | O_CLOEXEC); if (fd < 0) { ALOGE("Failed to open uhid: %s", strerror(errno)); LOGE("Failed to open uhid: %s", strerror(errno)); return nullptr; } struct uhid_event ev; memset(&ev, 0, sizeof(ev)); ev.type = UHID_CREATE; strncpy((char*)ev.u.create.name, name, UHID_MAX_NAME_LENGTH); ev.u.create.rd_data = descriptor.get(); ev.u.create.rd_size = descriptorSize; ev.u.create.bus = BUS_BLUETOOTH; ev.u.create.vendor = vid; ev.u.create.product = pid; ev.u.create.version = 0; ev.u.create.country = 0; ev.type = UHID_CREATE2; strncpy((char*)ev.u.create2.name, name, UHID_MAX_NAME_LENGTH); memcpy(&ev.u.create2.rd_data, descriptor.get(), descriptorSize * sizeof(ev.u.create2.rd_data[0])); ev.u.create2.rd_size = descriptorSize; ev.u.create2.bus = BUS_BLUETOOTH; ev.u.create2.vendor = vid; ev.u.create2.product = pid; ev.u.create2.version = 0; ev.u.create2.country = 0; errno = 0; ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev))); if (ret < 0 || ret != sizeof(ev)) { ::close(fd); ALOGE("Failed to create uhid node: %s", strerror(errno)); LOGE("Failed to create uhid node: %s", strerror(errno)); return nullptr; } Loading @@ -116,20 +124,30 @@ Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid, ret = TEMP_FAILURE_RETRY(::read(fd, &ev, sizeof(ev))); if (ret < 0 || ev.type != UHID_START) { ::close(fd); ALOGE("uhid node failed to start: %s", strerror(errno)); LOGE("uhid node failed to start: %s", strerror(errno)); return nullptr; } return new Device(id, fd, std::move(callback), looper); return new Device(id, fd, std::move(callback)); } Device::Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback, sp<Looper> looper) : mId(id), mFd(fd), mDeviceCallback(std::move(callback)), mLooper(looper) { looper->addFd(fd, 0, Looper::EVENT_INPUT, handleLooperEvents, reinterpret_cast<void*>(this)); Device::Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback) : mId(id), mFd(fd), mDeviceCallback(std::move(callback)) { ALooper* aLooper = ALooper_forThread(); if (aLooper == NULL) { LOGE("Could not get ALooper, ALooper_forThread returned NULL"); aLooper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); } ALooper_addFd(aLooper, fd, 0, ALOOPER_EVENT_INPUT, handleLooperEvents, reinterpret_cast<void*>(this)); } Device::~Device() { mLooper->removeFd(mFd); ALooper* looper = ALooper_forThread(); if (looper != NULL) { ALooper_removeFd(looper, mFd); } else { LOGE("Could not remove fd, ALooper_forThread() returned NULL!"); } struct uhid_event ev; memset(&ev, 0, sizeof(ev)); ev.type = UHID_DESTROY; Loading @@ -141,25 +159,25 @@ Device::~Device() { void Device::sendReport(uint8_t* report, size_t reportSize) { struct uhid_event ev; memset(&ev, 0, sizeof(ev)); ev.type = UHID_INPUT; ev.u.input.size = reportSize; memcpy(&ev.u.input.data, report, reportSize); ev.type = UHID_INPUT2; ev.u.input2.size = reportSize; memcpy(&ev.u.input2.data, report, reportSize); ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev))); if (ret < 0 || ret != sizeof(ev)) { ALOGE("Failed to send hid event: %s", strerror(errno)); LOGE("Failed to send hid event: %s", strerror(errno)); } } int Device::handleEvents(int events) { if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) { ALOGE("uhid node was closed or an error occurred. events=0x%x", events); if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { LOGE("uhid node was closed or an error occurred. events=0x%x", events); mDeviceCallback->onDeviceError(); return 0; } struct uhid_event ev; ssize_t ret = TEMP_FAILURE_RETRY(::read(mFd, &ev, sizeof(ev))); if (ret < 0) { ALOGE("Failed to read from uhid node: %s", strerror(errno)); LOGE("Failed to read from uhid node: %s", strerror(errno)); mDeviceCallback->onDeviceError(); return 0; } Loading @@ -184,7 +202,7 @@ std::unique_ptr<uint8_t[]> getData(JNIEnv* env, jbyteArray javaArray, size_t& ou } static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid, jint pid, jbyteArray rawDescriptor, jobject queue, jobject callback) { jbyteArray rawDescriptor, jobject callback) { ScopedUtfChars name(env, rawName); if (name.c_str() == nullptr) { return 0; Loading @@ -194,11 +212,10 @@ static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint i std::unique_ptr<uint8_t[]> desc = getData(env, rawDescriptor, size); std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback)); sp<Looper> looper = android_os_MessageQueue_getMessageQueue(env, queue)->getLooper(); uhid::Device* d = uhid::Device::open( id, reinterpret_cast<const char*>(name.c_str()), vid, pid, std::move(desc), size, std::move(cb), std::move(looper)); std::move(desc), size, std::move(cb)); return reinterpret_cast<jlong>(d); } Loading @@ -208,6 +225,8 @@ static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr,jbyteArray raw uhid::Device* d = reinterpret_cast<uhid::Device*>(ptr); if (d) { d->sendReport(report.get(), size); } else { LOGE("Could not send report, Device* is null!"); } } Loading @@ -220,7 +239,7 @@ static void closeDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) { static JNINativeMethod sMethods[] = { { "nativeOpenDevice", "(Ljava/lang/String;III[BLandroid/os/MessageQueue;" "(Ljava/lang/String;III[B" "Lcom/android/commands/hid/Device$DeviceCallback;)J", reinterpret_cast<void*>(openDevice) }, { "nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport) }, Loading @@ -228,11 +247,21 @@ static JNINativeMethod sMethods[] = { }; int register_com_android_commands_hid_Device(JNIEnv* env) { jclass clazz = FindClassOrDie(env, "com/android/commands/hid/Device$DeviceCallback"); jclass clazz = env->FindClass("com/android/commands/hid/Device$DeviceCallback"); if (clazz == NULL) { LOGE("Unable to find class 'DeviceCallback'"); return JNI_ERR; } uhid::gDeviceCallbackClassInfo.onDeviceOpen = GetMethodIDOrDie(env, clazz, "onDeviceOpen", "()V"); env->GetMethodID(clazz, "onDeviceOpen", "()V"); uhid::gDeviceCallbackClassInfo.onDeviceError = GetMethodIDOrDie(env, clazz, "onDeviceError", "()V"); env->GetMethodID(clazz, "onDeviceError", "()V"); if (uhid::gDeviceCallbackClassInfo.onDeviceOpen == NULL || uhid::gDeviceCallbackClassInfo.onDeviceError == NULL) { LOGE("Unable to obtain onDeviceOpen or onDeviceError methods"); return JNI_ERR; } return jniRegisterNativeMethods(env, "com/android/commands/hid/Device", sMethods, NELEM(sMethods)); } Loading cmds/hid/jni/com_android_commands_hid_Device.h +4 −5 Original line number Diff line number Diff line Loading @@ -17,8 +17,6 @@ #include <memory> #include <jni.h> #include <utils/Looper.h> #include <utils/StrongPointer.h> namespace android { namespace uhid { Loading @@ -32,16 +30,18 @@ public: void onDeviceError(); private: JNIEnv* getJNIEnv(); jobject mCallbackObject; JavaVM* mJavaVM; }; class Device { public: static Device* open(int32_t id, const char* name, int32_t vid, int32_t pid, std::unique_ptr<uint8_t[]> descriptor, size_t descriptorSize, std::unique_ptr<DeviceCallback> callback, sp<Looper> looper); std::unique_ptr<DeviceCallback> callback); Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback, sp<Looper> looper); Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback); ~Device(); void sendReport(uint8_t* report, size_t reportSize); Loading @@ -53,7 +53,6 @@ private: int32_t mId; int mFd; std::unique_ptr<DeviceCallback> mDeviceCallback; sp<Looper> mLooper; }; Loading Loading
api/test-current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -5989,6 +5989,7 @@ package android.app { method public void destroy(); method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(java.lang.Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException; method public android.os.ParcelFileDescriptor executeShellCommand(java.lang.String); method public android.os.ParcelFileDescriptor[] executeShellCommandRw(java.lang.String); method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow(); method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
cmds/hid/README.md 0 → 100644 +145 −0 Original line number Diff line number Diff line # Usage ## Two options to use the hid command: ### 1. Interactive through stdin: type `hid -` into the terminal, then type/paste commands to send to the binary. Use Ctrl+D to signal end of stream to the binary (EOF). This mode can be also used from an app to send HID events. For an example, see the cts test case at: [InputTestCase.java][2] When using another program to control hid in interactive mode, registering a new input device (for example, a bluetooth joystick) should be the first step. After the device is added, you need to wait for the _onInputDeviceAdded_ (see [InputDeviceListener][1]) notification before issuing commands to the device. Failure to do so will cause missed events and inconsistent behaviour. In the current implementation of the hid command, the hid binary will wait for the file descriptor to the uhid node to send the UHID_START and UHID_OPEN signals before returning. However, this is not sufficient. These signals only notify the readiness of the kernel driver, but do not take into account the inputflinger framework. ### 2. Using a file as an input: type `hid <filename>`, and the file will be used an an input to the binary. You must add a sufficient delay after a "register" command to ensure device is ready. The interactive mode is the recommended method of communicating with the hid binary. All of the input commands should be in pseudo-JSON format as documented below. See examples [here][3]. The file can have multiple commands one after the other (which is not strictly legal JSON format, as this would imply multiple root elements). ## Command description 1. `register` Register a new uhid device | Field | Type | Description | |:-------------:|:-------------:|:--------------------------| | id | integer | Device id | | command | string | Must be set to "register" | | name | string | Device name | | vid | 16-bit integer| Vendor id | | pid | 16-bit integer| Product id | | descriptor | byte array | USB HID report descriptor | Device ID is used for matching the subsequent commands to a specific device to avoid ambiguity when multiple devices are registered. USB HID report descriptor should be generated according the the USB HID spec and can be checked by reverse parsing using a variety of tools, for example [usbdescreqparser][5]. Example: ```json { "id": 1, "command": "register", "name": "Odie (Test)", "vid": 0x18d1, "pid": 0x2c40, "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00, 0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a, 0x23, 0x02, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0b, 0x81, 0x02, 0x75, 0x01, 0x95, 0x01, 0x81, 0x03, 0x05, 0x01, 0x75, 0x04, 0x95, 0x01, 0x25, 0x07, 0x46, 0x3b, 0x01, 0x66, 0x14, 0x00, 0x09, 0x39, 0x81, 0x42, 0x66, 0x00, 0x00, 0x09, 0x01, 0xa1, 0x00, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x05, 0x02, 0x09, 0xc5, 0x09, 0xc4, 0x15, 0x00, 0x26, 0xff, 0x00, 0x35, 0x00, 0x46, 0xff, 0x00, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0x85, 0x02, 0x05, 0x08, 0x0a, 0x01, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x03, 0x00, 0x0a, 0x04, 0x00, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x91, 0x02, 0x75, 0x04, 0x95, 0x01, 0x91, 0x03, 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x03, 0x05, 0x01, 0x09, 0x06, 0xa1, 0x02, 0x05, 0x06, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x01, 0x81, 0x02, 0x06, 0xbc, 0xff, 0x0a, 0xad, 0xbd, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0xc0] } ``` 2. `delay` Add a delay to command processing | Field | Type | Description | |:-------------:|:-------------:|:-------------------------- | | id | integer | Device id | | command | string | Must be set to "delay" | | duration | integer | Delay in milliseconds | Example: ```json { "id": 1, "command": "delay", "duration": 10 } ``` 3. `report` Send a report to the HID device | Field | Type | Description | |:-------------:|:-------------:|:-------------------------- | | id | integer | Device id | | command | string | Must be set to "report" | | report | byte array | Report data to send | Example: ```json { "id": 1, "command": "report", "report": [0x01, 0x01, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x00, 0x00] } ``` ### Sending a joystick button press event To send a button press event on a joystick device: 1. Register the joystick device 2. Send button down event with coordinates ABS_X, ABS_Y, ABS_Z, and ABS_RZ at the center of the range. If the coordinates are not centered, this event will generate a motion event within the input framework, in addition to the button press event. The range can be determined from the uhid report descriptor. 3. Send the button up event with the same coordinates as in 2. 4. Check that the button press event was received. ### Notes 1. As soon as EOF is reached (either in interactive mode, or in file mode), the device that was created will be unregistered. There is no explicit command for unregistering a device. 2. The linux input subsystem does not generate events for those values that remain unchanged. For example, if there are two events sent to the driver, and both events have the same value of ABS_X, then ABS_X coordinate will not be reported. 3. The description of joystick actions is available [here][6]. 4. Joysticks are split axes. When an analog stick is in a resting state, the reported coordinates are at the center of the range. 5. The `getevent` utility can used to print out the key events for debugging purposes. [1]: https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html [2]: ../../../../cts/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java [3]: ../../../../cts/tests/tests/hardware/res/raw/ [4]: https://developer.android.com/training/game-controllers/controller-input.html#button [5]: http://eleccelerator.com/usbdescreqparser/ [6]: https://developer.android.com/training/game-controllers/controller-input.html No newline at end of file
cmds/hid/jni/Android.mk +2 −7 Original line number Diff line number Diff line Loading @@ -6,14 +6,9 @@ LOCAL_SRC_FILES := \ com_android_commands_hid_Device.cpp LOCAL_C_INCLUDES := \ $(JNI_H_INCLUDE) \ frameworks/base/core/jni $(JNI_H_INCLUDE) LOCAL_SHARED_LIBRARIES := \ libandroid_runtime \ liblog \ libnativehelper \ libutils LOCAL_LDLIBS += -landroid -llog -lnativehelper LOCAL_MODULE := libhidcommand_jni LOCAL_MODULE_TAGS := optional Loading
cmds/hid/jni/com_android_commands_hid_Device.cpp +77 −48 Original line number Diff line number Diff line Loading @@ -26,17 +26,17 @@ #include <memory> #include <unistd.h> #include <android_runtime/AndroidRuntime.h> #include <android_runtime/Log.h> #include <android_os_MessageQueue.h> #include <core_jni_helpers.h> #include <jni.h> #include <JNIHelp.h> #include <ScopedPrimitiveArray.h> #include <ScopedUtfChars.h> #include <utils/Log.h> #include <utils/Looper.h> #include <utils/StrongPointer.h> #include <android/looper.h> #include <android/log.h> #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) namespace android { namespace uhid { Loading @@ -56,59 +56,67 @@ static int handleLooperEvents(int /* fd */, int events, void* data) { static void checkAndClearException(JNIEnv* env, const char* methodName) { if (env->ExceptionCheck()) { ALOGE("An exception was thrown by callback '%s'.", methodName); LOGE_EX(env); LOGE("An exception was thrown by callback '%s'.", methodName); env->ExceptionClear(); } } DeviceCallback::DeviceCallback(JNIEnv* env, jobject callback) : mCallbackObject(env->NewGlobalRef(callback)) { } mCallbackObject(env->NewGlobalRef(callback)) { env->GetJavaVM(&mJavaVM); } DeviceCallback::~DeviceCallback() { JNIEnv* env = AndroidRuntime::getJNIEnv(); JNIEnv* env = getJNIEnv(); env->DeleteGlobalRef(mCallbackObject); } void DeviceCallback::onDeviceError() { JNIEnv* env = AndroidRuntime::getJNIEnv(); JNIEnv* env = getJNIEnv(); env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceError); checkAndClearException(env, "onDeviceError"); } void DeviceCallback::onDeviceOpen() { JNIEnv* env = AndroidRuntime::getJNIEnv(); JNIEnv* env = getJNIEnv(); env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOpen); checkAndClearException(env, "onDeviceOpen"); } JNIEnv* DeviceCallback::getJNIEnv() { JNIEnv* env; mJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); return env; } Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid, std::unique_ptr<uint8_t[]> descriptor, size_t descriptorSize, std::unique_ptr<DeviceCallback> callback, sp<Looper> looper) { std::unique_ptr<DeviceCallback> callback) { int fd = ::open(UHID_PATH, O_RDWR | O_CLOEXEC); if (fd < 0) { ALOGE("Failed to open uhid: %s", strerror(errno)); LOGE("Failed to open uhid: %s", strerror(errno)); return nullptr; } struct uhid_event ev; memset(&ev, 0, sizeof(ev)); ev.type = UHID_CREATE; strncpy((char*)ev.u.create.name, name, UHID_MAX_NAME_LENGTH); ev.u.create.rd_data = descriptor.get(); ev.u.create.rd_size = descriptorSize; ev.u.create.bus = BUS_BLUETOOTH; ev.u.create.vendor = vid; ev.u.create.product = pid; ev.u.create.version = 0; ev.u.create.country = 0; ev.type = UHID_CREATE2; strncpy((char*)ev.u.create2.name, name, UHID_MAX_NAME_LENGTH); memcpy(&ev.u.create2.rd_data, descriptor.get(), descriptorSize * sizeof(ev.u.create2.rd_data[0])); ev.u.create2.rd_size = descriptorSize; ev.u.create2.bus = BUS_BLUETOOTH; ev.u.create2.vendor = vid; ev.u.create2.product = pid; ev.u.create2.version = 0; ev.u.create2.country = 0; errno = 0; ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev))); if (ret < 0 || ret != sizeof(ev)) { ::close(fd); ALOGE("Failed to create uhid node: %s", strerror(errno)); LOGE("Failed to create uhid node: %s", strerror(errno)); return nullptr; } Loading @@ -116,20 +124,30 @@ Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid, ret = TEMP_FAILURE_RETRY(::read(fd, &ev, sizeof(ev))); if (ret < 0 || ev.type != UHID_START) { ::close(fd); ALOGE("uhid node failed to start: %s", strerror(errno)); LOGE("uhid node failed to start: %s", strerror(errno)); return nullptr; } return new Device(id, fd, std::move(callback), looper); return new Device(id, fd, std::move(callback)); } Device::Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback, sp<Looper> looper) : mId(id), mFd(fd), mDeviceCallback(std::move(callback)), mLooper(looper) { looper->addFd(fd, 0, Looper::EVENT_INPUT, handleLooperEvents, reinterpret_cast<void*>(this)); Device::Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback) : mId(id), mFd(fd), mDeviceCallback(std::move(callback)) { ALooper* aLooper = ALooper_forThread(); if (aLooper == NULL) { LOGE("Could not get ALooper, ALooper_forThread returned NULL"); aLooper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); } ALooper_addFd(aLooper, fd, 0, ALOOPER_EVENT_INPUT, handleLooperEvents, reinterpret_cast<void*>(this)); } Device::~Device() { mLooper->removeFd(mFd); ALooper* looper = ALooper_forThread(); if (looper != NULL) { ALooper_removeFd(looper, mFd); } else { LOGE("Could not remove fd, ALooper_forThread() returned NULL!"); } struct uhid_event ev; memset(&ev, 0, sizeof(ev)); ev.type = UHID_DESTROY; Loading @@ -141,25 +159,25 @@ Device::~Device() { void Device::sendReport(uint8_t* report, size_t reportSize) { struct uhid_event ev; memset(&ev, 0, sizeof(ev)); ev.type = UHID_INPUT; ev.u.input.size = reportSize; memcpy(&ev.u.input.data, report, reportSize); ev.type = UHID_INPUT2; ev.u.input2.size = reportSize; memcpy(&ev.u.input2.data, report, reportSize); ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev))); if (ret < 0 || ret != sizeof(ev)) { ALOGE("Failed to send hid event: %s", strerror(errno)); LOGE("Failed to send hid event: %s", strerror(errno)); } } int Device::handleEvents(int events) { if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) { ALOGE("uhid node was closed or an error occurred. events=0x%x", events); if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { LOGE("uhid node was closed or an error occurred. events=0x%x", events); mDeviceCallback->onDeviceError(); return 0; } struct uhid_event ev; ssize_t ret = TEMP_FAILURE_RETRY(::read(mFd, &ev, sizeof(ev))); if (ret < 0) { ALOGE("Failed to read from uhid node: %s", strerror(errno)); LOGE("Failed to read from uhid node: %s", strerror(errno)); mDeviceCallback->onDeviceError(); return 0; } Loading @@ -184,7 +202,7 @@ std::unique_ptr<uint8_t[]> getData(JNIEnv* env, jbyteArray javaArray, size_t& ou } static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid, jint pid, jbyteArray rawDescriptor, jobject queue, jobject callback) { jbyteArray rawDescriptor, jobject callback) { ScopedUtfChars name(env, rawName); if (name.c_str() == nullptr) { return 0; Loading @@ -194,11 +212,10 @@ static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint i std::unique_ptr<uint8_t[]> desc = getData(env, rawDescriptor, size); std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback)); sp<Looper> looper = android_os_MessageQueue_getMessageQueue(env, queue)->getLooper(); uhid::Device* d = uhid::Device::open( id, reinterpret_cast<const char*>(name.c_str()), vid, pid, std::move(desc), size, std::move(cb), std::move(looper)); std::move(desc), size, std::move(cb)); return reinterpret_cast<jlong>(d); } Loading @@ -208,6 +225,8 @@ static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr,jbyteArray raw uhid::Device* d = reinterpret_cast<uhid::Device*>(ptr); if (d) { d->sendReport(report.get(), size); } else { LOGE("Could not send report, Device* is null!"); } } Loading @@ -220,7 +239,7 @@ static void closeDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) { static JNINativeMethod sMethods[] = { { "nativeOpenDevice", "(Ljava/lang/String;III[BLandroid/os/MessageQueue;" "(Ljava/lang/String;III[B" "Lcom/android/commands/hid/Device$DeviceCallback;)J", reinterpret_cast<void*>(openDevice) }, { "nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport) }, Loading @@ -228,11 +247,21 @@ static JNINativeMethod sMethods[] = { }; int register_com_android_commands_hid_Device(JNIEnv* env) { jclass clazz = FindClassOrDie(env, "com/android/commands/hid/Device$DeviceCallback"); jclass clazz = env->FindClass("com/android/commands/hid/Device$DeviceCallback"); if (clazz == NULL) { LOGE("Unable to find class 'DeviceCallback'"); return JNI_ERR; } uhid::gDeviceCallbackClassInfo.onDeviceOpen = GetMethodIDOrDie(env, clazz, "onDeviceOpen", "()V"); env->GetMethodID(clazz, "onDeviceOpen", "()V"); uhid::gDeviceCallbackClassInfo.onDeviceError = GetMethodIDOrDie(env, clazz, "onDeviceError", "()V"); env->GetMethodID(clazz, "onDeviceError", "()V"); if (uhid::gDeviceCallbackClassInfo.onDeviceOpen == NULL || uhid::gDeviceCallbackClassInfo.onDeviceError == NULL) { LOGE("Unable to obtain onDeviceOpen or onDeviceError methods"); return JNI_ERR; } return jniRegisterNativeMethods(env, "com/android/commands/hid/Device", sMethods, NELEM(sMethods)); } Loading
cmds/hid/jni/com_android_commands_hid_Device.h +4 −5 Original line number Diff line number Diff line Loading @@ -17,8 +17,6 @@ #include <memory> #include <jni.h> #include <utils/Looper.h> #include <utils/StrongPointer.h> namespace android { namespace uhid { Loading @@ -32,16 +30,18 @@ public: void onDeviceError(); private: JNIEnv* getJNIEnv(); jobject mCallbackObject; JavaVM* mJavaVM; }; class Device { public: static Device* open(int32_t id, const char* name, int32_t vid, int32_t pid, std::unique_ptr<uint8_t[]> descriptor, size_t descriptorSize, std::unique_ptr<DeviceCallback> callback, sp<Looper> looper); std::unique_ptr<DeviceCallback> callback); Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback, sp<Looper> looper); Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback); ~Device(); void sendReport(uint8_t* report, size_t reportSize); Loading @@ -53,7 +53,6 @@ private: int32_t mId; int mFd; std::unique_ptr<DeviceCallback> mDeviceCallback; sp<Looper> mLooper; }; Loading