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

Commit 3ddc4394 authored by Android (Google) Code Review's avatar Android (Google) Code Review
Browse files

Merge change 2969

* changes:
  Implement and expose SCO socket support in BluetoothSocket.java.
parents 2f1d60cd 6a669fac
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));
}