Implement bulk read and writes for Bluetooth sockets.

Before: 0.1 kB/s
After: 100 kB/s
(in my java BT speed test app)
diff --git a/core/java/android/bluetooth/BluetoothInputStream.java b/core/java/android/bluetooth/BluetoothInputStream.java
index ceae70c..e6f501c 100644
--- a/core/java/android/bluetooth/BluetoothInputStream.java
+++ b/core/java/android/bluetooth/BluetoothInputStream.java
@@ -24,7 +24,6 @@
  *
  * Used to write to a Bluetooth socket.
  *
- * TODO: Implement bulk writes (instead of one byte at a time).
  * @hide
  */
 /*package*/ final class BluetoothInputStream extends InputStream {
@@ -54,9 +53,46 @@
      * @return the byte read or -1 if the end of stream has been reached.
      * @throws IOException
      *             if the stream is closed or another IOException occurs.
-     * @since Android 1.0
+     * @since Android 1.5
      */
     public int read() throws IOException {
-        return mSocket.readNative();
+        byte b[] = new byte[1];
+        int ret = mSocket.readNative(b, 0, 1);
+        if (ret == 1) {
+            return (int)b[0];
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * Reads at most {@code length} bytes from this stream and stores them in
+     * the byte array {@code b} starting at {@code offset}.
+     *
+     * @param b
+     *            the byte array in which to store the bytes read.
+     * @param offset
+     *            the initial position in {@code buffer} to store the bytes
+     *            read from this stream.
+     * @param length
+     *            the maximum number of bytes to store in {@code b}.
+     * @return the number of bytes actually read or -1 if the end of the stream
+     *         has been reached.
+     * @throws IndexOutOfBoundsException
+     *             if {@code offset < 0} or {@code length < 0}, or if
+     *             {@code offset + length} is greater than the length of
+     *             {@code b}.
+     * @throws IOException
+     *             if the stream is closed or another IOException occurs.
+     * @since Android 1.5
+     */
+    public int read(byte[] b, int offset, int length) throws IOException {
+        if (b == null) {
+            throw new NullPointerException("byte array is null");
+        }
+        if ((offset | length) < 0 || length > b.length - offset) {
+            throw new ArrayIndexOutOfBoundsException("invalid offset or length");
+        }
+        return mSocket.readNative(b, offset, length);
     }
 }
diff --git a/core/java/android/bluetooth/BluetoothOutputStream.java b/core/java/android/bluetooth/BluetoothOutputStream.java
index 32e6d17c..7e2ead4 100644
--- a/core/java/android/bluetooth/BluetoothOutputStream.java
+++ b/core/java/android/bluetooth/BluetoothOutputStream.java
@@ -24,7 +24,6 @@
  *
  * Used to read from a Bluetooth socket.
  *
- * TODO: Implement bulk reads (instead of one byte at a time).
  * @hide
  */
 /*package*/ final class BluetoothOutputStream extends OutputStream {
@@ -52,6 +51,37 @@
      * @since Android 1.0
      */
     public void write(int oneByte) throws IOException {
-        mSocket.writeNative(oneByte);
+        byte b[] = new byte[1];
+        b[0] = (byte)oneByte;
+        mSocket.writeNative(b, 0, 1);
+    }
+
+    /**
+     * Writes {@code count} bytes from the byte array {@code buffer} starting
+     * at position {@code offset} to this stream.
+     *
+     * @param b
+     *            the buffer to be written.
+     * @param offset
+     *            the start position in {@code buffer} from where to get bytes.
+     * @param count
+     *            the number of bytes from {@code buffer} to write to this
+     *            stream.
+     * @throws IOException
+     *             if an error occurs while writing to this stream.
+     * @throws IndexOutOfBoundsException
+     *             if {@code offset < 0} or {@code count < 0}, or if
+     *             {@code offset + count} is bigger than the length of
+     *             {@code buffer}.
+     * @since Android 1.0
+     */
+    public void write(byte[] b, int offset, int count) throws IOException {
+        if (b == null) {
+            throw new NullPointerException("buffer is null");
+        }
+        if ((offset | count) < 0 || count > b.length - offset) {
+            throw new IndexOutOfBoundsException("invalid offset or length");
+        }
+        mSocket.writeNative(b, offset, count);
     }
 }
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index fd8885e..670146b 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -169,8 +169,8 @@
     /*package*/ native void bindListenNative(int port) throws IOException;
     /*package*/ native BluetoothSocket acceptNative(int timeout) throws IOException;
     /*package*/ native int availableNative();
-    /*package*/ native int readNative();
-    /*package*/ native void writeNative(int data);
+    /*package*/ native int readNative(byte[] b, int offset, int length);
+    /*package*/ native int writeNative(byte[] b, int offset, int length);
     /*package*/ native void closeNative();
     private native void destroyNative();
 }
diff --git a/core/jni/android_bluetooth_BluetoothSocket.cpp b/core/jni/android_bluetooth_BluetoothSocket.cpp
index dc4c1d4..e9c04a5 100644
--- a/core/jni/android_bluetooth_BluetoothSocket.cpp
+++ b/core/jni/android_bluetooth_BluetoothSocket.cpp
@@ -227,44 +227,72 @@
     return -1;
 }
 
-static jint readNative(JNIEnv *env, jobject obj) {
+/** jb must not be null. offset and offset+length must be within array */
+static jint readNative(JNIEnv *env, jobject obj, jbyteArray jb, jint offset,
+        jint length) {
 #ifdef HAVE_BLUETOOTH
     LOGV(__FUNCTION__);
 
-    char buf;
+    int ret;
+    jbyte *b;
     struct asocket *s = get_socketData(env, obj);
 
     if (!s)
         return -1;
 
-    if (asocket_read(s, &buf, 1, -1) < 0) {
-        jniThrowIOException(env, errno);
+    b = env->GetByteArrayElements(jb, NULL);
+    if (b == NULL) {
+        jniThrowIOException(env, EINVAL);
         return -1;
     }
 
-    return (jint)buf;
+    ret = asocket_read(s, &b[offset], length, -1);
+    if (ret < 0) {
+        jniThrowIOException(env, errno);
+        env->ReleaseByteArrayElements(jb, b, JNI_ABORT);
+        return -1;
+    }
+
+    env->ReleaseByteArrayElements(jb, b, 0);
+    return (jint)ret;
 
 #endif
     jniThrowIOException(env, ENOSYS);
     return -1;
 }
 
-static void writeNative(JNIEnv *env, jobject obj, jint data) {
+/** jb must not be null. offset and offset+length must be within array */
+static jint writeNative(JNIEnv *env, jobject obj, jbyteArray jb, jint offset,
+        jint length) {
 #ifdef HAVE_BLUETOOTH
     LOGV(__FUNCTION__);
 
-    const char buf = (char)data;
+    int ret;
+    jbyte *b;
     struct asocket *s = get_socketData(env, obj);
 
     if (!s)
-        return;
+        return -1;
 
-    if (asocket_write(s, &buf, 1, -1) < 0)
+    b = env->GetByteArrayElements(jb, NULL);
+    if (b == NULL) {
+        jniThrowIOException(env, EINVAL);
+        return -1;
+    }
+
+    ret = asocket_write(s, &b[offset], length, -1);
+    if (ret < 0) {
         jniThrowIOException(env, errno);
+        env->ReleaseByteArrayElements(jb, b, JNI_ABORT);
+        return -1;
+    }
 
-    return;
+    env->ReleaseByteArrayElements(jb, b, JNI_ABORT);  // no need to commit
+    return (jint)ret;
+
 #endif
     jniThrowIOException(env, ENOSYS);
+    return -1;
 }
 
 static void closeNative(JNIEnv *env, jobject obj) {
@@ -301,8 +329,8 @@
     {"bindListenNative", "(I)V", (void *) bindListenNative},
     {"acceptNative", "(I)Landroid/bluetooth/BluetoothSocket;", (void *) acceptNative},
     {"availableNative", "()I",    (void *) availableNative},
-    {"readNative", "()I",    (void *) readNative},
-    {"writeNative", "(I)V",    (void *) writeNative},
+    {"readNative", "([BII)I",    (void *) readNative},
+    {"writeNative", "([BII)I",    (void *) writeNative},
     {"closeNative", "()V",    (void *) closeNative},
     {"destroyNative", "()V",    (void *) destroyNative},
 };