Implement and expose SCO socket support in BluetoothSocket.java.

Implement L2CAP socket support, but do not expose it (untested).

NEXT: Switch to Builder style constructor instead of factory method.
diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java
index ca46701..f3baeab 100644
--- a/core/java/android/bluetooth/BluetoothServerSocket.java
+++ b/core/java/android/bluetooth/BluetoothServerSocket.java
@@ -27,7 +27,7 @@
  * RFCOMM is a connection orientated, streaming transport over Bluetooth. It is
  * also known as the Serial Port Profile (SPP).
  *
- * TODO: Consider implementing SCO and L2CAP sockets.
+ * TODO: Consider exposing L2CAP sockets.
  * TODO: Clean up javadoc grammer and formatting.
  * TODO: Remove @hide
  * @hide
@@ -45,9 +45,10 @@
      *                     insufficient permissions.
      */
     public static BluetoothServerSocket listenUsingRfcommOn(int port) throws IOException {
-        BluetoothServerSocket socket = new BluetoothServerSocket(true, true);
+        BluetoothServerSocket socket = new BluetoothServerSocket(
+                BluetoothSocket.TYPE_RFCOMM, true, true, port);
         try {
-            socket.mSocket.bindListenNative(port);
+            socket.mSocket.bindListenNative();
         } catch (IOException e) {
             try {
                 socket.close();
@@ -65,9 +66,31 @@
      *                     insufficient permissions.
      */
     public static BluetoothServerSocket listenUsingInsecureRfcommOn(int port) throws IOException {
-        BluetoothServerSocket socket = new BluetoothServerSocket(false, false);
+        BluetoothServerSocket socket = new BluetoothServerSocket(
+                BluetoothSocket.TYPE_RFCOMM, false, false, port);
         try {
-            socket.mSocket.bindListenNative(port);
+            socket.mSocket.bindListenNative();
+        } catch (IOException e) {
+            try {
+                socket.close();
+            } catch (IOException e2) { }
+            throw e;
+        }
+        return socket;
+    }
+
+    /**
+     * Construct a SCO server socket.
+     * Call #accept to retrieve connections to this socket.
+     * @return A SCO BluetoothServerSocket
+     * @throws IOException On error, for example Bluetooth not available, or
+     *                     insufficient permissions.
+     */
+    public static BluetoothServerSocket listenUsingScoOn() throws IOException {
+        BluetoothServerSocket socket = new BluetoothServerSocket(
+                BluetoothSocket.TYPE_SCO, false, false, -1);
+        try {
+            socket.mSocket.bindListenNative();
         } catch (IOException e) {
             try {
                 socket.close();
@@ -79,13 +102,16 @@
 
     /**
      * Construct a socket for incoming connections.
-     * @param auth    Require the remote device to be authenticated
-     * @param encrypt Require the connection to be encrypted
+     * @param type    type of socket
+     * @param auth    require the remote device to be authenticated
+     * @param encrypt require the connection to be encrypted
+     * @param port    remote port
      * @throws IOException On error, for example Bluetooth not available, or
      *                     insufficient priveleges
      */
-    private BluetoothServerSocket(boolean auth, boolean encrypt) throws IOException {
-        mSocket = new BluetoothSocket(-1, auth, encrypt, null, -1);
+    private BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port)
+            throws IOException {
+        mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port);
     }
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 670146b..de1f326 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -29,13 +29,19 @@
  * RFCOMM is a connection orientated, streaming transport over Bluetooth. It is
  * also known as the Serial Port Profile (SPP).
  *
- * TODO: Consider implementing SCO and L2CAP sockets.
+ * TODO: Consider exposing L2CAP sockets.
  * TODO: Clean up javadoc grammer and formatting.
  * TODO: Remove @hide
  * @hide
  */
 public final class BluetoothSocket implements Closeable {
-    private final int mPort;
+    /** Keep TYPE_RFCOMM etc in sync with BluetoothSocket.cpp */
+    /*package*/ static final int TYPE_RFCOMM = 1;
+    /*package*/ static final int TYPE_SCO = 2;
+    /*package*/ static final int TYPE_L2CAP = 3;
+
+    private final int mType;  /* one of TYPE_RFCOMM etc */
+    private final int mPort;  /* RFCOMM channel or L2CAP psm */
     private final String mAddress;    /* remote address */
     private final boolean mAuth;
     private final boolean mEncrypt;
@@ -57,7 +63,7 @@
      */
     public static BluetoothSocket createRfcommSocket(String address, int port)
             throws IOException {
-        return new BluetoothSocket(-1, true, true, address, port);
+        return new BluetoothSocket(TYPE_RFCOMM, -1, true, true, address, port);
     }
 
     /**
@@ -74,11 +80,25 @@
      */
     public static BluetoothSocket createInsecureRfcommSocket(String address, int port)
             throws IOException {
-        return new BluetoothSocket(-1, false, false, address, port);
+        return new BluetoothSocket(TYPE_RFCOMM, -1, false, false, address, port);
+    }
+
+    /**
+     * Construct a SCO socket ready to start an outgoing connection.
+     * Call #connect on the returned #BluetoothSocket to begin the connection.
+     * @param address remote Bluetooth address that this socket can connect to
+     * @return a SCO BluetoothSocket
+     * @throws IOException on error, for example Bluetooth not available, or
+     *                     insufficient permissions.
+     */
+    public static BluetoothSocket createScoSocket(String address, int port)
+            throws IOException {
+        return new BluetoothSocket(TYPE_SCO, -1, true, true, address, port);
     }
 
     /**
      * Construct a Bluetooth.
+     * @param type    type of socket
      * @param fd      fd to use for connected socket, or -1 for a new socket
      * @param auth    require the remote device to be authenticated
      * @param encrypt require the connection to be encrypted
@@ -87,8 +107,9 @@
      * @throws IOException On error, for example Bluetooth not available, or
      *                     insufficient priveleges
      */
-    /*package*/ BluetoothSocket(int fd, boolean auth, boolean encrypt, String address, int port)
-            throws IOException {
+    /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address,
+            int port) throws IOException {
+        mType = type;
         mAuth = auth;
         mEncrypt = encrypt;
         mAddress = address;
@@ -120,7 +141,7 @@
      * @throws IOException On error, for example connection failure
      */
     public void connect() throws IOException {
-        connectNative(mAddress, mPort, -1);
+        connectNative();
     }
 
     /**
@@ -163,14 +184,14 @@
         return mOutputStream;
     }
 
-    private native void initSocketNative();
-    private native void initSocketFromFdNative(int fd);
-    private native void connectNative(String address, int port, int timeout);
-    /*package*/ native void bindListenNative(int port) throws IOException;
+    private native void initSocketNative() throws IOException;
+    private native void initSocketFromFdNative(int fd) throws IOException;
+    private native void connectNative() throws IOException;
+    /*package*/ native void bindListenNative() throws IOException;
     /*package*/ native BluetoothSocket acceptNative(int timeout) throws IOException;
-    /*package*/ native int availableNative();
-    /*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();
+    /*package*/ native int availableNative() throws IOException;
+    /*package*/ native int readNative(byte[] b, int offset, int length) throws IOException;
+    /*package*/ native int writeNative(byte[] b, int offset, int length) throws IOException;
+    /*package*/ native void closeNative() throws IOException;
+    private native void destroyNative() throws IOException;
 }
diff --git a/core/jni/android_bluetooth_BluetoothSocket.cpp b/core/jni/android_bluetooth_BluetoothSocket.cpp
index e9c04a5..9c4f7c7 100644
--- a/core/jni/android_bluetooth_BluetoothSocket.cpp
+++ b/core/jni/android_bluetooth_BluetoothSocket.cpp
@@ -31,16 +31,29 @@
 #ifdef HAVE_BLUETOOTH
 #include <bluetooth/bluetooth.h>
 #include <bluetooth/rfcomm.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sco.h>
 #endif
 
+#define TYPE_AS_STR(t) \
+    ((t) == TYPE_RFCOMM ? "RFCOMM" : ((t) == TYPE_SCO ? "SCO" : "L2CAP"))
+
 namespace android {
 
 static jfieldID  field_mAuth;     /* read-only */
 static jfieldID  field_mEncrypt;  /* read-only */
+static jfieldID  field_mType;     /* read-only */
+static jfieldID  field_mAddress;  /* read-only */
+static jfieldID  field_mPort;     /* read-only */
 static jfieldID  field_mSocketData;
 static jmethodID method_BluetoothSocket_ctor;
 static jclass    class_BluetoothSocket;
 
+/* Keep TYPE_RFCOMM etc in sync with BluetoothSocket.java */
+static const int TYPE_RFCOMM = 1;
+static const int TYPE_SCO = 2;
+static const int TYPE_L2CAP = 3;  // TODO: Test l2cap code paths
+
 static struct asocket *get_socketData(JNIEnv *env, jobject obj) {
     struct asocket *s =
             (struct asocket *) env->GetIntField(obj, field_mSocketData);
@@ -76,9 +89,25 @@
     int lm = 0;
     jboolean auth;
     jboolean encrypt;
+    jint type;
 
-    /*TODO: do not hardcode to rfcomm */
-    fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+    type = env->GetIntField(obj, field_mType);
+
+    switch (type) {
+    case TYPE_RFCOMM:
+        fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+        break;
+    case TYPE_SCO:
+        fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
+        break;
+    case TYPE_L2CAP:
+        fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+        break;
+    default:
+        jniThrowIOException(env, ENOSYS);
+        return;
+    }
+
     if (fd < 0) {
         LOGV("socket() failed, throwing");
         jniThrowIOException(env, errno);
@@ -88,8 +117,17 @@
     auth = env->GetBooleanField(obj, field_mAuth);
     encrypt = env->GetBooleanField(obj, field_mEncrypt);
 
-    lm |= auth ? RFCOMM_LM_AUTH : 0;
-    lm |= encrypt? RFCOMM_LM_ENCRYPT : 0;
+    /* kernel does not yet support LM for SCO */
+    switch (type) {
+    case TYPE_RFCOMM:
+        lm |= auth ? RFCOMM_LM_AUTH : 0;
+        lm |= encrypt? RFCOMM_LM_ENCRYPT : 0;
+        break;
+    case TYPE_L2CAP:
+        lm |= auth ? L2CAP_LM_AUTH : 0;
+        lm |= encrypt? L2CAP_LM_ENCRYPT : 0;
+        break;
+    }
 
     if (lm) {
         if (setsockopt(fd, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm))) {
@@ -99,36 +137,83 @@
         }
     }
 
+    LOGV("...fd %d created (%s, lm = %x)", fd, TYPE_AS_STR(type), lm);
+
     initSocketFromFdNative(env, obj, fd);
     return;
 #endif
     jniThrowIOException(env, ENOSYS);
 }
 
-static void connectNative(JNIEnv *env, jobject obj, jstring address,
-        jint port, jint timeout) {
+static void connectNative(JNIEnv *env, jobject obj) {
 #ifdef HAVE_BLUETOOTH
     LOGV(__FUNCTION__);
 
     int ret;
-    struct sockaddr_rc addr;
+    jint type;
     const char *c_address;
+    jstring address;
+    bdaddr_t bdaddress;
+    socklen_t addr_sz;
+    struct sockaddr *addr;
     struct asocket *s = get_socketData(env, obj);
 
     if (!s)
         return;
 
-    addr.rc_family = AF_BLUETOOTH;
-    addr.rc_channel = port;
+    type = env->GetIntField(obj, field_mType);
+
+    /* parse address into bdaddress */
+    address = (jstring) env->GetObjectField(obj, field_mAddress);
     c_address = env->GetStringUTFChars(address, NULL);
-    if (get_bdaddr((const char *)c_address, &addr.rc_bdaddr)) {
+    if (get_bdaddr(c_address, &bdaddress)) {
         env->ReleaseStringUTFChars(address, c_address);
         jniThrowIOException(env, EINVAL);
         return;
     }
     env->ReleaseStringUTFChars(address, c_address);
 
-    ret = asocket_connect(s, (struct sockaddr *)&addr, sizeof(addr), timeout);
+    switch (type) {
+    case TYPE_RFCOMM:
+        struct sockaddr_rc addr_rc;
+        addr = (struct sockaddr *)&addr_rc;
+        addr_sz = sizeof(addr_rc);
+
+        memset(addr, 0, addr_sz);
+        addr_rc.rc_family = AF_BLUETOOTH;
+        addr_rc.rc_channel = env->GetIntField(obj, field_mPort);
+        memcpy(&addr_rc.rc_bdaddr, &bdaddress, sizeof(bdaddr_t));
+
+        break;
+    case TYPE_SCO:
+        struct sockaddr_sco addr_sco;
+        addr = (struct sockaddr *)&addr_sco;
+        addr_sz = sizeof(addr_sco);
+
+        memset(addr, 0, addr_sz);
+        addr_sco.sco_family = AF_BLUETOOTH;
+        memcpy(&addr_sco.sco_bdaddr, &bdaddress, sizeof(bdaddr_t));
+
+        break;
+    case TYPE_L2CAP:
+        struct sockaddr_l2 addr_l2;
+        addr = (struct sockaddr *)&addr_l2;
+        addr_sz = sizeof(addr_l2);
+
+        memset(addr, 0, addr_sz);
+        addr_l2.l2_family = AF_BLUETOOTH;
+        addr_l2.l2_psm = env->GetIntField(obj, field_mPort);
+        memcpy(&addr_l2.l2_bdaddr, &bdaddress, sizeof(bdaddr_t));
+
+        break;
+    default:
+        jniThrowIOException(env, ENOSYS);
+        return;
+    }
+
+    ret = asocket_connect(s, addr, addr_sz, -1);
+    LOGV("...connect(%d, %s) = %d (errno %d)",
+            s->fd, TYPE_AS_STR(type), ret, errno);
 
     if (ret)
         jniThrowIOException(env, errno);
@@ -138,22 +223,57 @@
     jniThrowIOException(env, ENOSYS);
 }
 
-static void bindListenNative(JNIEnv *env, jobject obj, jint port) {
+static void bindListenNative(JNIEnv *env, jobject obj) {
 #ifdef HAVE_BLUETOOTH
     LOGV(__FUNCTION__);
 
-    struct sockaddr_rc addr;
+    jint type;
+    socklen_t addr_sz;
+    struct sockaddr *addr;
+    bdaddr_t bdaddr = *BDADDR_ANY;
     struct asocket *s = get_socketData(env, obj);
 
     if (!s)
         return;
 
-    memset(&addr, 0, sizeof(struct sockaddr_rc));
-    addr.rc_family = AF_BLUETOOTH;
-    addr.rc_bdaddr = *BDADDR_ANY;
-    addr.rc_channel = port;
+    type = env->GetIntField(obj, field_mType);
 
-    if (bind(s->fd, (struct sockaddr *)&addr, sizeof(addr))) {
+    switch (type) {
+    case TYPE_RFCOMM:
+        struct sockaddr_rc addr_rc;
+        addr = (struct sockaddr *)&addr_rc;
+        addr_sz = sizeof(addr_rc);
+
+        memset(addr, 0, addr_sz);
+        addr_rc.rc_family = AF_BLUETOOTH;
+        addr_rc.rc_channel = env->GetIntField(obj, field_mPort);
+        memcpy(&addr_rc.rc_bdaddr, &bdaddr, sizeof(bdaddr_t));
+        break;
+    case TYPE_SCO:
+        struct sockaddr_sco addr_sco;
+        addr = (struct sockaddr *)&addr_sco;
+        addr_sz = sizeof(addr_sco);
+
+        memset(addr, 0, addr_sz);
+        addr_sco.sco_family = AF_BLUETOOTH;
+        memcpy(&addr_sco.sco_bdaddr, &bdaddr, sizeof(bdaddr_t));
+        break;
+    case TYPE_L2CAP:
+        struct sockaddr_l2 addr_l2;
+        addr = (struct sockaddr *)&addr_l2;
+        addr_sz = sizeof(addr_l2);
+
+        memset(addr, 0, addr_sz);
+        addr_l2.l2_family = AF_BLUETOOTH;
+        addr_l2.l2_psm = env->GetIntField(obj, field_mPort);
+        memcpy(&addr_l2.l2_bdaddr, &bdaddr, sizeof(bdaddr_t));
+        break;
+    default:
+        jniThrowIOException(env, ENOSYS);
+        return;
+    }
+
+    if (bind(s->fd, addr, addr_sz)) {
         jniThrowIOException(env, errno);
         return;
     }
@@ -173,10 +293,12 @@
     LOGV(__FUNCTION__);
 
     int fd;
-    struct sockaddr_rc addr;
-    int addrlen = sizeof(addr);
+    jint type;
+    struct sockaddr *addr;
+    socklen_t addr_sz;
     jstring addr_jstr;
     char addr_cstr[BTADDR_SIZE];
+    bdaddr_t *bdaddr;
     jboolean auth;
     jboolean encrypt;
 
@@ -185,7 +307,39 @@
     if (!s)
         return NULL;
 
-    fd = asocket_accept(s, (struct sockaddr *)&addr, &addrlen, timeout);
+    type = env->GetIntField(obj, field_mType);
+
+    switch (type) {
+    case TYPE_RFCOMM:
+        struct sockaddr_rc addr_rc;
+        addr = (struct sockaddr *)&addr_rc;
+        addr_sz = sizeof(addr_rc);
+        bdaddr = &addr_rc.rc_bdaddr;
+        memset(addr, 0, addr_sz);
+        break;
+    case TYPE_SCO:
+        struct sockaddr_sco addr_sco;
+        addr = (struct sockaddr *)&addr_sco;
+        addr_sz = sizeof(addr_sco);
+        bdaddr = &addr_sco.sco_bdaddr;
+        memset(addr, 0, addr_sz);
+        break;
+    case TYPE_L2CAP:
+        struct sockaddr_l2 addr_l2;
+        addr = (struct sockaddr *)&addr_l2;
+        addr_sz = sizeof(addr_l2);
+        bdaddr = &addr_l2.l2_bdaddr;
+        memset(addr, 0, addr_sz);
+        break;
+    default:
+        jniThrowIOException(env, ENOSYS);
+        return NULL;
+    }
+
+    fd = asocket_accept(s, addr, &addr_sz, timeout);
+
+    LOGV("...accept(%d, %s) = %d (errno %d)",
+            s->fd, TYPE_AS_STR(type), fd, errno);
 
     if (fd < 0) {
         jniThrowIOException(env, errno);
@@ -195,10 +349,12 @@
     /* Connected - return new BluetoothSocket */
     auth = env->GetBooleanField(obj, field_mAuth);
     encrypt = env->GetBooleanField(obj, field_mEncrypt);
-    get_bdaddr_as_string(&addr.rc_bdaddr, addr_cstr);
+
+    get_bdaddr_as_string(bdaddr, addr_cstr);
+
     addr_jstr = env->NewStringUTF(addr_cstr);
-    return env->NewObject(class_BluetoothSocket, method_BluetoothSocket_ctor, fd,
-            auth, encrypt, addr_jstr, -1);
+    return env->NewObject(class_BluetoothSocket, method_BluetoothSocket_ctor,
+            type, fd, auth, encrypt, addr_jstr, -1);
 
 #endif
     jniThrowIOException(env, ENOSYS);
@@ -304,6 +460,8 @@
         return;
 
     asocket_abort(s);
+
+    LOGV("...asocket_abort(%d) complete", s->fd);
     return;
 #endif
     jniThrowIOException(env, ENOSYS);
@@ -313,10 +471,14 @@
 #ifdef HAVE_BLUETOOTH
     LOGV(__FUNCTION__);
     struct asocket *s = get_socketData(env, obj);
+    int fd = s->fd;
+
     if (!s)
         return;
 
     asocket_destroy(s);
+
+    LOGV("...asocket_destroy(%d) complete", fd);
     return;
 #endif
     jniThrowIOException(env, ENOSYS);
@@ -325,8 +487,8 @@
 static JNINativeMethod sMethods[] = {
     {"initSocketNative", "()V",  (void*) initSocketNative},
     {"initSocketFromFdNative", "(I)V",  (void*) initSocketFromFdNative},
-    {"connectNative", "(Ljava/lang/String;II)V", (void *) connectNative},
-    {"bindListenNative", "(I)V", (void *) bindListenNative},
+    {"connectNative", "()V", (void *) connectNative},
+    {"bindListenNative", "()V", (void *) bindListenNative},
     {"acceptNative", "(I)Landroid/bluetooth/BluetoothSocket;", (void *) acceptNative},
     {"availableNative", "()I",    (void *) availableNative},
     {"readNative", "([BII)I",    (void *) readNative},
@@ -340,10 +502,13 @@
     if (clazz == NULL)
         return -1;
     class_BluetoothSocket = (jclass) env->NewGlobalRef(clazz);
+    field_mType = env->GetFieldID(clazz, "mType", "I");
+    field_mAddress = env->GetFieldID(clazz, "mAddress", "Ljava/lang/String;");
+    field_mPort = env->GetFieldID(clazz, "mPort", "I");
     field_mAuth = env->GetFieldID(clazz, "mAuth", "Z");
     field_mEncrypt = env->GetFieldID(clazz, "mEncrypt", "Z");
     field_mSocketData = env->GetFieldID(clazz, "mSocketData", "I");
-    method_BluetoothSocket_ctor = env->GetMethodID(clazz, "<init>", "(IZZLjava/lang/String;I)V");
+    method_BluetoothSocket_ctor = env->GetMethodID(clazz, "<init>", "(IIZZLjava/lang/String;I)V");
     return AndroidRuntime::registerNativeMethods(env,
         "android/bluetooth/BluetoothSocket", sMethods, NELEM(sMethods));
 }