Fix GamepadTestCase#testButtonA CTS test
Hid command, JNI layer: - Removed dependency of the hid device on libandroid_runtime and libutils. Using ALooper from libandroid to process callbacks from /dev/uhid file descriptor. - Switched to using "CREATE2" and "INPUT2" constructs in uhid driver Hid command, Java layer: - Removed delay workarounds, user now responsible for waiting for onInputDeviceChanged notification prior to using the hid commands. UiAutomation: - Added a new executeShellCommandRw function that allows bidirectional communication to shell command platform.xml: - Added uhid permissions to bluetooth stack for /dev/uhid access - CTS test now consistently passes Bug: 34052337 Test: CTS test case invoked with the following command: run cts -t android.hardware.input.cts.tests.GamepadTestCase -m CtsHardwareTestCases --skip-system-status-check com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker Change-Id: Ic916c513b7e652b1e25675f0f38f0f1f3a65d214
This commit is contained in:
@@ -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();
|
||||
|
||||
145
cmds/hid/README.md
Normal file
145
cmds/hid/README.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# 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
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -194,20 +212,21 @@ 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);
|
||||
}
|
||||
|
||||
static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr,jbyteArray rawReport) {
|
||||
static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr, jbyteArray rawReport) {
|
||||
size_t size;
|
||||
std::unique_ptr<uint8_t[]> report = getData(env, rawReport, size);
|
||||
uhid::Device* d = reinterpret_cast<uhid::Device*>(ptr);
|
||||
if (d) {
|
||||
d->sendReport(report.get(), size);
|
||||
} else {
|
||||
LOGE("Could not send report, Device* is null!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) },
|
||||
@@ -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");
|
||||
uhid::gDeviceCallbackClassInfo.onDeviceError=
|
||||
GetMethodIDOrDie(env, clazz, "onDeviceError", "()V");
|
||||
env->GetMethodID(clazz, "onDeviceOpen", "()V");
|
||||
uhid::gDeviceCallbackClassInfo.onDeviceError =
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
#include <memory>
|
||||
|
||||
#include <jni.h>
|
||||
#include <utils/Looper.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
|
||||
namespace android {
|
||||
namespace uhid {
|
||||
@@ -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);
|
||||
@@ -53,7 +53,6 @@ private:
|
||||
int32_t mId;
|
||||
int mFd;
|
||||
std::unique_ptr<DeviceCallback> mDeviceCallback;
|
||||
sp<Looper> mLooper;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -29,22 +29,14 @@ import com.android.internal.os.SomeArgs;
|
||||
public class Device {
|
||||
private static final String TAG = "HidDevice";
|
||||
|
||||
// Minimum amount of time to wait before sending input events to a device. Even though we're
|
||||
// guaranteed that the device has been created and opened by the input system, there's still a
|
||||
// window in which the system hasn't started reading events out of it. If a stream of events
|
||||
// begins in during this window (like a button down event) and *then* we start reading, we're
|
||||
// liable to ignore the whole stream.
|
||||
private static final int MIN_WAIT_FOR_FIRST_EVENT = 150;
|
||||
|
||||
private static final int MSG_OPEN_DEVICE = 1;
|
||||
private static final int MSG_SEND_REPORT = 2;
|
||||
private static final int MSG_CLOSE_DEVICE = 3;
|
||||
|
||||
|
||||
private final int mId;
|
||||
private final HandlerThread mThread;
|
||||
private final DeviceHandler mHandler;
|
||||
private long mEventTime;
|
||||
private long mTimeToSend;
|
||||
|
||||
private final Object mCond = new Object();
|
||||
|
||||
@@ -53,7 +45,7 @@ public class Device {
|
||||
}
|
||||
|
||||
private static native long nativeOpenDevice(String name, int id, int vid, int pid,
|
||||
byte[] descriptor, MessageQueue queue, DeviceCallback callback);
|
||||
byte[] descriptor, DeviceCallback callback);
|
||||
private static native void nativeSendReport(long ptr, byte[] data);
|
||||
private static native void nativeCloseDevice(long ptr);
|
||||
|
||||
@@ -74,22 +66,22 @@ public class Device {
|
||||
args.arg2 = descriptor;
|
||||
args.arg3 = report;
|
||||
mHandler.obtainMessage(MSG_OPEN_DEVICE, args).sendToTarget();
|
||||
mEventTime = SystemClock.uptimeMillis() + MIN_WAIT_FOR_FIRST_EVENT;
|
||||
mTimeToSend = SystemClock.uptimeMillis();
|
||||
}
|
||||
|
||||
public void sendReport(byte[] report) {
|
||||
Message msg = mHandler.obtainMessage(MSG_SEND_REPORT, report);
|
||||
mHandler.sendMessageAtTime(msg, mEventTime);
|
||||
// if two messages are sent at identical time, they will be processed in order received
|
||||
mHandler.sendMessageAtTime(msg, mTimeToSend);
|
||||
}
|
||||
|
||||
public void addDelay(int delay) {
|
||||
mEventTime += delay;
|
||||
mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE);
|
||||
msg.setAsynchronous(true);
|
||||
mHandler.sendMessageAtTime(msg, mEventTime + 1);
|
||||
mHandler.sendMessageAtTime(msg, Math.max(SystemClock.uptimeMillis(), mTimeToSend) + 1);
|
||||
try {
|
||||
synchronized (mCond) {
|
||||
mCond.wait();
|
||||
@@ -111,8 +103,7 @@ public class Device {
|
||||
case MSG_OPEN_DEVICE:
|
||||
SomeArgs args = (SomeArgs) msg.obj;
|
||||
mPtr = nativeOpenDevice((String) args.arg1, args.argi1, args.argi2, args.argi3,
|
||||
(byte[]) args.arg2, getLooper().myQueue(), new DeviceCallback());
|
||||
nativeSendReport(mPtr, (byte[]) args.arg3);
|
||||
(byte[]) args.arg2, new DeviceCallback());
|
||||
pauseEvents();
|
||||
break;
|
||||
case MSG_SEND_REPORT:
|
||||
@@ -155,6 +146,7 @@ public class Device {
|
||||
}
|
||||
|
||||
public void onDeviceError() {
|
||||
Log.e(TAG, "Device error occurred, closing /dev/uhid");
|
||||
Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE);
|
||||
msg.setAsynchronous(true);
|
||||
msg.sendToTarget();
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.commands.hid;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonToken;
|
||||
import android.util.Log;
|
||||
@@ -91,7 +90,6 @@ public class Hid {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void process(Event e) {
|
||||
final int index = mDevices.indexOfKey(e.getId());
|
||||
if (index >= 0) {
|
||||
@@ -101,10 +99,16 @@ public class Hid {
|
||||
} else if (Event.COMMAND_REPORT.equals(e.getCommand())) {
|
||||
d.sendReport(e.getReport());
|
||||
} else {
|
||||
error("Unknown command \"" + e.getCommand() + "\". Ignoring event.");
|
||||
if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
|
||||
error("Device id=" + e.getId() + " is already registered. Ignoring event.");
|
||||
} else {
|
||||
error("Unknown command \"" + e.getCommand() + "\". Ignoring event.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
|
||||
registerDevice(e);
|
||||
} else {
|
||||
Log.e(TAG, "Unknown device id specified. Ignoring event.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +128,6 @@ public class Hid {
|
||||
}
|
||||
|
||||
private static void error(String msg, Exception e) {
|
||||
System.out.println(msg);
|
||||
Log.e(TAG, msg);
|
||||
if (e != null) {
|
||||
Log.e(TAG, Log.getStackTraceString(e));
|
||||
|
||||
@@ -42,7 +42,8 @@ interface IUiAutomationConnection {
|
||||
WindowContentFrameStats getWindowContentFrameStats(int windowId);
|
||||
void clearWindowAnimationFrameStats();
|
||||
WindowAnimationFrameStats getWindowAnimationFrameStats();
|
||||
void executeShellCommand(String command, in ParcelFileDescriptor fd);
|
||||
void executeShellCommand(String command, in ParcelFileDescriptor sink,
|
||||
in ParcelFileDescriptor source);
|
||||
void grantRuntimePermission(String packageName, String permission, int userId);
|
||||
void revokeRuntimePermission(String packageName, String permission, int userId);
|
||||
|
||||
|
||||
@@ -975,11 +975,11 @@ public final class UiAutomation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a shell command. This method returs a file descriptor that points
|
||||
* Executes a shell command. This method returns a file descriptor that points
|
||||
* to the standard output stream. The command execution is similar to running
|
||||
* "adb shell <command>" from a host connected to the device.
|
||||
* <p>
|
||||
* <strong>Note:</strong> It is your responsibility to close the retunred file
|
||||
* <strong>Note:</strong> It is your responsibility to close the returned file
|
||||
* descriptor once you are done reading.
|
||||
* </p>
|
||||
*
|
||||
@@ -1000,7 +1000,7 @@ public final class UiAutomation {
|
||||
sink = pipe[1];
|
||||
|
||||
// Calling out without a lock held.
|
||||
mUiAutomationConnection.executeShellCommand(command, sink);
|
||||
mUiAutomationConnection.executeShellCommand(command, sink, null);
|
||||
} catch (IOException ioe) {
|
||||
Log.e(LOG_TAG, "Error executing shell command!", ioe);
|
||||
} catch (RemoteException re) {
|
||||
@@ -1012,6 +1012,59 @@ public final class UiAutomation {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a shell command. This method returns two file descriptors,
|
||||
* one that points to the standard output stream (element at index 0), and one that points
|
||||
* to the standard input stream (element at index 1). The command execution is similar
|
||||
* to running "adb shell <command>" from a host connected to the device.
|
||||
* <p>
|
||||
* <strong>Note:</strong> It is your responsibility to close the returned file
|
||||
* descriptors once you are done reading/writing.
|
||||
* </p>
|
||||
*
|
||||
* @param command The command to execute.
|
||||
* @return File descriptors (out, in) to the standard output/input streams.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
public ParcelFileDescriptor[] executeShellCommandRw(String command) {
|
||||
synchronized (mLock) {
|
||||
throwIfNotConnectedLocked();
|
||||
}
|
||||
|
||||
ParcelFileDescriptor source_read = null;
|
||||
ParcelFileDescriptor sink_read = null;
|
||||
|
||||
ParcelFileDescriptor source_write = null;
|
||||
ParcelFileDescriptor sink_write = null;
|
||||
|
||||
try {
|
||||
ParcelFileDescriptor[] pipe_read = ParcelFileDescriptor.createPipe();
|
||||
source_read = pipe_read[0];
|
||||
sink_read = pipe_read[1];
|
||||
|
||||
ParcelFileDescriptor[] pipe_write = ParcelFileDescriptor.createPipe();
|
||||
source_write = pipe_write[0];
|
||||
sink_write = pipe_write[1];
|
||||
|
||||
// Calling out without a lock held.
|
||||
mUiAutomationConnection.executeShellCommand(command, sink_read, source_write);
|
||||
} catch (IOException ioe) {
|
||||
Log.e(LOG_TAG, "Error executing shell command!", ioe);
|
||||
} catch (RemoteException re) {
|
||||
Log.e(LOG_TAG, "Error executing shell command!", re);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(sink_read);
|
||||
IoUtils.closeQuietly(source_write);
|
||||
}
|
||||
|
||||
ParcelFileDescriptor[] result = new ParcelFileDescriptor[2];
|
||||
result[0] = source_read;
|
||||
result[1] = sink_write;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static float getDegreesForRotation(int value) {
|
||||
switch (value) {
|
||||
case Surface.ROTATION_90: {
|
||||
|
||||
@@ -36,9 +36,11 @@ import android.view.WindowAnimationFrameStats;
|
||||
import android.view.WindowContentFrameStats;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.IAccessibilityManager;
|
||||
import android.util.Log;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -55,6 +57,8 @@ import java.io.OutputStream;
|
||||
*/
|
||||
public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
|
||||
|
||||
private static final String TAG = "UiAutomationConnection";
|
||||
|
||||
private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1;
|
||||
|
||||
private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
|
||||
@@ -267,47 +271,95 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
public class Repeater implements Runnable {
|
||||
// Continuously read readFrom and write back to writeTo until EOF is encountered
|
||||
private final InputStream readFrom;
|
||||
private final OutputStream writeTo;
|
||||
public Repeater (InputStream readFrom, OutputStream writeTo) {
|
||||
this.readFrom = readFrom;
|
||||
this.writeTo = writeTo;
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
final byte[] buffer = new byte[8192];
|
||||
int readByteCount;
|
||||
while (true) {
|
||||
readByteCount = readFrom.read(buffer);
|
||||
if (readByteCount < 0) {
|
||||
break;
|
||||
}
|
||||
writeTo.write(buffer, 0, readByteCount);
|
||||
writeTo.flush();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("Error while reading/writing ", ioe);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(readFrom);
|
||||
IoUtils.closeQuietly(writeTo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeShellCommand(final String command, final ParcelFileDescriptor sink)
|
||||
throws RemoteException {
|
||||
public void executeShellCommand(final String command, final ParcelFileDescriptor sink,
|
||||
final ParcelFileDescriptor source) throws RemoteException {
|
||||
synchronized (mLock) {
|
||||
throwIfCalledByNotTrustedUidLocked();
|
||||
throwIfShutdownLocked();
|
||||
throwIfNotConnectedLocked();
|
||||
}
|
||||
final java.lang.Process process;
|
||||
|
||||
Thread streamReader = new Thread() {
|
||||
try {
|
||||
process = Runtime.getRuntime().exec(command);
|
||||
} catch (IOException exc) {
|
||||
throw new RuntimeException("Error running shell command '" + command + "'", exc);
|
||||
}
|
||||
|
||||
// Read from process and write to pipe
|
||||
final Thread readFromProcess;
|
||||
if (sink != null) {
|
||||
InputStream sink_in = process.getInputStream();;
|
||||
OutputStream sink_out = new FileOutputStream(sink.getFileDescriptor());
|
||||
|
||||
readFromProcess = new Thread(new Repeater(sink_in, sink_out));
|
||||
readFromProcess.start();
|
||||
} else {
|
||||
readFromProcess = null;
|
||||
}
|
||||
|
||||
// Read from pipe and write to process
|
||||
final Thread writeToProcess;
|
||||
if (source != null) {
|
||||
OutputStream source_out = process.getOutputStream();
|
||||
InputStream source_in = new FileInputStream(source.getFileDescriptor());
|
||||
|
||||
writeToProcess = new Thread(new Repeater(source_in, source_out));
|
||||
writeToProcess.start();
|
||||
} else {
|
||||
writeToProcess = null;
|
||||
}
|
||||
|
||||
Thread cleanup = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
java.lang.Process process = null;
|
||||
|
||||
try {
|
||||
process = Runtime.getRuntime().exec(command);
|
||||
|
||||
in = process.getInputStream();
|
||||
out = new FileOutputStream(sink.getFileDescriptor());
|
||||
|
||||
final byte[] buffer = new byte[8192];
|
||||
while (true) {
|
||||
final int readByteCount = in.read(buffer);
|
||||
if (readByteCount < 0) {
|
||||
break;
|
||||
}
|
||||
out.write(buffer, 0, readByteCount);
|
||||
if (writeToProcess != null) {
|
||||
writeToProcess.join();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("Error running shell command", ioe);
|
||||
} finally {
|
||||
if (process != null) {
|
||||
process.destroy();
|
||||
if (readFromProcess != null) {
|
||||
readFromProcess.join();
|
||||
}
|
||||
IoUtils.closeQuietly(out);
|
||||
IoUtils.closeQuietly(sink);
|
||||
} catch (InterruptedException exc) {
|
||||
Log.e(TAG, "At least one of the threads was interrupted");
|
||||
}
|
||||
};
|
||||
};
|
||||
streamReader.start();
|
||||
IoUtils.closeQuietly(sink);
|
||||
IoUtils.closeQuietly(source);
|
||||
process.destroy();
|
||||
}
|
||||
});
|
||||
cleanup.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user