blob: 173531adb13d2e20feb2b71dc18a8ed86f473902 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.input;
import static android.os.FileUtils.closeQuietly;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.hardware.input.InputManager;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Represents a virtual HID device registered through /dev/uhid.
*/
public final class HidDevice implements InputManager.InputDeviceListener {
private static final String TAG = "HidDevice";
// hid executable expects "-" argument to read from stdin instead of a file
private static final String HID_COMMAND = "hid -";
private final int mId; // // initialized from the json file
private OutputStream mOutputStream;
private Instrumentation mInstrumentation;
private volatile CountDownLatch mDeviceAddedSignal; // to wait for onInputDeviceAdded signal
public HidDevice(Instrumentation instrumentation, int deviceId, String registerCommand) {
mInstrumentation = instrumentation;
setupPipes();
mInstrumentation.runOnMainSync(new Runnable(){
@Override
public void run() {
InputManager inputManager =
mInstrumentation.getContext().getSystemService(InputManager.class);
inputManager.registerInputDeviceListener(HidDevice.this, null);
}
});
mId = deviceId;
registerInputDevice(registerCommand);
}
/**
* Register an input device. May cause a failure if the device added notification
* is not received within the timeout period
*
* @param registerCommand The full json command that specifies how to register this device
*/
private void registerInputDevice(String registerCommand) {
mDeviceAddedSignal = new CountDownLatch(1);
writeHidCommands(registerCommand.getBytes());
try {
// Found that in kernel 3.10, the device registration takes a very long time
// The wait can be decreased to 2 seconds after kernel 3.10 is no longer supported
mDeviceAddedSignal.await(20L, TimeUnit.SECONDS);
if (mDeviceAddedSignal.getCount() != 0) {
throw new RuntimeException("Did not receive device added notification in time");
}
} catch (InterruptedException ex) {
throw new RuntimeException(
"Unexpectedly interrupted while waiting for device added notification.");
}
// Even though the device has been added, it still may not be ready to process the events
// right away. This seems to be a kernel bug.
// Add a small delay here to ensure device is "ready".
SystemClock.sleep(500);
}
/**
* Add a delay between processing events.
*
* @param milliSeconds The delay in milliseconds.
*/
public void delay(int milliSeconds) {
JSONObject json = new JSONObject();
try {
json.put("command", "delay");
json.put("id", mId);
json.put("duration", milliSeconds);
} catch (JSONException e) {
throw new RuntimeException(
"Could not create JSON object to delay " + milliSeconds + " milliseconds");
}
writeHidCommands(json.toString().getBytes());
}
/**
* Send a HID report to the device. The report should follow the report descriptor
* that was specified during device registration.
* An example report:
* String report = "[0x01, 0x00, 0x00, 0x02]";
*
* @param report The report to send (a JSON-formatted array of hex)
*/
public void sendHidReport(String report) {
JSONObject json = new JSONObject();
try {
json.put("command", "report");
json.put("id", mId);
json.put("report", new JSONArray(report));
} catch (JSONException e) {
throw new RuntimeException("Could not process HID report: " + report);
}
writeHidCommands(json.toString().getBytes());
}
/**
* Close the device, which would cause the associated input device to unregister.
*/
public void close() {
closeQuietly(mOutputStream);
}
private void setupPipes() {
UiAutomation ui = mInstrumentation.getUiAutomation();
ParcelFileDescriptor[] pipes = ui.executeShellCommandRw(HID_COMMAND);
mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pipes[1]);
closeQuietly(pipes[0]); // hid command is write-only
}
private void writeHidCommands(byte[] bytes) {
try {
mOutputStream.write(bytes);
mOutputStream.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// InputManager.InputDeviceListener functions
@Override
public void onInputDeviceAdded(int deviceId) {
mDeviceAddedSignal.countDown();
}
@Override
public void onInputDeviceChanged(int deviceId) {
}
@Override
public void onInputDeviceRemoved(int deviceId) {
}
}