diff --git a/api/current.txt b/api/current.txt index 4c982638e3c6e..b39fcf8254a3a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -14835,10 +14835,11 @@ package android.hardware.usb { ctor public UsbRequest(); method public boolean cancel(); method public void close(); + method public boolean enqueue(java.nio.ByteBuffer); method public java.lang.Object getClientData(); method public android.hardware.usb.UsbEndpoint getEndpoint(); method public boolean initialize(android.hardware.usb.UsbDeviceConnection, android.hardware.usb.UsbEndpoint); - method public boolean queue(java.nio.ByteBuffer, int); + method public deprecated boolean queue(java.nio.ByteBuffer, int); method public void setClientData(java.lang.Object); } diff --git a/api/system-current.txt b/api/system-current.txt index ce115d85e6ad3..d73dbe9f7731e 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -16045,10 +16045,11 @@ package android.hardware.usb { ctor public UsbRequest(); method public boolean cancel(); method public void close(); + method public boolean enqueue(java.nio.ByteBuffer); method public java.lang.Object getClientData(); method public android.hardware.usb.UsbEndpoint getEndpoint(); method public boolean initialize(android.hardware.usb.UsbDeviceConnection, android.hardware.usb.UsbEndpoint); - method public boolean queue(java.nio.ByteBuffer, int); + method public deprecated boolean queue(java.nio.ByteBuffer, int); method public void setClientData(java.lang.Object); } diff --git a/api/test-current.txt b/api/test-current.txt index 92e5776a5cc2b..fbd1315397e34 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -14852,10 +14852,11 @@ package android.hardware.usb { ctor public UsbRequest(); method public boolean cancel(); method public void close(); + method public boolean enqueue(java.nio.ByteBuffer); method public java.lang.Object getClientData(); method public android.hardware.usb.UsbEndpoint getEndpoint(); method public boolean initialize(android.hardware.usb.UsbDeviceConnection, android.hardware.usb.UsbEndpoint); - method public boolean queue(java.nio.ByteBuffer, int); + method public deprecated boolean queue(java.nio.ByteBuffer, int); method public void setClientData(java.lang.Object); } diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java index ca2b1f87d8735..ffd6ec32fccce 100644 --- a/core/java/android/hardware/usb/UsbDeviceConnection.java +++ b/core/java/android/hardware/usb/UsbDeviceConnection.java @@ -265,8 +265,6 @@ public class UsbDeviceConnection { * {@link android.hardware.usb.UsbRequest#getEndpoint} and {@link * android.hardware.usb.UsbRequest#getClientData} can be useful in determining how to process * the result of this function.

- *

Position and array offset of the request's buffer are ignored and assumed to be 0. The - * position will be set to the number of bytes read/written.

* * @return a completed USB request, or null if an error occurred * @@ -291,8 +289,6 @@ public class UsbDeviceConnection { * {@link android.hardware.usb.UsbRequest#getEndpoint} and {@link * android.hardware.usb.UsbRequest#getClientData} can be useful in determining how to process * the result of this function.

- *

Position and array offset of the request's buffer are ignored and assumed to be 0. The - * position will be set to the number of bytes read/written.

*

Android processes {@link UsbRequest UsbRequests} asynchronously. Hence it is not * guaranteed that {@link #requestWait(int) requestWait(0)} returns a request that has been * queued right before even if the request could have been processed immediately.

@@ -307,7 +303,7 @@ public class UsbDeviceConnection { * {@link UsbRequest#queue(ByteBuffer, int)} */ public UsbRequest requestWait(int timeout) { - timeout = Preconditions.checkArgumentNonnegative(timeout); + timeout = Preconditions.checkArgumentNonnegative(timeout, "timeout"); UsbRequest request = native_request_wait(timeout); if (request != null) { diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java index 9f7592f880e6c..badb344aabadc 100644 --- a/core/java/android/hardware/usb/UsbRequest.java +++ b/core/java/android/hardware/usb/UsbRequest.java @@ -16,8 +16,10 @@ package android.hardware.usb; -import android.util.Log; +import android.annotation.Nullable; + import com.android.internal.util.Preconditions; + import dalvik.system.CloseGuard; import java.nio.ByteBuffer; @@ -38,13 +40,18 @@ public class UsbRequest { private static final String TAG = "UsbRequest"; + // From drivers/usb/core/devio.c + private static final int MAX_USBFS_BUFFER_SIZE = 16384; + // used by the JNI code private long mNativeContext; private UsbEndpoint mEndpoint; - // for temporarily saving current buffer across queue and dequeue + /** The buffer that is currently being read / written */ private ByteBuffer mBuffer; + + /** The amount of data to read / write when using {@link #queue} */ private int mLength; // for client use @@ -53,8 +60,21 @@ public class UsbRequest { // Prevent the connection from being finalized private UsbDeviceConnection mConnection; + /** Whether this buffer was {@link #enqueue enqueued (new behavior)} or {@link #queue queued + * (deprecared behavior)}. */ + private boolean mIsUsingEnqueue; + + /** Temporary buffer than might be used while buffer is enqueued */ + private ByteBuffer mTempBuffer; + private final CloseGuard mCloseGuard = CloseGuard.get(); + /** + * Lock for queue, enqueue and dequeue, so a queue operation can be finished by a dequeue + * operation on a different thread. + */ + private final Object mLock = new Object(); + public UsbRequest() { } @@ -67,7 +87,7 @@ public class UsbRequest { */ public boolean initialize(UsbDeviceConnection connection, UsbEndpoint endpoint) { mEndpoint = endpoint; - mConnection = Preconditions.checkNotNull(connection); + mConnection = Preconditions.checkNotNull(connection, "connection"); boolean wasInitialized = native_init(connection, endpoint.getAddress(), endpoint.getAttributes(), endpoint.getMaxPacketSize(), endpoint.getInterval()); @@ -140,54 +160,164 @@ public class UsbRequest { * Queues the request to send or receive data on its endpoint. *

For OUT endpoints, the given buffer data will be sent on the endpoint. For IN endpoints, * the endpoint will attempt to read the given number of bytes into the specified buffer. If the - * queueing operation is successful, we return true and the result will be returned via {@link - * android.hardware.usb.UsbDeviceConnection#requestWait}

+ * queueing operation is successful, return true. The result will be returned via + * {@link UsbDeviceConnection#requestWait}

* * @param buffer the buffer containing the bytes to write, or location to store the results of a * read. Position and array offset will be ignored and assumed to be 0. Limit and - * capacity will be ignored. + * capacity will be ignored. Once the request + * {@link UsbDeviceConnection#requestWait() is processed} the position will be set + * to the number of bytes read/written. * @param length number of bytes to read or write. * * @return true if the queueing operation succeeded + * + * @deprecated Use {@link #enqueue(ByteBuffer)} instead. */ + @Deprecated public boolean queue(ByteBuffer buffer, int length) { boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); - - // save our buffer for when the request has completed - mBuffer = buffer; - mLength = length; - - // Note: On a buffer slice we lost the capacity information about the underlying buffer, - // hence we cannot check if the access would be a data leak/memory corruption. - boolean result; - if (buffer.isDirect()) { - result = native_queue_direct(buffer, length, out); - } else if (buffer.hasArray()) { - result = native_queue_array(buffer.array(), length, out); - } else { - throw new IllegalArgumentException("buffer is not direct and has no array"); - } - if (!result) { - mBuffer = null; - mLength = 0; + + synchronized (mLock) { + // save our buffer for when the request has completed + mBuffer = buffer; + mLength = length; + + // Note: On a buffer slice we lost the capacity information about the underlying buffer, + // hence we cannot check if the access would be a data leak/memory corruption. + + if (buffer.isDirect()) { + result = native_queue_direct(buffer, length, out); + } else if (buffer.hasArray()) { + result = native_queue_array(buffer.array(), length, out); + } else { + throw new IllegalArgumentException("buffer is not direct and has no array"); + } + if (!result) { + mBuffer = null; + mLength = 0; + } } + return result; } + /** + * Queues the request to send or receive data on its endpoint. + * + *

For OUT endpoints, the remaining bytes of the buffer will be sent on the endpoint. For IN + * endpoints, the endpoint will attempt to fill the remaining bytes of the buffer. If the + * queueing operation is successful, return true. The result will be returned via + * {@link UsbDeviceConnection#requestWait}

+ * + * @param buffer the buffer containing the bytes to send, or the buffer to fill. The state + * of the buffer is undefined until the request is returned by + * {@link UsbDeviceConnection#requestWait}. If the request failed the buffer + * will be unchanged; if the request succeeded the position of the buffer is + * incremented by the number of bytes sent/received. + * + * @return true if the queueing operation succeeded + */ + public boolean enqueue(@Nullable ByteBuffer buffer) { + // Request need to be initialized + Preconditions.checkState(mNativeContext != 0, "request is not initialized"); + + // Request can not be currently enqueued + Preconditions.checkState(!mIsUsingEnqueue, "request is currently enqueued"); + + boolean isSend = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); + boolean wasEnqueued; + + synchronized (mLock) { + mBuffer = buffer; + + if (buffer == null) { + // Null buffers enqueue empty USB requests which is supported + mIsUsingEnqueue = true; + wasEnqueued = native_enqueue(null, 0, 0); + } else { + // Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once + Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE, + "number of remaining bytes"); + + // Can not receive into read-only buffers. + Preconditions.checkArgument(!(buffer.isReadOnly() && !isSend), "buffer can not be " + + "read-only when receiving data"); + + if (!buffer.isDirect()) { + mTempBuffer = ByteBuffer.allocateDirect(mBuffer.remaining()); + + if (isSend) { + // Copy buffer into temporary buffer + mBuffer.mark(); + mTempBuffer.put(mBuffer); + mTempBuffer.flip(); + mBuffer.reset(); + } + + // Send/Receive into the temp buffer instead + buffer = mTempBuffer; + } + + mIsUsingEnqueue = true; + wasEnqueued = native_enqueue(buffer, buffer.position(), buffer.remaining()); + } + } + + if (!wasEnqueued) { + mIsUsingEnqueue = false; + mTempBuffer = null; + mBuffer = null; + } + + return wasEnqueued; + } + /* package */ void dequeue() { - boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); - int bytesRead; - if (mBuffer.isDirect()) { - bytesRead = native_dequeue_direct(); - } else { - bytesRead = native_dequeue_array(mBuffer.array(), mLength, out); + boolean isSend = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); + int bytesTransferred; + + synchronized (mLock) { + if (mIsUsingEnqueue) { + bytesTransferred = native_dequeue_direct(); + mIsUsingEnqueue = false; + + if (mBuffer == null) { + // Nothing to do + } else if (mTempBuffer == null) { + mBuffer.position(mBuffer.position() + bytesTransferred); + } else { + mTempBuffer.limit(bytesTransferred); + + // The user might have modified mBuffer which might make put/position fail. + // Changing the buffer while a request is in flight is not supported. Still, + // make sure to free mTempBuffer correctly. + try { + if (isSend) { + mBuffer.position(mBuffer.position() + bytesTransferred); + } else { + // Copy temp buffer back into original buffer + mBuffer.put(mTempBuffer); + } + } finally { + mTempBuffer = null; + } + } + } else { + if (mBuffer.isDirect()) { + bytesTransferred = native_dequeue_direct(); + } else { + bytesTransferred = native_dequeue_array(mBuffer.array(), mLength, isSend); + } + if (bytesTransferred >= 0) { + mBuffer.position(Math.min(bytesTransferred, mLength)); + } + } + + mBuffer = null; + mLength = 0; } - if (bytesRead >= 0) { - mBuffer.position(Math.min(bytesRead, mLength)); - } - mBuffer = null; - mLength = 0; } /** @@ -202,6 +332,7 @@ public class UsbRequest { private native boolean native_init(UsbDeviceConnection connection, int ep_address, int ep_attributes, int ep_max_packet_size, int ep_interval); private native void native_close(); + private native boolean native_enqueue(ByteBuffer buffer, int offset, int length); private native boolean native_queue_array(byte[] buffer, int length, boolean out); private native int native_dequeue_array(byte[] buffer, int length, boolean out); private native boolean native_queue_direct(ByteBuffer buffer, int length, boolean out); diff --git a/core/jni/android_hardware_UsbRequest.cpp b/core/jni/android_hardware_UsbRequest.cpp index 4cbe3e47c96a9..4b7e0dd562b23 100644 --- a/core/jni/android_hardware_UsbRequest.cpp +++ b/core/jni/android_hardware_UsbRequest.cpp @@ -166,6 +166,38 @@ android_hardware_UsbRequest_queue_direct(JNIEnv *env, jobject thiz, return JNI_TRUE; } +static jboolean +android_hardware_UsbRequest_enqueue(JNIEnv *env, jobject thiz, jobject buffer, jint offset, + jint length) +{ + struct usb_request* request = get_request_from_object(env, thiz); + if (!request) { + ALOGE("request is closed in native_queue"); + return JNI_FALSE; + } + + if (buffer == NULL) { + request->buffer = NULL; + request->buffer_length = 0; + } else { + request->buffer = (void *)((char *)env->GetDirectBufferAddress(buffer) + offset); + request->buffer_length = length; + } + + // Save a reference to ourselves so UsbDeviceConnection.waitRequest() can find us. + // We also need this to make sure our native buffer is not deallocated while IO is active. + request->client_data = (void *)env->NewGlobalRef(thiz); + + int err = usb_request_queue(request); + + if (err != 0) { + request->buffer = NULL; + env->DeleteGlobalRef((jobject)request->client_data); + return JNI_FALSE; + } + return JNI_TRUE; +} + static jint android_hardware_UsbRequest_dequeue_direct(JNIEnv *env, jobject thiz) { @@ -194,6 +226,8 @@ static const JNINativeMethod method_table[] = { {"native_init", "(Landroid/hardware/usb/UsbDeviceConnection;IIII)Z", (void *)android_hardware_UsbRequest_init}, {"native_close", "()V", (void *)android_hardware_UsbRequest_close}, + {"native_enqueue", "(Ljava/nio/ByteBuffer;II)Z", + (void *)android_hardware_UsbRequest_enqueue}, {"native_queue_array", "([BIZ)Z", (void *)android_hardware_UsbRequest_queue_array}, {"native_dequeue_array", "([BIZ)I", (void *)android_hardware_UsbRequest_dequeue_array}, {"native_queue_direct", "(Ljava/nio/ByteBuffer;IZ)Z",