I am dealing with an issue where a device called an aggregator board (AggBoard for short) becomes disconnected from an Android tablet, and upon re-connection it won't resume communication between endpoints.
Some background: I have been trying to refactor this Android app's Usb system. Initially, in order to transfer data between endpoints, they used the UsbDeviceConnection.bulkTransfer() method. I refactored it to use the UsbRequest.initialize(), UsbRequest.queue(), and then UsbDeviceConnection.requestWait() methods. When the Android program is booted on the tablet, my new methods work fine (perhaps even better), but when there is a USB disruption/disconnection from the AggBoard, once reconnected, it won't pick up where it left off. However, their old method, UsbDeviceConnection.bulkTransfer() DOES pick up communication upon reconnect.
This entire process was spurred by data packets consistently being dropped by the old bulktransfer() method, and we thought using the UsbRequest class might be an improvement. It appears this might be the case, but seeing as this product has to tendency to have its AggBoard become disconnected/disrupted, it is imperative that the new method allows the AggBoard to continue to deliver information upon reconnect.
Here is the code I refactored, along with the original method:
Original Method [bulkTransfer()] -
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbRequest;
import android.os.AsyncTask;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
public class PayloadTask extends AsyncTask<Payload, Void, Payload> {
private UsbInterface usbInterface;
private UsbDeviceConnection usbConnection;
private UsbEndpoint usbToAggBoard;
private UsbEndpoint usbFromAggBoard;
private AggBoard owner;
PayloadTask(AggBoard owner, UsbInterface intf, UsbDeviceConnection connection) {
usbConnection = connection;
usbInterface = intf;
/*synchronized (intf)*/ {
usbToAggBoard = intf.getEndpoint(1);
usbFromAggBoard = intf.getEndpoint(0);
}
this.owner = owner;
}
@Override
protected Payload doInBackground(Payload... payloads) {
if (payloads != null) {
if (payloads.length > 1) {
Log.d("PayloadTask", "There was more than one payload. Going to have to iterate.");
int i = 0;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (Payload payload : payloads) {
byte[] buffer = payload.getBytes();
while (AndroidUnityPlayerActivity.aggBoard == null)
{
try {
Log.d("PayloadTask", "Waiting for AndroidUnityPlayerActivity.aggboard to not be null");
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int sent = usbConnection.bulkTransfer(usbToAggBoard, buffer, buffer.length, 1500);
busyWaitMicros(20);
if (++i % 1024 == 0) {
Log.e(AndroidUnityPlayerActivity.TAG, new Integer(i / 1024 * 8).toString() + "KB sent");
}
AggBoard.percentInstalled = i * 100 / payloads.length;
// Log.w(AndroidUnityPlayerActivity.TAG, new Float((float)i /payloads.length).toString());
}
Log.d("PayloadTask", "Finished iterating over the payloads.");
// rk3188 doesn't seem to see the success/fail message.. so let's fake one :/
if (AggBoard.encryptedBytes != null) {
Log.d("PayloadTask", "Aggboard.encryptedBytes was null. Creating some kind of success response payload??");
byte[] response = new byte [] { (byte)Payload.BOOTLOAD_STATUS,
(byte)BootloadStatusPayload.AGGREGATOR,
(byte)BootloadStatusPayload.SUCCESS,
0, 0, 0, 0,
(byte)Payload.AGGREGATOR_TAIL };
if (AggBoard.percentInstalled < 100) {
response[2] = (byte)BootloadStatusPayload.ERROR ;
}
Log.d("PayloadTask", "Creating bootload status SUCCESS task");
return Payload.CreatePayload(response);
}
} else if (payloads.length == 1 && payloads[0] != null) {
Log.d("PayloadTask", "There was only one payload. Running it");
byte[] buffer = payloads[0].getBytes();
if (AndroidUnityPlayerActivity.aggBoard != null) {
Log.d("PayloadTask", "Only one payload, beginning bulk transfer");
usbConnection.bulkTransfer(usbToAggBoard, buffer, buffer.length, 100);
/*if (Payload.START_SENDING_DATA == buffer[0]) {
AndroidUnityPlayerActivity.hasAggBoard = true;
}*/
}
else{
Log.d("PayloadTask", "AndroidUnityPlayerActivity's aggboard was null. Can't perform bulk transfer of payload.");
}
}
} else {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
byte[] response = new byte[64];
int result = 0;
int tries = 0;
while (result < 1){
if (tries > 5) {
Log.d("PayloadTask", "PayloadTask Timed Out.");
return null;
} else if (isCancelled()) {
Log.w("PayloadTask", "PayloadTask Cancelled!");
return null;
}
/*synchronized (usbConnection)*/
result = usbConnection.bulkTransfer(usbFromAggBoard, response, response.length, 25);
tries += 1;
}
Log.i("PayloadTask", new Integer(response.length).toString() + " bytes received: ");
Log.d("PayloadTask", "doInBackground returning some payload from the aggboard.");
return Payload.CreatePayload(response);
}
@Override
protected void onPostExecute(Payload payload) {
owner.handlePayload(payload);
}
@Override
protected void onCancelled() {
Log.d("PayloadTask", "thread canceled");
super.onCancelled();
}
@Override
protected void onPreExecute() {
Log.d("PayloadTask", "started thread");
if (!usbConnection.claimInterface(usbInterface, true)) {
Log.w("PayloadTask", "Failed to claim interface!");
}
else{
Log.w("PayloadTask", "Claimed interface!");
}
}
public static void busyWaitMicros(long micros){
long waitUntil = System.nanoTime() + (micros * 1000);
while(waitUntil > System.nanoTime()){
;
}
}
}
Refactored Code [UsbRequest/requestWait()] -
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbRequest;
import android.os.AsyncTask;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
public class PayloadTask extends AsyncTask<Payload, Void, Payload> {
private UsbInterface usbInterface;
private UsbDeviceConnection usbConnection;
private UsbEndpoint usbToAggBoard;
private UsbEndpoint usbFromAggBoard;
private AggBoard owner;
PayloadTask(AggBoard owner, UsbInterface intf, UsbDeviceConnection connection) {
usbConnection = connection;
usbInterface = intf;
/*synchronized (intf)*/ {
usbToAggBoard = intf.getEndpoint(1);
usbFromAggBoard = intf.getEndpoint(0);
}
this.owner = owner;
}
@Override
protected Payload doInBackground(Payload... payloads) {
if (payloads != null) {
if (payloads.length > 1) {
Log.d("PayloadTask", "There was more than one payload. Going to have to iterate.");
int i = 0;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (Payload payload : payloads) {
byte[] buffer = payload.getBytes();
while (AndroidUnityPlayerActivity.aggBoard == null)
{
try {
Log.d("PayloadTask", "Waiting for AndroidUnityPlayerActivity.aggboard to not be null");
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
UsbRequest request = new UsbRequest();
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
byteBuffer.rewind();
try {
request.initialize(usbConnection, usbToAggBoard);
if (!request.queue(byteBuffer, buffer.length)) {
throw new IOException("Error queueing USB request.");
}
usbConnection.requestWait();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(usbConnection != null) {
int sent = request.hashCode();
request.close();
}
else{
Log.d("PayloadTask", "Request not received by usbConnection");
}
}
busyWaitMicros(20);
if (++i % 1024 == 0) {
Log.e(AndroidUnityPlayerActivity.TAG, new Integer(i / 1024 * 8).toString() + "KB sent");
}
AggBoard.percentInstalled = i * 100 / payloads.length;
// Log.w(AndroidUnityPlayerActivity.TAG, new Float((float)i /payloads.length).toString());
}
Log.d("PayloadTask", "Finished iterating over the payloads.");
// rk3188 doesn't seem to see the success/fail message.. so let's fake one :/
if (AggBoard.encryptedBytes != null) {
Log.d("PayloadTask", "Aggboard.encryptedBytes was null. Creating some kind of success response payload??");
byte[] response = new byte [] { (byte)Payload.BOOTLOAD_STATUS,
(byte)BootloadStatusPayload.AGGREGATOR,
(byte)BootloadStatusPayload.SUCCESS,
0, 0, 0, 0,
(byte)Payload.AGGREGATOR_TAIL };
if (AggBoard.percentInstalled < 100) {
response[2] = (byte)BootloadStatusPayload.ERROR ;
}
Log.d("PayloadTask", "Creating bootload status SUCCESS task");
return Payload.CreatePayload(response);
}
} else if (payloads.length == 1 && payloads[0] != null) {
Log.d("PayloadTask", "There was only one payload. Running it");
byte[] buffer = payloads[0].getBytes();
if (AndroidUnityPlayerActivity.aggBoard != null) {
Log.d("PayloadTask", "Only one payload, beginning bulk transfer");
UsbRequest request = new UsbRequest();
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
byteBuffer.rewind();
try {
request.initialize(usbConnection, usbToAggBoard);
if (!request.queue(byteBuffer, buffer.length)) {
throw new IOException("Error queueing USB request.");
}
usbConnection.requestWait();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(usbConnection != null) {
request.close();
}
else{
Log.d("PayloadTask", "Request not received by usbConnection");
}
}
/*if (Payload.START_SENDING_DATA == buffer[0]) {
AndroidUnityPlayerActivity.hasAggBoard = true;
}*/
}
else{
Log.d("PayloadTask", "AndroidUnityPlayerActivity's aggboard was null. Can't perform bulk transfer of payload.");
}
}
} else {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
byte[] response = new byte[64];
int result = 0;
int tries = 0;
while (result < 1){
if (tries > 5) {
Log.d("PayloadTask", "PayloadTask Timed Out.");
return null;
} else if (isCancelled()) {
Log.w("PayloadTask", "PayloadTask Cancelled!");
return null;
}
/*synchronized (usbConnection)*/
if (usbConnection != null) {
UsbRequest request = new UsbRequest();
ByteBuffer byteBuffer = ByteBuffer.wrap(response);
byteBuffer.rewind();
try {
request.initialize(usbConnection, usbFromAggBoard);
if (!request.queue(byteBuffer, response.length)) {
throw new IOException("Error queueing USB request.");
}
usbConnection.requestWait();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(usbConnection != null) {
result = request.hashCode();
request.close();
}
else{
Log.d("PayloadTask", "Request not received by usbConnection");
}
}
}
tries += 1;
}
Log.i("PayloadTask", new Integer(response.length).toString() + " bytes received: ");
Log.d("PayloadTask", "doInBackground returning some payload from the aggboard.");
return Payload.CreatePayload(response);
}
@Override
protected void onPostExecute(Payload payload) {
owner.handlePayload(payload);
}
@Override
protected void onCancelled() {
Log.d("PayloadTask", "thread canceled");
super.onCancelled();
}
@Override
protected void onPreExecute() {
Log.d("PayloadTask", "started thread");
if (!usbConnection.claimInterface(usbInterface, true)) {
Log.w("PayloadTask", "Failed to claim interface!");
}
else{
Log.w("PayloadTask", "Claimed interface!");
}
}
public static void busyWaitMicros(long micros){
long waitUntil = System.nanoTime() + (micros * 1000);
while(waitUntil > System.nanoTime()){
;
}
}
}