| /* |
| ** 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 "BT HSHFP" |
| |
| #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_mAddress; |
| static jfieldID field_mRfcommChannel; |
| static jfieldID field_mTimeoutRemainingMs; |
| |
| typedef struct { |
| jstring address; |
| const char *c_address; |
| int rfcomm_channel; |
| int last_read_err; |
| int rfcomm_sock; |
| int rfcomm_connected; // -1 in progress, 0 not connected, 1 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 const char CRLF[] = "\xd\xa"; |
| static const int CRLF_LEN = 2; |
| |
| static inline int write_error_check(int fd, const char* line, int len) { |
| int ret; |
| errno = 0; |
| ret = write(fd, line, len); |
| if (ret < 0) { |
| LOGE("%s: write() failed: %s (%d)", __FUNCTION__, strerror(errno), |
| errno); |
| return -1; |
| } |
| if (ret != len) { |
| LOGE("%s: write() only wrote %d of %d bytes", __FUNCTION__, ret, len); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int send_line(int fd, const char* line) { |
| int nw; |
| int len = strlen(line); |
| int llen = len + CRLF_LEN * 2 + 1; |
| char *buffer = (char *)calloc(llen, sizeof(char)); |
| |
| snprintf(buffer, llen, "%s%s%s", CRLF, line, CRLF); |
| |
| if (write_error_check(fd, buffer, llen - 1)) { |
| free(buffer); |
| return -1; |
| } |
| free(buffer); |
| return 0; |
| } |
| |
| static int is_ascii(char *line) { |
| for (;;line++) { |
| if (*line == 0) return 1; |
| if (*line >> 7) return 0; |
| } |
| } |
| |
| static const char* get_line(int fd, char *buf, int len, int timeout_ms, |
| int *err) { |
| char *bufit=buf; |
| int fd_flags = fcntl(fd, F_GETFL, 0); |
| struct pollfd pfd; |
| |
| again: |
| *bufit = 0; |
| pfd.fd = fd; |
| pfd.events = POLLIN; |
| *err = errno = 0; |
| int ret = poll(&pfd, 1, timeout_ms); |
| if (ret < 0) { |
| LOGE("poll() error\n"); |
| *err = errno; |
| return NULL; |
| } |
| if (ret == 0) { |
| return NULL; |
| } |
| |
| if (pfd.revents & (POLLHUP | POLLERR | POLLNVAL)) { |
| LOGW("RFCOMM poll() returned success (%d), " |
| "but with an unexpected revents bitmask: %#x\n", ret, pfd.revents); |
| errno = EIO; |
| *err = errno; |
| return NULL; |
| } |
| |
| while ((int)(bufit - buf) < (len - 1)) |
| { |
| errno = 0; |
| int rc = read(fd, bufit, 1); |
| |
| if (!rc) |
| break; |
| |
| if (rc < 0) { |
| if (errno == EBUSY) { |
| LOGI("read() error %s (%d): repeating read()...", |
| strerror(errno), errno); |
| goto again; |
| } |
| *err = errno; |
| LOGE("read() error %s (%d)", strerror(errno), errno); |
| return NULL; |
| } |
| |
| |
| if (*bufit=='\xd') { |
| break; |
| } |
| |
| if (*bufit=='\xa') |
| bufit = buf; |
| else |
| bufit++; |
| } |
| |
| *bufit = NULL; |
| |
| // Simple validation. Must be all ASCII. |
| // (we sometimes send non-ASCII UTF-8 in address book, but should |
| // never receive non-ASCII UTF-8). |
| // This was added because of the BMW 2005 E46 which sends binary junk. |
| if (is_ascii(buf)) { |
| LOG(LOG_INFO, "Bluetooth AT recv", buf); |
| } else { |
| LOGW("Ignoring invalid AT command: %s", buf); |
| buf[0] = NULL; |
| } |
| |
| return buf; |
| } |
| #endif |
| |
| static void classInitNative(JNIEnv* env, jclass clazz) { |
| LOGV(__FUNCTION__); |
| #ifdef HAVE_BLUETOOTH |
| field_mNativeData = get_field(env, clazz, "mNativeData", "I"); |
| field_mAddress = get_field(env, clazz, "mAddress", "Ljava/lang/String;"); |
| field_mTimeoutRemainingMs = get_field(env, clazz, "mTimeoutRemainingMs", "I"); |
| field_mRfcommChannel = get_field(env, clazz, "mRfcommChannel", "I"); |
| #endif |
| } |
| |
| static void initializeNativeDataNative(JNIEnv* env, jobject object, |
| jint socketFd) { |
| LOGV(__FUNCTION__); |
| #ifdef HAVE_BLUETOOTH |
| native_data_t *nat = (native_data_t *)calloc(1, sizeof(native_data_t)); |
| if (NULL == nat) { |
| LOGE("%s: out of memory!", __FUNCTION__); |
| return; |
| } |
| |
| env->SetIntField(object, field_mNativeData, (jint)nat); |
| nat->address = |
| (jstring)env->NewGlobalRef(env->GetObjectField(object, |
| field_mAddress)); |
| nat->c_address = env->GetStringUTFChars(nat->address, NULL); |
| nat->rfcomm_channel = env->GetIntField(object, field_mRfcommChannel); |
| nat->rfcomm_sock = socketFd; |
| nat->rfcomm_connected = socketFd >= 0; |
| if (nat->rfcomm_connected) |
| LOGI("%s: ALREADY CONNECTED!", __FUNCTION__); |
| #endif |
| } |
| |
| static void cleanupNativeDataNative(JNIEnv* env, jobject object) { |
| LOGV(__FUNCTION__); |
| #ifdef HAVE_BLUETOOTH |
| native_data_t *nat = |
| (native_data_t *)env->GetIntField(object, field_mNativeData); |
| env->ReleaseStringUTFChars(nat->address, nat->c_address); |
| env->DeleteGlobalRef(nat->address); |
| if (nat) |
| free(nat); |
| #endif |
| } |
| |
| static jboolean connectNative(JNIEnv *env, jobject obj) |
| { |
| LOGV(__FUNCTION__); |
| #ifdef HAVE_BLUETOOTH |
| int lm; |
| struct sockaddr_rc addr; |
| 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 JNI_FALSE; |
| } |
| |
| if (debug_no_encrypt()) { |
| lm = RFCOMM_LM_AUTH; |
| } else { |
| 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 JNI_FALSE; |
| } |
| |
| 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("%s: connect() failed: %s\n", __FUNCTION__, strerror(errno)); |
| close(nat->rfcomm_sock); |
| nat->rfcomm_sock = -1; |
| return JNI_FALSE; |
| } else { |
| nat->rfcomm_connected = 1; |
| } |
| } |
| |
| return JNI_TRUE; |
| #else |
| return JNI_FALSE; |
| #endif |
| } |
| |
| static jint connectAsyncNative(JNIEnv *env, jobject obj) { |
| LOGV(__FUNCTION__); |
| #ifdef HAVE_BLUETOOTH |
| struct sockaddr_rc addr; |
| native_data_t *nat = get_native_data(env, obj); |
| |
| if (nat->rfcomm_connected) { |
| LOGV("RFCOMM socket is already connected or connection is in progress."); |
| return 0; |
| } |
| |
| if (nat->rfcomm_sock < 0) { |
| int lm; |
| |
| 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 -1; |
| } |
| |
| if (debug_no_encrypt()) { |
| lm = RFCOMM_LM_AUTH; |
| } else { |
| 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 -1; |
| } |
| LOGI("Created RFCOMM socket fd %d.", nat->rfcomm_sock); |
| } |
| |
| 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; |
| if (nat->rfcomm_sock_flags >= 0) { |
| 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 = 1; |
| LOGI("async connect successful"); |
| return 0; |
| } |
| else if (rc < 0) { |
| if (errno == EINPROGRESS || errno == EAGAIN) |
| { |
| LOGI("async connect is in progress (%s)", |
| strerror(errno)); |
| nat->rfcomm_connected = -1; |
| return 0; |
| } |
| else |
| { |
| LOGE("async connect error: %s (%d)", strerror(errno), errno); |
| close(nat->rfcomm_sock); |
| nat->rfcomm_sock = -1; |
| return -errno; |
| } |
| } |
| } // fcntl(nat->rfcomm_sock ...) |
| } // if (nat->rfcomm_sock_flags >= 0) |
| #endif |
| return -1; |
| } |
| |
| 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_connected > 0) { |
| LOGI("RFCOMM is already connected!"); |
| return 1; |
| } |
| |
| if (nat->rfcomm_sock >= 0 && nat->rfcomm_connected == 0) { |
| LOGI("Re-opening RFCOMM socket."); |
| close(nat->rfcomm_sock); |
| nat->rfcomm_sock = -1; |
| } |
| int ret = connectAsyncNative(env, obj); |
| |
| if (ret < 0) { |
| LOGI("Failed to re-open RFCOMM socket!"); |
| return ret; |
| } |
| |
| if (nat->rfcomm_sock >= 0) { |
| /* 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; |
| LOGV("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 -errno; |
| } |
| return 0; |
| } |
| /* n must be equal to 1 and either rset or wset must have the |
| file descriptor set. */ |
| LOGV("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); |
| close(nat->rfcomm_sock); |
| nat->rfcomm_sock = -1; |
| return -errno; |
| } |
| } |
| /* 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 = 1; |
| return 1; |
| } |
| } |
| else LOGE("RFCOMM socket file descriptor %d is bad!", |
| nat->rfcomm_sock); |
| #endif |
| return -1; |
| } |
| |
| static void disconnectNative(JNIEnv *env, jobject obj) { |
| LOGV(__FUNCTION__); |
| #ifdef HAVE_BLUETOOTH |
| native_data_t *nat = get_native_data(env, obj); |
| if (nat->rfcomm_sock >= 0) { |
| close(nat->rfcomm_sock); |
| nat->rfcomm_sock = -1; |
| nat->rfcomm_connected = 0; |
| } |
| #endif |
| } |
| |
| static void pretty_log_urc(const char *urc) { |
| size_t i; |
| bool in_line_break = false; |
| char *buf = (char *)calloc(strlen(urc) + 1, sizeof(char)); |
| |
| strcpy(buf, urc); |
| for (i = 0; i < strlen(buf); i++) { |
| switch(buf[i]) { |
| case '\r': |
| case '\n': |
| in_line_break = true; |
| buf[i] = ' '; |
| break; |
| default: |
| if (in_line_break) { |
| in_line_break = false; |
| buf[i-1] = '\n'; |
| } |
| } |
| } |
| LOG(LOG_INFO, "Bluetooth AT sent", buf); |
| |
| free(buf); |
| } |
| |
| static jboolean sendURCNative(JNIEnv *env, jobject obj, jstring urc) { |
| #ifdef HAVE_BLUETOOTH |
| native_data_t *nat = get_native_data(env, obj); |
| if (nat->rfcomm_connected) { |
| const char *c_urc = env->GetStringUTFChars(urc, NULL); |
| jboolean ret = send_line(nat->rfcomm_sock, c_urc) == 0 ? JNI_TRUE : JNI_FALSE; |
| if (ret == JNI_TRUE) pretty_log_urc(c_urc); |
| env->ReleaseStringUTFChars(urc, c_urc); |
| return ret; |
| } |
| #endif |
| return JNI_FALSE; |
| } |
| |
| static jstring readNative(JNIEnv *env, jobject obj, jint timeout_ms) { |
| #ifdef HAVE_BLUETOOTH |
| { |
| native_data_t *nat = get_native_data(env, obj); |
| if (nat->rfcomm_connected) { |
| char buf[256]; |
| const char *ret = get_line(nat->rfcomm_sock, |
| buf, sizeof(buf), |
| timeout_ms, |
| &nat->last_read_err); |
| return ret ? env->NewStringUTF(ret) : NULL; |
| } |
| return NULL; |
| } |
| #else |
| return NULL; |
| #endif |
| } |
| |
| static jint getLastReadStatusNative(JNIEnv *env, jobject obj) { |
| #ifdef HAVE_BLUETOOTH |
| { |
| native_data_t *nat = get_native_data(env, obj); |
| if (nat->rfcomm_connected) |
| return (jint)nat->last_read_err; |
| return 0; |
| } |
| #else |
| return 0; |
| #endif |
| } |
| |
| static JNINativeMethod sMethods[] = { |
| /* name, signature, funcPtr */ |
| {"classInitNative", "()V", (void*)classInitNative}, |
| {"initializeNativeDataNative", "(I)V", (void *)initializeNativeDataNative}, |
| {"cleanupNativeDataNative", "()V", (void *)cleanupNativeDataNative}, |
| {"connectNative", "()Z", (void *)connectNative}, |
| {"connectAsyncNative", "()I", (void *)connectAsyncNative}, |
| {"waitForAsyncConnectNative", "(I)I", (void *)waitForAsyncConnectNative}, |
| {"disconnectNative", "()V", (void *)disconnectNative}, |
| {"sendURCNative", "(Ljava/lang/String;)Z", (void *)sendURCNative}, |
| {"readNative", "(I)Ljava/lang/String;", (void *)readNative}, |
| {"getLastReadStatusNative", "()I", (void *)getLastReadStatusNative}, |
| }; |
| |
| int register_android_bluetooth_HeadsetBase(JNIEnv *env) { |
| return AndroidRuntime::registerNativeMethods(env, |
| "android/bluetooth/HeadsetBase", sMethods, NELEM(sMethods)); |
| } |
| |
| } /* namespace android */ |