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:
Siarhei Vishniakou
2017-03-31 17:23:39 -07:00
parent 97208056fe
commit 55656e4cb9
10 changed files with 385 additions and 115 deletions

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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));
}

View File

@@ -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;
};

View File

@@ -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();

View File

@@ -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));

View File

@@ -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);

View File

@@ -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: {

View File

@@ -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