diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:31:44 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:31:44 -0800 |
commit | 9066cfe9886ac131c34d59ed0e2d287b0e3c0087 (patch) | |
tree | d88beb88001f2482911e3d28e43833b50e4b4e97 /core/jni/android_bluetooth_ScoSocket.cpp | |
parent | d83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (diff) | |
download | frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.zip frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.gz frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'core/jni/android_bluetooth_ScoSocket.cpp')
-rw-r--r-- | core/jni/android_bluetooth_ScoSocket.cpp | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/core/jni/android_bluetooth_ScoSocket.cpp b/core/jni/android_bluetooth_ScoSocket.cpp new file mode 100644 index 0000000..3afe5f5 --- /dev/null +++ b/core/jni/android_bluetooth_ScoSocket.cpp @@ -0,0 +1,506 @@ +/* +** Copyright 2008, 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_ScoSocket.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 <pthread.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <sys/poll.h> + +#ifdef HAVE_BLUETOOTH +#include <bluetooth/bluetooth.h> +#include <bluetooth/sco.h> +#endif + +/* Ideally, blocking I/O on a SCO socket would return when another thread + * calls close(). However it does not right now, in fact close() on a SCO + * socket has strange behavior (returns a bogus value) when other threads + * are performing blocking I/O on that socket. So, to workaround, we always + * call close() from the same thread that does blocking I/O. This requires the + * use of a socketpair to signal the blocking I/O to abort. + * + * Unfortunately I don't know a way to abort connect() yet, but at least this + * times out after the BT page timeout (10 seconds currently), so the thread + * will die eventually. The fact that the thread can outlive + * the Java object forces us to use a mutex in destoryNative(). + * + * The JNI API is entirely async. + * + * Also note this class deals only with SCO connections, not with data + * transmission. + */ +namespace android { +#ifdef HAVE_BLUETOOTH + +static JavaVM *jvm; +static jfieldID field_mNativeData; +static jmethodID method_onAccepted; +static jmethodID method_onConnected; +static jmethodID method_onClosed; + +struct thread_data_t; +static void *work_thread(void *arg); +static int connect_work(const char *address); +static int accept_work(int signal_sk); +static void wait_for_close(int sk, int signal_sk); +static void closeNative(JNIEnv *env, jobject object); + +/* shared native data - protected by mutex */ +typedef struct { + pthread_mutex_t mutex; + int signal_sk; // socket to signal blocked I/O to unblock + jobject object; // JNI global ref to the Java object + thread_data_t *thread_data; // pointer to thread local data + // max 1 thread per sco socket +} native_data_t; + +/* thread local data */ +struct thread_data_t { + native_data_t *nat; + bool is_accept; // accept (listening) or connect (outgoing) thread + int signal_sk; // socket for thread to listen for unblock signal + char address[BTADDR_SIZE]; // BT addres as string +}; + +static inline native_data_t * get_native_data(JNIEnv *env, jobject object) { + return (native_data_t *)(env->GetIntField(object, field_mNativeData)); +} +#endif + +static void classInitNative(JNIEnv* env, jclass clazz) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + if (env->GetJavaVM(&jvm) < 0) { + LOGE("Could not get handle to the VM"); + } + field_mNativeData = get_field(env, clazz, "mNativeData", "I"); + method_onAccepted = env->GetMethodID(clazz, "onAccepted", "(I)V"); + method_onConnected = env->GetMethodID(clazz, "onConnected", "(I)V"); + method_onClosed = env->GetMethodID(clazz, "onClosed", "()V"); +#endif +} + +/* Returns false if a serious error occured */ +static jboolean initNative(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 JNI_FALSE; + } + + pthread_mutex_init(&nat->mutex, NULL); + env->SetIntField(object, field_mNativeData, (jint)nat); + nat->signal_sk = -1; + nat->object = NULL; + nat->thread_data = NULL; + +#endif + return JNI_TRUE; +} + +static void destroyNative(JNIEnv* env, jobject object) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, object); + + closeNative(env, object); + + pthread_mutex_lock(&nat->mutex); + if (nat->thread_data != NULL) { + nat->thread_data->nat = NULL; + } + pthread_mutex_unlock(&nat->mutex); + pthread_mutex_destroy(&nat->mutex); + + free(nat); +#endif +} + +static jboolean acceptNative(JNIEnv *env, jobject object) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, object); + int signal_sks[2]; + pthread_t thread; + struct thread_data_t *data = NULL; + + pthread_mutex_lock(&nat->mutex); + if (nat->signal_sk != -1) { + pthread_mutex_unlock(&nat->mutex); + return JNI_FALSE; + } + + // setup socketpair to pass messages between threads + if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sks) < 0) { + LOGE("%s: socketpair() failed: %s", __FUNCTION__, strerror(errno)); + pthread_mutex_unlock(&nat->mutex); + return JNI_FALSE; + } + nat->signal_sk = signal_sks[0]; + nat->object = env->NewGlobalRef(object); + + data = (thread_data_t *)calloc(1, sizeof(thread_data_t)); + if (data == NULL) { + LOGE("%s: out of memory", __FUNCTION__); + pthread_mutex_unlock(&nat->mutex); + return JNI_FALSE; + } + nat->thread_data = data; + pthread_mutex_unlock(&nat->mutex); + + data->signal_sk = signal_sks[1]; + data->nat = nat; + data->is_accept = true; + + if (pthread_create(&thread, NULL, &work_thread, (void *)data) < 0) { + LOGE("%s: pthread_create() failed: %s", __FUNCTION__, strerror(errno)); + return JNI_FALSE; + } + return JNI_TRUE; + +#endif + return JNI_FALSE; +} + +static jboolean connectNative(JNIEnv *env, jobject object, jstring address) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, object); + int signal_sks[2]; + pthread_t thread; + struct thread_data_t *data; + const char *c_address; + + pthread_mutex_lock(&nat->mutex); + if (nat->signal_sk != -1) { + pthread_mutex_unlock(&nat->mutex); + return JNI_FALSE; + } + + // setup socketpair to pass messages between threads + if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sks) < 0) { + LOGE("%s: socketpair() failed: %s\n", __FUNCTION__, strerror(errno)); + pthread_mutex_unlock(&nat->mutex); + return JNI_FALSE; + } + nat->signal_sk = signal_sks[0]; + nat->object = env->NewGlobalRef(object); + + data = (thread_data_t *)calloc(1, sizeof(thread_data_t)); + if (data == NULL) { + LOGE("%s: out of memory", __FUNCTION__); + pthread_mutex_unlock(&nat->mutex); + return JNI_FALSE; + } + pthread_mutex_unlock(&nat->mutex); + + data->signal_sk = signal_sks[1]; + data->nat = nat; + c_address = env->GetStringUTFChars(address, NULL); + strlcpy(data->address, c_address, BTADDR_SIZE); + env->ReleaseStringUTFChars(address, c_address); + data->is_accept = false; + + if (pthread_create(&thread, NULL, &work_thread, (void *)data) < 0) { + LOGE("%s: pthread_create() failed: %s", __FUNCTION__, strerror(errno)); + return JNI_FALSE; + } + return JNI_TRUE; + +#endif + return JNI_FALSE; +} + +static void closeNative(JNIEnv *env, jobject object) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, object); + int signal_sk; + + pthread_mutex_lock(&nat->mutex); + signal_sk = nat->signal_sk; + nat->signal_sk = -1; + env->DeleteGlobalRef(nat->object); + nat->object = NULL; + pthread_mutex_unlock(&nat->mutex); + + if (signal_sk >= 0) { + LOGV("%s: signal_sk = %d", __FUNCTION__, signal_sk); + unsigned char dummy; + write(signal_sk, &dummy, sizeof(dummy)); + close(signal_sk); + } +#endif +} + +#ifdef HAVE_BLUETOOTH +/* thread entry point */ +static void *work_thread(void *arg) { + JNIEnv* env; + thread_data_t *data = (thread_data_t *)arg; + int sk; + + LOGV(__FUNCTION__); + if (jvm->AttachCurrentThread(&env, NULL) != JNI_OK) { + LOGE("%s: AttachCurrentThread() failed", __FUNCTION__); + return NULL; + } + + /* connect the SCO socket */ + if (data->is_accept) { + LOGV("SCO OBJECT %p ACCEPT #####", data->nat->object); + sk = accept_work(data->signal_sk); + LOGV("SCO OBJECT %p END ACCEPT *****", data->nat->object); + } else { + sk = connect_work(data->address); + } + + /* callback with connection result */ + if (data->nat == NULL) { + LOGV("%s: object destroyed!", __FUNCTION__); + goto done; + } + pthread_mutex_lock(&data->nat->mutex); + if (data->nat->object == NULL) { + pthread_mutex_unlock(&data->nat->mutex); + LOGV("%s: callback cancelled", __FUNCTION__); + goto done; + } + if (data->is_accept) { + env->CallVoidMethod(data->nat->object, method_onAccepted, sk); + } else { + env->CallVoidMethod(data->nat->object, method_onConnected, sk); + } + pthread_mutex_unlock(&data->nat->mutex); + + if (sk < 0) { + goto done; + } + + LOGV("SCO OBJECT %p %d CONNECTED +++ (%s)", data->nat->object, sk, + data->is_accept ? "in" : "out"); + + /* wait for the socket to close */ + LOGV("wait_for_close()..."); + wait_for_close(sk, data->signal_sk); + LOGV("wait_for_close() returned"); + + /* callback with close result */ + if (data->nat == NULL) { + LOGV("%s: object destroyed!", __FUNCTION__); + goto done; + } + pthread_mutex_lock(&data->nat->mutex); + if (data->nat->object == NULL) { + LOGV("%s: callback cancelled", __FUNCTION__); + } else { + env->CallVoidMethod(data->nat->object, method_onClosed); + } + pthread_mutex_unlock(&data->nat->mutex); + +done: + if (sk >= 0) { + close(sk); + LOGV("SCO OBJECT %p %d CLOSED --- (%s)", data->nat->object, sk, data->is_accept ? "in" : "out"); + } + if (data->signal_sk >= 0) { + close(data->signal_sk); + } + LOGV("SCO socket closed"); + + if (data->nat != NULL) { + pthread_mutex_lock(&data->nat->mutex); + env->DeleteGlobalRef(data->nat->object); + data->nat->object = NULL; + data->nat->thread_data = NULL; + pthread_mutex_unlock(&data->nat->mutex); + } + + free(data); + if (jvm->DetachCurrentThread() != JNI_OK) { + LOGE("%s: DetachCurrentThread() failed", __FUNCTION__); + } + + LOGV("work_thread() done"); + return NULL; +} + +static int accept_work(int signal_sk) { + LOGV(__FUNCTION__); + int sk; + int nsk; + int addr_sz; + int max_fd; + fd_set fds; + struct sockaddr_sco addr; + + sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); + if (sk < 0) { + LOGE("%s socket() failed: %s", __FUNCTION__, strerror(errno)); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + memcpy(&addr.sco_bdaddr, BDADDR_ANY, sizeof(bdaddr_t)); + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGE("%s bind() failed: %s", __FUNCTION__, strerror(errno)); + goto error; + } + + if (listen(sk, 1)) { + LOGE("%s: listen() failed: %s", __FUNCTION__, strerror(errno)); + goto error; + } + + memset(&addr, 0, sizeof(addr)); + addr_sz = sizeof(addr); + + FD_ZERO(&fds); + FD_SET(sk, &fds); + FD_SET(signal_sk, &fds); + + max_fd = (sk > signal_sk) ? sk : signal_sk; + LOGI("Listening SCO socket..."); + while (select(max_fd + 1, &fds, NULL, NULL, NULL) < 0) { + if (errno != EINTR) { + LOGE("%s: select() failed: %s", __FUNCTION__, strerror(errno)); + goto error; + } + LOGV("%s: select() EINTR, retrying", __FUNCTION__); + } + LOGV("select() returned"); + if (FD_ISSET(signal_sk, &fds)) { + // signal to cancel listening + LOGV("cancelled listening socket, closing"); + goto error; + } + if (!FD_ISSET(sk, &fds)) { + LOGE("error: select() returned >= 0 with no fds set"); + goto error; + } + + nsk = accept(sk, (struct sockaddr *)&addr, &addr_sz); + if (nsk < 0) { + LOGE("%s: accept() failed: %s", __FUNCTION__, strerror(errno)); + goto error; + } + LOGI("Connected SCO socket (incoming)"); + close(sk); // The listening socket + + return nsk; + +error: + close(sk); + + return -1; +} + +static int connect_work(const char *address) { + LOGV(__FUNCTION__); + struct sockaddr_sco addr; + int sk = -1; + + sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); + if (sk < 0) { + LOGE("%s: socket() failed: %s", __FUNCTION__, strerror(errno)); + return -1; + } + + /* Bind to local address */ + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + memcpy(&addr.sco_bdaddr, BDADDR_ANY, sizeof(bdaddr_t)); + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGE("%s: bind() failed: %s", __FUNCTION__, strerror(errno)); + goto error; + } + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + get_bdaddr(address, &addr.sco_bdaddr); + LOGI("Connecting to socket"); + while (connect(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + if (errno != EINTR) { + LOGE("%s: connect() failed: %s", __FUNCTION__, strerror(errno)); + goto error; + } + LOGV("%s: connect() EINTR, retrying", __FUNCTION__); + } + LOGI("SCO socket connected (outgoing)"); + + return sk; + +error: + if (sk >= 0) close(sk); + return -1; +} + +static void wait_for_close(int sk, int signal_sk) { + LOGV(__FUNCTION__); + pollfd p[2]; + + memset(p, 0, 2 * sizeof(pollfd)); + p[0].fd = sk; + p[1].fd = signal_sk; + p[1].events = POLLIN | POLLPRI; + + LOGV("poll..."); + + while (poll(p, 2, -1) < 0) { // blocks + if (errno != EINTR) { + LOGE("%s: poll() failed: %s", __FUNCTION__, strerror(errno)); + break; + } + LOGV("%s: poll() EINTR, retrying", __FUNCTION__); + } + + LOGV("poll() returned"); +} +#endif + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "()V", (void *)initNative}, + {"destroyNative", "()V", (void *)destroyNative}, + {"connectNative", "(Ljava/lang/String;)Z", (void *)connectNative}, + {"acceptNative", "()Z", (void *)acceptNative}, + {"closeNative", "()V", (void *)closeNative}, +}; + +int register_android_bluetooth_ScoSocket(JNIEnv *env) { + return AndroidRuntime::registerNativeMethods(env, + "android/bluetooth/ScoSocket", sMethods, NELEM(sMethods)); +} + +} /* namespace android */ |