diff options
author | Nick Pelly <npelly@google.com> | 2009-06-02 15:57:18 -0700 |
---|---|---|
committer | Nick Pelly <npelly@google.com> | 2009-06-02 16:05:09 -0700 |
commit | 6a669fac385b51b8bb01844b77a9a43840dda854 (patch) | |
tree | ae86e3e41162c08fa56b1f84f68a50fe52d0493a | |
parent | 47e82dee6b18c33fab8c2cdf4f68b20d3663079e (diff) | |
download | frameworks_base-6a669fac385b51b8bb01844b77a9a43840dda854.zip frameworks_base-6a669fac385b51b8bb01844b77a9a43840dda854.tar.gz frameworks_base-6a669fac385b51b8bb01844b77a9a43840dda854.tar.bz2 |
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.
-rw-r--r-- | core/java/android/bluetooth/BluetoothServerSocket.java | 44 | ||||
-rw-r--r-- | core/java/android/bluetooth/BluetoothSocket.java | 53 | ||||
-rw-r--r-- | core/jni/android_bluetooth_BluetoothSocket.cpp | 219 |
3 files changed, 264 insertions, 52 deletions
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 @@ 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); } /** 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 @@ 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; } 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 @@ static void initSocketNative(JNIEnv *env, jobject obj) { int lm = 0; jboolean auth; jboolean encrypt; + jint type; + + 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; + } - /*TODO: do not hardcode to rfcomm */ - fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); 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); - 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 @@ 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)); } |