summaryrefslogtreecommitdiffstats
path: root/core/jni/android_bluetooth_HeadsetBase.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'core/jni/android_bluetooth_HeadsetBase.cpp')
-rw-r--r--core/jni/android_bluetooth_HeadsetBase.cpp548
1 files changed, 548 insertions, 0 deletions
diff --git a/core/jni/android_bluetooth_HeadsetBase.cpp b/core/jni/android_bluetooth_HeadsetBase.cpp
new file mode 100644
index 0000000..bb19e92
--- /dev/null
+++ b/core/jni/android_bluetooth_HeadsetBase.cpp
@@ -0,0 +1,548 @@
+/*
+** 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 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)
+ {
+ 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 = '\x0';
+ LOG(LOG_INFO, "Bluetooth AT recv", buf);
+
+ 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 jboolean 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 JNI_TRUE;
+ }
+
+ 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 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;
+ }
+ 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 JNI_TRUE;
+ }
+ else if (rc < 0) {
+ if (errno == EINPROGRESS || errno == EAGAIN)
+ {
+ LOGI("async connect is in progress (%s)",
+ strerror(errno));
+ nat->rfcomm_connected = -1;
+ return JNI_TRUE;
+ }
+ else
+ {
+ LOGE("async connect error: %s (%d)", strerror(errno), errno);
+ close(nat->rfcomm_sock);
+ nat->rfcomm_sock = -1;
+ return JNI_FALSE;
+ }
+ }
+ } // fcntl(nat->rfcomm_sock ...)
+ } // if (nat->rfcomm_sock_flags >= 0)
+#endif
+ return JNI_FALSE;
+}
+
+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;
+ }
+ if (JNI_FALSE == connectAsyncNative(env, obj)) {
+ LOGI("Failed to re-open RFCOMM socket!");
+ return -1;
+ }
+
+ 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 -1;
+ }
+ 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 -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 = 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[128];
+ 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", "()Z", (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 */