summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSiarhei Vishniakou <svv@google.com>2017-03-31 17:23:39 -0700
committerSiarhei Vishniakou <svv@google.com>2017-05-12 21:01:02 +0000
commit55656e4cb923d1aeef4445b3aaa3a6ce3f35c90a (patch)
tree323e1b593b44221d81b24b43c7fbe237a6710396
parent97208056febe682001bef70988105dcdb5d52316 (diff)
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
-rw-r--r--api/test-current.txt1
-rw-r--r--cmds/hid/README.md145
-rw-r--r--cmds/hid/jni/Android.mk9
-rw-r--r--cmds/hid/jni/com_android_commands_hid_Device.cpp125
-rw-r--r--cmds/hid/jni/com_android_commands_hid_Device.h9
-rw-r--r--cmds/hid/src/com/android/commands/hid/Device.java26
-rw-r--r--cmds/hid/src/com/android/commands/hid/Hid.java13
-rw-r--r--core/java/android/app/IUiAutomationConnection.aidl3
-rw-r--r--core/java/android/app/UiAutomation.java59
-rw-r--r--core/java/android/app/UiAutomationConnection.java110
10 files changed, 385 insertions, 115 deletions
diff --git a/api/test-current.txt b/api/test-current.txt
index 84fe4c5c4f77..586b22bf4c6e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -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();
diff --git a/cmds/hid/README.md b/cmds/hid/README.md
new file mode 100644
index 000000000000..7e22d08eeaeb
--- /dev/null
+++ b/cmds/hid/README.md
@@ -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 \ No newline at end of file
diff --git a/cmds/hid/jni/Android.mk b/cmds/hid/jni/Android.mk
index d41d39d27f5b..86f4e012a943 100644
--- a/cmds/hid/jni/Android.mk
+++ b/cmds/hid/jni/Android.mk
@@ -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
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index 1ea33ced7bbf..107dc863ef66 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -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));
}
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h
index 6c5899eeb861..149456d8c10d 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.h
+++ b/cmds/hid/jni/com_android_commands_hid_Device.h
@@ -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;
};
diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java
index dbe883bd1136..8c52a8ed1e09 100644
--- a/cmds/hid/src/com/android/commands/hid/Device.java
+++ b/cmds/hid/src/com/android/commands/hid/Device.java
@@ -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();
diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java
index 976a78249bec..234e47f12dee 100644
--- a/cmds/hid/src/com/android/commands/hid/Hid.java
+++ b/cmds/hid/src/com/android/commands/hid/Hid.java
@@ -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));
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 7640e75ba153..b26117d3d31c 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -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);
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 18e7599e1846..c99de5ddb776 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -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: {
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 9960df63b537..5e414b837f79 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -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() {
- public void run() {
- InputStream in = null;
- OutputStream out = null;
- java.lang.Process process = null;
+ 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() {
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");
+ }
+ IoUtils.closeQuietly(sink);
+ IoUtils.closeQuietly(source);
+ process.destroy();
}
- };
- };
- streamReader.start();
+ });
+ cleanup.start();
}
@Override