Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6a669fac authored by Nick Pelly's avatar Nick Pelly
Browse files

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.
parent 47e82dee
Loading
Loading
Loading
Loading
+35 −9
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ import java.io.IOException;
 * 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 @@ public final class BluetoothServerSocket implements Closeable {
     *                     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 @@ public final class BluetoothServerSocket implements Closeable {
     *                     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 @@ public final class BluetoothServerSocket implements Closeable {

    /**
     * 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);
    }

    /**
+37 −16
Original line number Diff line number Diff line
@@ -29,13 +29,19 @@ import java.io.OutputStream;
 * 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 final class BluetoothSocket implements Closeable {
     */
    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 final class BluetoothSocket implements Closeable {
     */
    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 @@ public final class BluetoothSocket implements Closeable {
     * @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 @@ public final class BluetoothSocket implements Closeable {
     * @throws IOException On error, for example connection failure
     */
    public void connect() throws IOException {
        connectNative(mAddress, mPort, -1);
        connectNative();
    }

    /**
@@ -163,14 +184,14 @@ public final class BluetoothSocket implements Closeable {
        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;
}
+192 −27
Original line number Diff line number Diff line
@@ -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 @@ static void initSocketNative(JNIEnv *env, jobject obj) {
    int lm = 0;
    jboolean auth;
    jboolean encrypt;
    jint type;

    /*TODO: do not hardcode to 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 @@ static void initSocketNative(JNIEnv *env, jobject obj) {
    auth = env->GetBooleanField(obj, field_mAuth);
    encrypt = env->GetBooleanField(obj, field_mEncrypt);

    /* 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 @@ static void initSocketNative(JNIEnv *env, jobject obj) {
        }
    }

    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 @@ static void connectNative(JNIEnv *env, jobject obj, jstring address,
    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);

    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, (struct sockaddr *)&addr, sizeof(addr))) {
    if (bind(s->fd, addr, addr_sz)) {
        jniThrowIOException(env, errno);
        return;
    }
@@ -173,10 +293,12 @@ static jobject acceptNative(JNIEnv *env, jobject obj, int timeout) {
    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 @@ static jobject acceptNative(JNIEnv *env, jobject obj, int timeout) {
    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 @@ static jobject acceptNative(JNIEnv *env, jobject obj, int timeout) {
    /* 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 @@ static void closeNative(JNIEnv *env, jobject obj) {
        return;

    asocket_abort(s);

    LOGV("...asocket_abort(%d) complete", s->fd);
    return;
#endif
    jniThrowIOException(env, ENOSYS);
@@ -313,10 +471,14 @@ static void destroyNative(JNIEnv *env, jobject obj) {
#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 void destroyNative(JNIEnv *env, jobject obj) {
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 @@ int register_android_bluetooth_BluetoothSocket(JNIEnv *env) {
    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));
}