Merge "Correctly use ByteBuffer in UsbRequest"
diff --git a/api/current.txt b/api/current.txt
index 4c98263..b39fcf8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14835,10 +14835,11 @@
     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 ce115d8..d73dbe9 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -16045,10 +16045,11 @@
     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 92e5776..fbd13153 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -14852,10 +14852,11 @@
     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 ca2b1f8..ffd6ec3 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -265,8 +265,6 @@
      * {@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.</p>
-     * <p>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.</p>
      *
      * @return a completed USB request, or null if an error occurred
      *
@@ -291,8 +289,6 @@
      * {@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.</p>
-     * <p>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.</p>
      * <p>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.</p>
@@ -307,7 +303,7 @@
      *                                  {@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 9f7592f..badb344 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 @@
 
     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 @@
     // 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 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 @@
      * Queues the request to send or receive data on its endpoint.
      * <p>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}</p>
+     * queueing operation is successful, return true. The result will be returned via
+     * {@link UsbDeviceConnection#requestWait}</p>
      *
      * @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");
+
+        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;
+            }
         }
-        if (!result) {
-            mBuffer = null;
-            mLength = 0;
-        }
+
         return result;
     }
 
+    /**
+     * Queues the request to send or receive data on its endpoint.
+     *
+     * <p>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}</p>
+     *
+     * @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 @@
     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 4cbe3e4..4b7e0dd 100644
--- a/core/jni/android_hardware_UsbRequest.cpp
+++ b/core/jni/android_hardware_UsbRequest.cpp
@@ -166,6 +166,38 @@
     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 @@
     {"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",