diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | 54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch) | |
tree | 35051494d2af230dce54d6b31c6af8fc24091316 /core/jni/android_bluetooth_RfcommSocket.cpp | |
download | frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2 |
Initial Contribution
Diffstat (limited to 'core/jni/android_bluetooth_RfcommSocket.cpp')
-rw-r--r-- | core/jni/android_bluetooth_RfcommSocket.cpp | 621 |
1 files changed, 621 insertions, 0 deletions
diff --git a/core/jni/android_bluetooth_RfcommSocket.cpp b/core/jni/android_bluetooth_RfcommSocket.cpp new file mode 100644 index 0000000..3ed35d9 --- /dev/null +++ b/core/jni/android_bluetooth_RfcommSocket.cpp @@ -0,0 +1,621 @@ +/* +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#define LOG_TAG "bluetooth_RfcommSocket.cpp" + +#include "android_bluetooth_common.h" +#include "android_runtime/AndroidRuntime.h" +#include "JNIHelp.h" +#include "jni.h" +#include "utils/Log.h" +#include "utils/misc.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/poll.h> + +#ifdef HAVE_BLUETOOTH +#include <bluetooth/bluetooth.h> +#include <bluetooth/rfcomm.h> +#include <bluetooth/sco.h> +#endif + +namespace android { + +#ifdef HAVE_BLUETOOTH +static jfieldID field_mNativeData; +static jfieldID field_mTimeoutRemainingMs; +static jfieldID field_mAcceptTimeoutRemainingMs; +static jfieldID field_mAddress; +static jfieldID field_mPort; + +typedef struct { + jstring address; + const char *c_address; + int rfcomm_channel; + int last_read_err; + int rfcomm_sock; + // < 0 -- in progress, + // 0 -- not connected + // > 0 connected + // 1 input is open + // 2 output is open + // 3 both input and output are open + int rfcomm_connected; + int rfcomm_sock_flags; +} native_data_t; + +static inline native_data_t * get_native_data(JNIEnv *env, jobject object) { + return (native_data_t *)(env->GetIntField(object, field_mNativeData)); +} + +static inline void init_socket_info( + JNIEnv *env, jobject object, + native_data_t *nat, + jstring address, + jint rfcomm_channel) { + nat->address = (jstring)env->NewGlobalRef(address); + nat->c_address = env->GetStringUTFChars(nat->address, NULL); + nat->rfcomm_channel = (int)rfcomm_channel; +} + +static inline void cleanup_socket_info(JNIEnv *env, native_data_t *nat) { + if (nat->c_address != NULL) { + env->ReleaseStringUTFChars(nat->address, nat->c_address); + env->DeleteGlobalRef(nat->address); + nat->c_address = NULL; + } +} +#endif + +static void classInitNative(JNIEnv* env, jclass clazz) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + field_mNativeData = get_field(env, clazz, "mNativeData", "I"); + field_mTimeoutRemainingMs = get_field(env, clazz, "mTimeoutRemainingMs", "I"); + field_mAcceptTimeoutRemainingMs = get_field(env, clazz, "mAcceptTimeoutRemainingMs", "I"); + field_mAddress = get_field(env, clazz, "mAddress", "Ljava/lang/String;"); + field_mPort = get_field(env, clazz, "mPort", "I"); +#endif +} + +static void initializeNativeDataNative(JNIEnv* env, jobject object) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + + native_data_t *nat = (native_data_t *)calloc(1, sizeof(native_data_t)); + if (nat == NULL) { + LOGE("%s: out of memory!", __FUNCTION__); + return; + } + + env->SetIntField(object, field_mNativeData, (jint)nat); + nat->rfcomm_sock = -1; + nat->rfcomm_connected = 0; +#endif +} + +static void cleanupNativeDataNative(JNIEnv* env, jobject object) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, object); + if (nat) { + free(nat); + } +#endif +} + +static jobject createNative(JNIEnv *env, jobject obj) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + int lm; + native_data_t *nat = get_native_data(env, obj); + nat->rfcomm_sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + + if (nat->rfcomm_sock < 0) { + LOGE("%s: Could not create RFCOMM socket: %s\n", __FUNCTION__, + strerror(errno)); + return NULL; + } + + lm = RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT; + + if (lm && setsockopt(nat->rfcomm_sock, SOL_RFCOMM, RFCOMM_LM, &lm, + sizeof(lm)) < 0) { + LOGE("%s: Can't set RFCOMM link mode", __FUNCTION__); + close(nat->rfcomm_sock); + return NULL; + } + + return jniCreateFileDescriptor(env, nat->rfcomm_sock); +#else + return NULL; +#endif +} + +static void destroyNative(JNIEnv *env, jobject obj) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, obj); + cleanup_socket_info(env, nat); + if (nat->rfcomm_sock >= 0) { + close(nat->rfcomm_sock); + nat->rfcomm_sock = -1; + } +#endif +} + + +static jboolean connectNative(JNIEnv *env, jobject obj, + jstring address, jint port) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, obj); + + if (nat->rfcomm_sock >= 0) { + if (nat->rfcomm_connected) { + LOGI("RFCOMM socket: %s.", + (nat->rfcomm_connected > 0) ? "already connected" : "connection is in progress"); + return JNI_TRUE; + } + + init_socket_info(env, obj, nat, address, port); + + struct sockaddr_rc addr; + memset(&addr, 0, sizeof(struct sockaddr_rc)); + get_bdaddr(nat->c_address, &addr.rc_bdaddr); + addr.rc_channel = nat->rfcomm_channel; + addr.rc_family = AF_BLUETOOTH; + nat->rfcomm_connected = 0; + + while (nat->rfcomm_connected == 0) { + if (connect(nat->rfcomm_sock, (struct sockaddr *)&addr, + sizeof(addr)) < 0) { + if (errno == EINTR) continue; + LOGE("connect error: %s (%d)\n", strerror(errno), errno); + break; + } else { + nat->rfcomm_connected = 3; // input and output + } + } + } else { + LOGE("%s: socket(RFCOMM) error: socket not created", __FUNCTION__); + } + + if (nat->rfcomm_connected > 0) { + env->SetIntField(obj, field_mPort, port); + return JNI_TRUE; + } +#endif + return JNI_FALSE; +} + +static jboolean connectAsyncNative(JNIEnv *env, jobject obj, + jstring address, jint port) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, obj); + + if (nat->rfcomm_sock < 0) { + LOGE("%s: socket(RFCOMM) error: socket not created", __FUNCTION__); + return JNI_FALSE; + } + + if (nat->rfcomm_connected) { + LOGI("RFCOMM socket: %s.", + (nat->rfcomm_connected > 0) ? + "already connected" : "connection is in progress"); + return JNI_TRUE; + } + + init_socket_info(env, obj, nat, address, port); + + struct sockaddr_rc addr; + memset(&addr, 0, sizeof(struct sockaddr_rc)); + get_bdaddr(nat->c_address, &addr.rc_bdaddr); + addr.rc_channel = nat->rfcomm_channel; + addr.rc_family = AF_BLUETOOTH; + + nat->rfcomm_sock_flags = fcntl(nat->rfcomm_sock, F_GETFL, 0); + if (fcntl(nat->rfcomm_sock, + F_SETFL, nat->rfcomm_sock_flags | O_NONBLOCK) >= 0) { + int rc; + nat->rfcomm_connected = 0; + errno = 0; + rc = connect(nat->rfcomm_sock, + (struct sockaddr *)&addr, + sizeof(addr)); + + if (rc >= 0) { + nat->rfcomm_connected = 3; + LOGI("RFCOMM async connect immediately successful"); + env->SetIntField(obj, field_mPort, port); + return JNI_TRUE; + } + else if (rc < 0) { + if (errno == EINPROGRESS || errno == EAGAIN) + { + LOGI("RFCOMM async connect is in progress (%s)", + strerror(errno)); + nat->rfcomm_connected = -1; + env->SetIntField(obj, field_mPort, port); + return JNI_TRUE; + } + else + { + LOGE("RFCOMM async connect error (%d): %s (%d)", + nat->rfcomm_sock, strerror(errno), errno); + return JNI_FALSE; + } + } + } // fcntl(nat->rfcomm_sock ...) +#endif + return JNI_FALSE; +} + +static jboolean interruptAsyncConnectNative(JNIEnv *env, jobject obj) { + //WRITEME + return JNI_TRUE; +} + +static jint waitForAsyncConnectNative(JNIEnv *env, jobject obj, + jint timeout_ms) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + struct sockaddr_rc addr; + native_data_t *nat = get_native_data(env, obj); + + env->SetIntField(obj, field_mTimeoutRemainingMs, timeout_ms); + + if (nat->rfcomm_sock < 0) { + LOGE("%s: socket(RFCOMM) error: socket not created", __FUNCTION__); + return -1; + } + + if (nat->rfcomm_connected > 0) { + LOGI("%s: RFCOMM is already connected!", __FUNCTION__); + return 1; + } + + /* Do an asynchronous select() */ + int n; + fd_set rset, wset; + struct timeval to; + + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_SET(nat->rfcomm_sock, &rset); + FD_SET(nat->rfcomm_sock, &wset); + if (timeout_ms >= 0) { + to.tv_sec = timeout_ms / 1000; + to.tv_usec = 1000 * (timeout_ms % 1000); + } + n = select(nat->rfcomm_sock + 1, + &rset, + &wset, + NULL, + (timeout_ms < 0 ? NULL : &to)); + + if (timeout_ms > 0) { + jint remaining = to.tv_sec*1000 + to.tv_usec/1000; + LOGI("Remaining time %ldms", (long)remaining); + env->SetIntField(obj, field_mTimeoutRemainingMs, + remaining); + } + + if (n <= 0) { + if (n < 0) { + LOGE("select() on RFCOMM socket: %s (%d)", + strerror(errno), + errno); + return -1; + } + return 0; + } + /* n must be equal to 1 and either rset or wset must have the + file descriptor set. */ + LOGI("select() returned %d.", n); + if (FD_ISSET(nat->rfcomm_sock, &rset) || + FD_ISSET(nat->rfcomm_sock, &wset)) { + /* A trial async read() will tell us if everything is OK. */ + char ch; + errno = 0; + int nr = read(nat->rfcomm_sock, &ch, 1); + /* It should be that nr != 1 because we just opened a socket + and we haven't sent anything over it for the other side to + respond... but one can't be paranoid enough. + */ + if (nr >= 0 || errno != EAGAIN) { + LOGE("RFCOMM async connect() error: %s (%d), nr = %d\n", + strerror(errno), + errno, + nr); + /* Clear the rfcomm_connected flag to cause this function + to re-create the socket and re-attempt the connect() + the next time it is called. + */ + nat->rfcomm_connected = 0; + /* Restore the blocking properties of the socket. */ + fcntl(nat->rfcomm_sock, F_SETFL, nat->rfcomm_sock_flags); + return -1; + } + /* Restore the blocking properties of the socket. */ + fcntl(nat->rfcomm_sock, F_SETFL, nat->rfcomm_sock_flags); + LOGI("Successful RFCOMM socket connect."); + nat->rfcomm_connected = 3; // input and output + return 1; + } +#endif + return -1; +} + +static jboolean shutdownNative(JNIEnv *env, jobject obj, + jboolean shutdownInput) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + /* NOTE: If you change the bcode to modify nat, make sure you + add synchronize(this) to the method calling this native + method. + */ + native_data_t *nat = get_native_data(env, obj); + if (nat->rfcomm_sock < 0) { + LOGE("socket(RFCOMM) error: socket not created"); + return JNI_FALSE; + } + int rc = shutdown(nat->rfcomm_sock, + shutdownInput ? SHUT_RD : SHUT_WR); + if (!rc) { + nat->rfcomm_connected &= + shutdownInput ? ~1 : ~2; + return JNI_TRUE; + } +#endif + return JNI_FALSE; +} + +static jint isConnectedNative(JNIEnv *env, jobject obj) { + LOGI(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + const native_data_t *nat = get_native_data(env, obj); + return nat->rfcomm_connected; +#endif + return 0; +} + +//@@@@@@@@@ bind to device??? +static jboolean bindNative(JNIEnv *env, jobject obj, jstring device, + jint port) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + + /* NOTE: If you change the code to modify nat, make sure you + add synchronize(this) to the method calling this native + method. + */ + const native_data_t *nat = get_native_data(env, obj); + if (nat->rfcomm_sock < 0) { + LOGE("socket(RFCOMM) error: socket not created"); + return JNI_FALSE; + } + + struct sockaddr_rc laddr; + int lm; + + lm = 0; +/* + lm |= RFCOMM_LM_MASTER; + lm |= RFCOMM_LM_AUTH; + lm |= RFCOMM_LM_ENCRYPT; + lm |= RFCOMM_LM_SECURE; +*/ + + if (lm && setsockopt(nat->rfcomm_sock, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm)) < 0) { + LOGE("Can't set RFCOMM link mode"); + return JNI_FALSE; + } + + laddr.rc_family = AF_BLUETOOTH; + bacpy(&laddr.rc_bdaddr, BDADDR_ANY); + laddr.rc_channel = port; + + if (bind(nat->rfcomm_sock, (struct sockaddr *)&laddr, sizeof(laddr)) < 0) { + LOGE("Can't bind RFCOMM socket"); + return JNI_FALSE; + } + + env->SetIntField(obj, field_mPort, port); + + return JNI_TRUE; +#endif + return JNI_FALSE; +} + +static jboolean listenNative(JNIEnv *env, jobject obj, jint backlog) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + /* NOTE: If you change the code to modify nat, make sure you + add synchronize(this) to the method calling this native + method. + */ + const native_data_t *nat = get_native_data(env, obj); + if (nat->rfcomm_sock < 0) { + LOGE("socket(RFCOMM) error: socket not created"); + return JNI_FALSE; + } + return listen(nat->rfcomm_sock, backlog) < 0 ? JNI_FALSE : JNI_TRUE; +#else + return JNI_FALSE; +#endif +} + +static int set_nb(int sk, bool nb) { + int flags = fcntl(sk, F_GETFL); + if (flags < 0) { + LOGE("Can't get socket flags with fcntl(): %s (%d)", + strerror(errno), errno); + close(sk); + return -1; + } + flags &= ~O_NONBLOCK; + if (nb) flags |= O_NONBLOCK; + int status = fcntl(sk, F_SETFL, flags); + if (status < 0) { + LOGE("Can't set socket to nonblocking mode with fcntl(): %s (%d)", + strerror(errno), errno); + close(sk); + return -1; + } + return 0; +} + +// Note: the code should check at a higher level to see whether +// listen() has been called. +#ifdef HAVE_BLUETOOTH +static int do_accept(JNIEnv* env, jobject object, int sock, + jobject newsock, + jfieldID out_address, + bool must_succeed) { + + if (must_succeed && set_nb(sock, true) < 0) + return -1; + + struct sockaddr_rc raddr; + int alen = sizeof(raddr); + int nsk = accept(sock, (struct sockaddr *) &raddr, &alen); + if (nsk < 0) { + LOGE("Error on accept from socket fd %d: %s (%d).", + sock, + strerror(errno), + errno); + if (must_succeed) set_nb(sock, false); + return -1; + } + + char addr[BTADDR_SIZE]; + get_bdaddr_as_string(&raddr.rc_bdaddr, addr); + env->SetObjectField(newsock, out_address, env->NewStringUTF(addr)); + + LOGI("Successful accept() on AG socket %d: new socket %d, address %s, RFCOMM channel %d", + sock, + nsk, + addr, + raddr.rc_channel); + if (must_succeed) set_nb(sock, false); + return nsk; +} +#endif /*HAVE_BLUETOOTH*/ + +static jobject acceptNative(JNIEnv *env, jobject obj, + jobject newsock, jint timeoutMs) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, obj); + if (nat->rfcomm_sock < 0) { + LOGE("socket(RFCOMM) error: socket not created"); + return JNI_FALSE; + } + + if (newsock == NULL) { + LOGE("%s: newsock = NULL\n", __FUNCTION__); + return JNI_FALSE; + } + + int nsk = -1; + if (timeoutMs < 0) { + /* block until accept() succeeds */ + nsk = do_accept(env, obj, nat->rfcomm_sock, + newsock, field_mAddress, false); + if (nsk < 0) { + return NULL; + } + } + else { + /* wait with a timeout */ + + struct pollfd fds; + fds.fd = nat->rfcomm_sock; + fds.events = POLLIN | POLLPRI | POLLOUT | POLLERR; + + env->SetIntField(obj, field_mAcceptTimeoutRemainingMs, 0); + int n = poll(&fds, 1, timeoutMs); + if (n <= 0) { + if (n < 0) { + LOGE("listening poll() on RFCOMM socket: %s (%d)", + strerror(errno), + errno); + env->SetIntField(obj, field_mAcceptTimeoutRemainingMs, timeoutMs); + } + else { + LOGI("listening poll() on RFCOMM socket timed out"); + } + return NULL; + } + + LOGI("listening poll() on RFCOMM socket returned %d", n); + if (fds.fd == nat->rfcomm_sock) { + if (fds.revents & (POLLIN | POLLPRI | POLLOUT)) { + LOGI("Accepting connection.\n"); + nsk = do_accept(env, obj, nat->rfcomm_sock, + newsock, field_mAddress, true); + if (nsk < 0) { + return NULL; + } + } + } + } + + LOGI("Connection accepted, new socket fd = %d.", nsk); + native_data_t *newnat = get_native_data(env, newsock); + newnat->rfcomm_sock = nsk; + newnat->rfcomm_connected = 3; + return jniCreateFileDescriptor(env, nsk); +#else + return NULL; +#endif +} + +static JNINativeMethod sMethods[] = { + /* name, signature, funcPtr */ + {"classInitNative", "()V", (void*)classInitNative}, + {"initializeNativeDataNative", "()V", (void *)initializeNativeDataNative}, + {"cleanupNativeDataNative", "()V", (void *)cleanupNativeDataNative}, + + {"createNative", "()Ljava/io/FileDescriptor;", (void *)createNative}, + {"destroyNative", "()V", (void *)destroyNative}, + {"connectNative", "(Ljava/lang/String;I)Z", (void *)connectNative}, + {"connectAsyncNative", "(Ljava/lang/String;I)Z", (void *)connectAsyncNative}, + {"interruptAsyncConnectNative", "()Z", (void *)interruptAsyncConnectNative}, + {"waitForAsyncConnectNative", "(I)I", (void *)waitForAsyncConnectNative}, + {"shutdownNative", "(Z)Z", (void *)shutdownNative}, + {"isConnectedNative", "()I", (void *)isConnectedNative}, + + {"bindNative", "(Ljava/lang/String;I)Z", (void*)bindNative}, + {"listenNative", "(I)Z", (void*)listenNative}, + {"acceptNative", "(Landroid/bluetooth/RfcommSocket;I)Ljava/io/FileDescriptor;", (void*)acceptNative}, +}; + +int register_android_bluetooth_RfcommSocket(JNIEnv *env) { + return AndroidRuntime::registerNativeMethods(env, + "android/bluetooth/RfcommSocket", sMethods, NELEM(sMethods)); +} + +} /* namespace android */ |