| /* |
| ** 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 */ |