Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Low-level USB API on Android at NDK level to use on Qt Android

I need to interact with an HID device on Android. The problem is that i'm using Qt Android, and i'm not using Java UsbManager classes.

Is there any C lib which I can link against in order to communicate with an HID device on Android without having to work with the Java API?

I have found this:

http://source.android.com/devices/reference/bt__hh_8h_source.html

Which seems to be an header that defines HID communication but I can't find the associated lib. Any ideas?

Thanks in advance

like image 931
Nuno Santos Avatar asked Dec 08 '22 09:12

Nuno Santos


1 Answers

I have found a way of doing this. In my case, i'm developing a control panel for a device and I need it to work on every device without having the device rooted.

Basically I'm using UsbManager to find and grab the device. I then open the device and call getFileDescriptor() method from UsbDeviceConnection. I then pass this int to the native code side. From there I can easily obtain data from the device using any kind of requests without having to pass data from native code to java and vice versa thru JNI which is slow and slow dying work.

The most tricky part is actually doing the ioctl calls which need to have a special format.

Part of this codes are already well exemplified on libusb source code since it's the way they implement libsub calls to linux kernel.

If anyone knows an even better solution to this problem just let me know.

Android activity code with USBManager hooking:

public class MyActivity extends QtActivity
{
    private static MyActivity m_instance;
    private UsbAccessory accessory;
    private String TAG = "TAG";
    private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
    private PendingIntent mPermissionIntent;
    private UsbManager manager;
    private UsbDeviceConnection connection;
    private HashMap<Integer, Integer> connectedDevices;

    public MyActivity()
    {
        m_instance = this;

        connectedDevices = new HashMap<Integer, Integer>();
    }

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        manager = (UsbManager) getSystemService(Context.USB_SERVICE);

        registerReceiver(usbManagerBroadcastReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED));
        registerReceiver(usbManagerBroadcastReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED));
        registerReceiver(usbManagerBroadcastReceiver, new IntentFilter(ACTION_USB_PERMISSION));

        mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);

        final Handler handler = new Handler();

        handler.postDelayed(new Runnable()
        {
            @Override
            public void run()
            {
                checkForDevices();
            }
        }, 1000);
    }

    @Override
    public void onDestroy()
    {
        super.onDestroy();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        super.onActivityResult(requestCode, resultCode, data);
    }

    private static native void notifyDeviceAttached(int fd);
    private static native void notifyDeviceDetached(int fd);

    private final BroadcastReceiver usbManagerBroadcastReceiver = new BroadcastReceiver()
    {
        public void onReceive(Context context, Intent intent)
        {
            try
            {
                String action = intent.getAction();

                Log.d(TAG, "INTENT ACTION: " + action);

                if (ACTION_USB_PERMISSION.equals(action))
                {
                    Log.d(TAG, "onUsbPermission");

                    synchronized (this)
                    {
                        UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                        if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false))
                        {
                            if(device != null)
                            {
                                int fd = connectToDevice(device);
                                Log.d(TAG,"device file descriptor: " + fd);
                                notifyDeviceAttached(fd);
                            }
                        }
                        else
                        {
                            Log.d(TAG, "permission denied for device " + device);
                        }
                    }
                }

                if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action))
                {
                    Log.d(TAG, "onDeviceConnected");

                    synchronized(this)
                    {
                        UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                        if (device != null)
                        {
                            manager.requestPermission(device, mPermissionIntent);
                        }
                    }
                }

                if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action))
                {
                    Log.d(TAG, "onDeviceDisconnected");

                    synchronized(this)
                    {
                        UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                        int fd = connectedDevices.get(device.getDeviceId());

                        Log.d(TAG, "device: " + device.getDeviceId() + " disconnected. fd: " + fd);

                        notifyDeviceDetached(fd);

                        connectedDevices.remove(device.getDeviceId());
                    }
                }
            }
            catch(Exception e)
            {
                Log.d(TAG, "Exception: " + e);
            }
        }
    };

    private int connectToDevice(UsbDevice device)
    {
        connection = manager.openDevice(device);
        // if we make this, kernel driver will be disconnected
        connection.claimInterface(device.getInterface(0), true);

        Log.d(TAG, "inserting device with id: " + device.getDeviceId() + " and file descriptor: " + connection.getFileDescriptor());
        connectedDevices.put(device.getDeviceId(), connection.getFileDescriptor());

        return connection.getFileDescriptor();
    }

    private void checkForDevices()
    {
        HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
        Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();

        while(deviceIterator.hasNext())
        {
            UsbDevice device = deviceIterator.next();

            if (device.getVendorId()==VID && device.getProductId()==PID)
            {
                Log.d(TAG, "Found a device: " + device);

                manager.requestPermission(device, mPermissionIntent);
            }
        }
    }
}

When the device with the wished VID and PID is connected or disconnected the native calls notifyDeviceAttached(int fd) and notifyDeviceDetached(int fd) are called sending the file descriptor of the device to the native side. In my case I instantiate a class of type Device. At this point the device is already open and you can start doing calls to it. In Linux you make ioctl calls just like libusb does. You can see the code below for getFeature and setFeature. If you need anything else than this you can look at libusb source code.

C++ native side code:

#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>

#include <QDebug>
#include <QElapsedTimer>

static inline uint16_t cpu_to_le16(const uint16_t x)
{
    union
    {
        uint8_t  b8[2];
        uint16_t b16;
    } _tmp;

    _tmp.b8[1] = (uint8_t) (x >> 8);
    _tmp.b8[0] = (uint8_t) (x & 0xff);

    return _tmp.b16;
}

struct usbdevfs_ctrltransfer
{
    unsigned char bRequestType;
    unsigned char bRequest;
    unsigned short wValue;
    unsigned short wIndex;
    unsigned short wLength;
    unsigned int timeout;
    void *data;
};

Device::Device(int fileDescriptor, QObject *parent) :
    fd(fileDescriptor)
{
}

int Device::getFeature(unsigned char reportId, unsigned char *buffer, int length)
{
    struct usbdevfs_ctrltransfer data;

    data.bRequestType = (0x01 << 5)|0x01|0x80;
    data.bRequest = 0x01;
    data.wValue = cpu_to_le16((3 << 8) | reportId);
    data.wIndex = cpu_to_le16(0);
    data.wLength = cpu_to_le16(length);
    data.data = buffer;
    data.timeout = 1000;

    int res = ioctl(fd, _IOWR('U', 0, struct usbdevfs_ctrltransfer), &data);

    if (res<0)
    {
        qDebug() << "error: " << strerror(errno);
    }

    return res;
}

int Device::setFeature(unsigned char reportId, unsigned char *buffer, int length)
{
    struct usbdevfs_ctrltransfer data;

    data.bRequestType = (0x01 << 5)|0x01|0x00;
    data.bRequest = 0x09;
    data.wValue = cpu_to_le16((3 << 8) | reportId);
    data.wIndex = cpu_to_le16(0);
    data.wLength = cpu_to_le16(length);
    data.data = buffer;
    data.timeout = 1000;

    int res = ioctl(fd, _IOWR('U', 0, struct usbdevfs_ctrltransfer), &data);

    if (res<0)
    {
        qDebug() << "error: " << strerror(errno);
    }

    return res;
}

Regards,

Nuno Santos

like image 93
Nuno Santos Avatar answered Feb 22 '23 23:02

Nuno Santos