Search code examples
javaandroidusbtransferlibusb

Android receive usb bulk transfer


I have created a java app that sets itself up as a USB accessory and sends some data via a bulk transfer.

The Android device should see this and print the data to a TextView. At the moment I have it so that the Android device sees the connection of the accessory and it launches the app the bulk transfer however fails with the following.

org.usb4java.LibUsbException: USB error 5: Bulk write error!: Entity not found
    at com.xxx.xxx.AccessoryTest.writeAndRead(AccessoryTest.java:60)
    at com.xxx.xxx.AccessoryTest.main(AccessoryTest.java:37)

Any suggestions are welcome!

I have another utility which reports the endpoint information of the Android device as

  Endpoint Descriptor:
    bLength                  7
    bDescriptorType          5
    bEndpointAddress      0x81  EP 1 IN
    bmAttributes             2
      Transfer Type             Bulk
      Synch Type                None
      Usage Type                Data
    wMaxPacketSize         512
    bInterval                0
    extralen                 0
    extra:

  Endpoint Descriptor:
    bLength                  7
    bDescriptorType          5
    bEndpointAddress      0x02  EP 2 OUT
    bmAttributes             2
      Transfer Type             Bulk
      Synch Type                None
      Usage Type                Data
    wMaxPacketSize         512
    bInterval                0
    extralen                 0
    extra:

I'm attempting to write to the 0x02 endpoint

Android code

public class MainActivity extends AppCompatActivity implements Runnable {

    private UsbManager manager;
    private UsbAccessory accessory;
    private ParcelFileDescriptor accessoryFileDescriptor;
    private FileInputStream accessoryInput;
    private FileOutputStream accessoryOutput;

    private TextView questionTV;

    private final BroadcastReceiver usbBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(action)) {
                synchronized (this) {
                    accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
                }
            } else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
                UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
                if (accessory != null) {
                    // call your method that cleans up and closes communication with the accessory
                    try {
                        accessoryFileDescriptor.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    };

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

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

        IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
        filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
        registerReceiver(usbBroadcastReceiver, filter);

        if (getLastNonConfigurationInstance() != null)
        {
            accessory = (UsbAccessory) getLastNonConfigurationInstance();
            openAccessory();
        }

        setContentView(R.layout.activity_main);
    }

    private void openAccessory() {
        accessoryFileDescriptor = manager.openAccessory(accessory);
        if(accessoryFileDescriptor != null) {
            FileDescriptor fd = accessoryFileDescriptor.getFileDescriptor();
            accessoryInput = new FileInputStream(fd);
            accessoryOutput = new FileOutputStream(fd);
            Thread thread = new Thread(null, this, "AccessoryThread");
            thread.start();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void run() {
        int ret = 0;
        byte[] buffer = new byte[51];
        int bufferUsed = 0;

        while(ret >= 0) {
            try {
                ret = accessoryInput.read(buffer);
            } catch (IOException e) {
                Log.e("MainActivity", "Exception in USB accessory input reading", e);
                break;
            }
        }

        String question = new String(buffer);

        LinearLayout layout= (LinearLayout) findViewById(R.id.layout);

        questionTV = new TextView(this);
        questionTV.setText(question);

        layout.addView(questionTV);
    }
}

Java app code

import java.nio.ByteBuffer;
import java.nio.IntBuffer;

import org.usb4java.BufferUtils;
import org.usb4java.Context;
import org.usb4java.Device;
import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceHandle;
import org.usb4java.DeviceList;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;

public class AccessoryTest {

    private final static byte REQUEST_TYPE_READ = (byte) 0xC0;
    private final static byte END_POINT_IN = (byte) 0x81;
    private final static byte END_POINT_OUT = (byte) 0x02;

    private final static short NEXUS7_VENDORID = (short) 0x18D1;
    private final static short NEXUS7_PRODUCTID = (short) 0x4EE1;

    private static Context context;
    private static Device device;
    private static DeviceHandle handle;

    public static void main(String[] args) {
        try {
            init();
            int result = setupAccessory("PCHost", "PCHost1", "Description", "1.0", "http://www.mycompany.com",
                    "SerialNumber");

            if (result != LibUsb.SUCCESS)
                throw new LibUsbException("Unable to setup Accessory", result);

            writeAndRead();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            LibUsb.releaseInterface(handle, 0);
            LibUsb.resetDevice(handle);
            LibUsb.close(handle);
            LibUsb.exit(context);
        }

    }

    private static void writeAndRead() {
        String question = "Hello Android I'll be your host today, how are you?";
        byte[] questionBuffer = question.getBytes();
        ByteBuffer questionData = BufferUtils.allocateByteBuffer(questionBuffer.length);
        IntBuffer transferred = IntBuffer.allocate(1);

        int result = 0;
        //THIS IS THE PART WHERE IT FAILS!
        result = LibUsb.bulkTransfer(handle, END_POINT_OUT, questionData, transferred, 5000);

        if(result < 0) {
            throw new LibUsbException("Bulk write error!", result);
        }
    }

    private static void init() {
        context = new Context();
        int result = LibUsb.init(context);
        if (result != LibUsb.SUCCESS)
            throw new LibUsbException("Unable to initialize libusb.", result);

        device = findDevice(NEXUS7_VENDORID);

        handle = new DeviceHandle();

        result = LibUsb.open(device, handle);
        if (result < 0)
            throw new LibUsbException("Unable to open USB device", result);

        result = LibUsb.claimInterface(handle, 0);
        if (result != LibUsb.SUCCESS)
            throw new LibUsbException("Unable to claim interface", result);
    }

    private static Device findDevice(short vendorId) {
        // Read the USB device list
        DeviceList list = new DeviceList();
        int result = LibUsb.getDeviceList(null, list);
        if (result < 0)
            throw new LibUsbException("Unable to get device list", result);

        try {
            // Iterate over all devices and scan for the right one
            for (Device device : list) {
                DeviceDescriptor descriptor = new DeviceDescriptor();
                result = LibUsb.getDeviceDescriptor(device, descriptor);
                if (result != LibUsb.SUCCESS)
                    throw new LibUsbException("Unable to read device descriptor", result);
                if (descriptor.idVendor() == vendorId)
                    return device;
            }
        } finally {
            // Ensure the allocated device list is freed
            LibUsb.freeDeviceList(list, true);
        }

        // Device not found
        return null;
    }

    private static int setupAccessory(String vendor, String model, String description, String version, String url,
            String serial) throws LibUsbException {

        int response = 0;

        // Setup setup token
        response = transferSetupPacket((short) 2, REQUEST_TYPE_READ, (byte) 51);

        // Setup data packet
        response = transferAccessoryDataPacket(vendor, (short) 0);
        response = transferAccessoryDataPacket(model, (short) 1);
        response = transferAccessoryDataPacket(description, (short) 2);
        response = transferAccessoryDataPacket(version, (short) 3);
        response = transferAccessoryDataPacket(url, (short) 4);
        response = transferAccessoryDataPacket(serial, (short) 5);

        // Setup handshake packet
        response = transferSetupPacket((short) 0, (byte) (LibUsb.REQUEST_TYPE_VENDOR | LibUsb.ENDPOINT_OUT), (byte) 53);

        LibUsb.releaseInterface(handle, 0);

        return response;
    }

    private static int transferSetupPacket(short bufferLength, byte requestType, byte request) throws LibUsbException {
        int response = 0;
        byte[] bytebuff = new byte[bufferLength];
        ByteBuffer data = BufferUtils.allocateByteBuffer(bytebuff.length);
        data.put(bytebuff);

        final short wValue = 0;
        final short wIndex = 0;
        final long timeout = 1000;

        data.rewind();
        response = LibUsb.controlTransfer(handle, requestType, request, wValue, wIndex,
                data, timeout);

        if(response < 0)
            throw new LibUsbException("Unable to transfer setup packet ", response);

        return response;
    }

    private static int transferAccessoryDataPacket(String param, short index) {
        int response;
        byte[] byteArray = param.getBytes();
        ByteBuffer data = BufferUtils.allocateByteBuffer(byteArray.length);
        data.put(byteArray);
        final byte bRequest = (byte) 52;
        final short wValue = 0;
        final long timeout = 0;
        response = LibUsb.controlTransfer(handle, LibUsb.REQUEST_TYPE_VENDOR, bRequest, wValue, index,
                data, timeout);
        if(response < 0)
            throw new LibUsbException("Unable to control transfer.", response);
        return response;
    }

}

AndroidManifest.xml

<uses-feature android:name="android.hardware.usb.accessory" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <uses-library android:name="com.android.future.usb.accessory" />
    <activity
        android:name=".MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
        </intent-filter>

        <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
            android:resource="@xml/accessory_filter" />
    </activity>
</application>


Solution

  • The issue was according to the AOP standards once an Android device has entered Accessory mode it's VID and PID change

    Therefore after setting up the accessory the device handle and the interface need to be reclaimed.