summaryrefslogtreecommitdiffstats
path: root/core/jni/android_bluetooth_BluetoothAudioGateway.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'core/jni/android_bluetooth_BluetoothAudioGateway.cpp')
-rwxr-xr-xcore/jni/android_bluetooth_BluetoothAudioGateway.cpp553
1 files changed, 553 insertions, 0 deletions
diff --git a/core/jni/android_bluetooth_BluetoothAudioGateway.cpp b/core/jni/android_bluetooth_BluetoothAudioGateway.cpp
new file mode 100755
index 0000000..7f87d80
--- /dev/null
+++ b/core/jni/android_bluetooth_BluetoothAudioGateway.cpp
@@ -0,0 +1,553 @@
+/*
+** 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 "BluetoothAudioGateway.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"
+
+#define USE_ACCEPT_DIRECTLY (0)
+#define USE_SELECT (0) /* 1 for select(), 0 for poll(); used only when
+ USE_ACCEPT_DIRECTLY == 0 */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/uio.h>
+#include <ctype.h>
+
+#if USE_SELECT
+#include <sys/select.h>
+#else
+#include <sys/poll.h>
+#endif
+
+#ifdef HAVE_BLUETOOTH
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sco.h>
+#endif
+
+namespace android {
+
+#ifdef HAVE_BLUETOOTH
+static jfieldID field_mNativeData;
+ /* in */
+static jfieldID field_mHandsfreeAgRfcommChannel;
+static jfieldID field_mHeadsetAgRfcommChannel;
+ /* out */
+static jfieldID field_mTimeoutRemainingMs; /* out */
+
+static jfieldID field_mConnectingHeadsetAddress;
+static jfieldID field_mConnectingHeadsetRfcommChannel; /* -1 when not connected */
+static jfieldID field_mConnectingHeadsetSocketFd;
+
+static jfieldID field_mConnectingHandsfreeAddress;
+static jfieldID field_mConnectingHandsfreeRfcommChannel; /* -1 when not connected */
+static jfieldID field_mConnectingHandsfreeSocketFd;
+
+
+typedef struct {
+ int hcidev;
+ int hf_ag_rfcomm_channel;
+ int hs_ag_rfcomm_channel;
+ int hf_ag_rfcomm_sock;
+ int hs_ag_rfcomm_sock;
+} 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 int setup_listening_socket(int dev, int channel);
+#endif
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+ LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+
+ /* in */
+ field_mNativeData = get_field(env, clazz, "mNativeData", "I");
+ field_mHandsfreeAgRfcommChannel =
+ get_field(env, clazz, "mHandsfreeAgRfcommChannel", "I");
+ field_mHeadsetAgRfcommChannel =
+ get_field(env, clazz, "mHeadsetAgRfcommChannel", "I");
+
+ /* out */
+ field_mConnectingHeadsetAddress =
+ get_field(env, clazz,
+ "mConnectingHeadsetAddress", "Ljava/lang/String;");
+ field_mConnectingHeadsetRfcommChannel =
+ get_field(env, clazz, "mConnectingHeadsetRfcommChannel", "I");
+ field_mConnectingHeadsetSocketFd =
+ get_field(env, clazz, "mConnectingHeadsetSocketFd", "I");
+
+ field_mConnectingHandsfreeAddress =
+ get_field(env, clazz,
+ "mConnectingHandsfreeAddress", "Ljava/lang/String;");
+ field_mConnectingHandsfreeRfcommChannel =
+ get_field(env, clazz, "mConnectingHandsfreeRfcommChannel", "I");
+ field_mConnectingHandsfreeSocketFd =
+ get_field(env, clazz, "mConnectingHandsfreeSocketFd", "I");
+
+ field_mTimeoutRemainingMs =
+ get_field(env, clazz, "mTimeoutRemainingMs", "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 (NULL == nat) {
+ LOGE("%s: out of memory!", __FUNCTION__);
+ return;
+ }
+
+ nat->hcidev = BLUETOOTH_ADAPTER_HCI_NUM;
+
+ env->SetIntField(object, field_mNativeData, (jint)nat);
+ nat->hf_ag_rfcomm_channel =
+ env->GetIntField(object, field_mHandsfreeAgRfcommChannel);
+ nat->hs_ag_rfcomm_channel =
+ env->GetIntField(object, field_mHeadsetAgRfcommChannel);
+ LOGV("HF RFCOMM channel = %d.", nat->hf_ag_rfcomm_channel);
+ LOGV("HS RFCOMM channel = %d.", nat->hs_ag_rfcomm_channel);
+
+ /* Set the default values of these to -1. */
+ env->SetIntField(object, field_mConnectingHeadsetRfcommChannel, -1);
+ env->SetIntField(object, field_mConnectingHandsfreeRfcommChannel, -1);
+
+ nat->hf_ag_rfcomm_sock = -1;
+ nat->hs_ag_rfcomm_sock = -1;
+#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
+}
+
+#ifdef HAVE_BLUETOOTH
+
+#if USE_ACCEPT_DIRECTLY==0
+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;
+}
+#endif /*USE_ACCEPT_DIRECTLY==0*/
+
+static int do_accept(JNIEnv* env, jobject object, int ag_fd,
+ jfieldID out_fd,
+ jfieldID out_address,
+ jfieldID out_channel) {
+
+#if USE_ACCEPT_DIRECTLY==0
+ if (set_nb(ag_fd, true) < 0)
+ return -1;
+#endif
+
+ struct sockaddr_rc raddr;
+ int alen = sizeof(raddr);
+ int nsk = accept(ag_fd, (struct sockaddr *) &raddr, &alen);
+ if (nsk < 0) {
+ LOGE("Error on accept from socket fd %d: %s (%d).",
+ ag_fd,
+ strerror(errno),
+ errno);
+#if USE_ACCEPT_DIRECTLY==0
+ set_nb(ag_fd, false);
+#endif
+ return -1;
+ }
+
+ env->SetIntField(object, out_fd, nsk);
+ env->SetIntField(object, out_channel, raddr.rc_channel);
+
+ char addr[BTADDR_SIZE];
+ get_bdaddr_as_string(&raddr.rc_bdaddr, addr);
+ env->SetObjectField(object, out_address, env->NewStringUTF(addr));
+
+ LOGI("Successful accept() on AG socket %d: new socket %d, address %s, RFCOMM channel %d",
+ ag_fd,
+ nsk,
+ addr,
+ raddr.rc_channel);
+#if USE_ACCEPT_DIRECTLY==0
+ set_nb(ag_fd, false);
+#endif
+ return 0;
+}
+
+#if USE_SELECT
+static inline int on_accept_set_fields(JNIEnv* env, jobject object,
+ fd_set *rset, int ag_fd,
+ jfieldID out_fd,
+ jfieldID out_address,
+ jfieldID out_channel) {
+
+ env->SetIntField(object, out_channel, -1);
+
+ if (ag_fd >= 0 && FD_ISSET(ag_fd, &rset)) {
+ return do_accept(env, object, ag_fd,
+ out_fd, out_address, out_channel);
+ }
+ else {
+ LOGI("fd = %d, FD_ISSET() = %d",
+ ag_fd,
+ FD_ISSET(ag_fd, &rset));
+ if (ag_fd >= 0 && !FD_ISSET(ag_fd, &rset)) {
+ LOGE("WTF???");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+#endif
+#endif /* HAVE_BLUETOOTH */
+
+static jboolean waitForHandsfreeConnectNative(JNIEnv* env, jobject object,
+ jint timeout_ms) {
+// LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+
+ env->SetIntField(object, field_mTimeoutRemainingMs, timeout_ms);
+
+ int n = 0;
+ native_data_t *nat = get_native_data(env, object);
+#if USE_ACCEPT_DIRECTLY
+ if (nat->hf_ag_rfcomm_channel > 0) {
+ LOGI("Setting HF AG server socket to RFCOMM port %d!",
+ nat->hf_ag_rfcomm_channel);
+ struct timeval tv;
+ int len = sizeof(tv);
+ if (getsockopt(nat->hf_ag_rfcomm_channel,
+ SOL_SOCKET, SO_RCVTIMEO, &tv, &len) < 0) {
+ LOGE("getsockopt(%d, SOL_SOCKET, SO_RCVTIMEO): %s (%d)",
+ nat->hf_ag_rfcomm_channel,
+ strerror(errno),
+ errno);
+ return JNI_FALSE;
+ }
+ LOGI("Current HF AG server socket RCVTIMEO is (%d(s), %d(us))!",
+ (int)tv.tv_sec, (int)tv.tv_usec);
+ if (timeout_ms >= 0) {
+ tv.tv_sec = timeout_ms / 1000;
+ tv.tv_usec = 1000 * (timeout_ms % 1000);
+ if (setsockopt(nat->hf_ag_rfcomm_channel,
+ SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
+ LOGE("setsockopt(%d, SOL_SOCKET, SO_RCVTIMEO): %s (%d)",
+ nat->hf_ag_rfcomm_channel,
+ strerror(errno),
+ errno);
+ return JNI_FALSE;
+ }
+ LOGI("Changed HF AG server socket RCVTIMEO to (%d(s), %d(us))!",
+ (int)tv.tv_sec, (int)tv.tv_usec);
+ }
+
+ if (!do_accept(env, object, nat->hf_ag_rfcomm_sock,
+ field_mConnectingHandsfreeSocketFd,
+ field_mConnectingHandsfreeAddress,
+ field_mConnectingHandsfreeRfcommChannel))
+ {
+ env->SetIntField(object, field_mTimeoutRemainingMs, 0);
+ return JNI_TRUE;
+ }
+ return JNI_FALSE;
+ }
+#else
+#if USE_SELECT
+ fd_set rset;
+ FD_ZERO(&rset);
+ int cnt = 0;
+ if (nat->hf_ag_rfcomm_channel > 0) {
+ LOGI("Setting HF AG server socket to RFCOMM port %d!",
+ nat->hf_ag_rfcomm_channel);
+ cnt++;
+ FD_SET(nat->hf_ag_rfcomm_sock, &rset);
+ }
+ if (nat->hs_ag_rfcomm_channel > 0) {
+ LOGI("Setting HS AG server socket to RFCOMM port %d!",
+ nat->hs_ag_rfcomm_channel);
+ cnt++;
+ FD_SET(nat->hs_ag_rfcomm_sock, &rset);
+ }
+ if (cnt == 0) {
+ LOGE("Neither HF nor HS listening sockets are open!");
+ return JNI_FALSE;
+ }
+
+ struct timeval to;
+ if (timeout_ms >= 0) {
+ to.tv_sec = timeout_ms / 1000;
+ to.tv_usec = 1000 * (timeout_ms % 1000);
+ }
+ n = select(MAX(nat->hf_ag_rfcomm_sock,
+ nat->hs_ag_rfcomm_sock) + 1,
+ &rset,
+ NULL,
+ 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(object, field_mTimeoutRemainingMs,
+ remaining);
+ }
+
+ LOGI("listening select() returned %d", n);
+
+ if (n <= 0) {
+ if (n < 0) {
+ LOGE("listening select() on RFCOMM sockets: %s (%d)",
+ strerror(errno),
+ errno);
+ }
+ return JNI_FALSE;
+ }
+
+ n = on_accept_set_fields(env, object,
+ &rset, nat->hf_ag_rfcomm_sock,
+ field_mConnectingHandsfreeSocketFd,
+ field_mConnectingHandsfreeAddress,
+ field_mConnectingHandsfreeRfcommChannel);
+
+ n += on_accept_set_fields(env, object,
+ &rset, nat->hs_ag_rfcomm_sock,
+ field_mConnectingHeadsetSocketFd,
+ field_mConnectingHeadsetAddress,
+ field_mConnectingHeadsetRfcommChannel);
+
+ return !n ? JNI_TRUE : JNI_FALSE;
+#else
+ struct pollfd fds[2];
+ int cnt = 0;
+ if (nat->hf_ag_rfcomm_channel > 0) {
+// LOGI("Setting HF AG server socket %d to RFCOMM port %d!",
+// nat->hf_ag_rfcomm_sock,
+// nat->hf_ag_rfcomm_channel);
+ fds[cnt].fd = nat->hf_ag_rfcomm_sock;
+ fds[cnt].events = POLLIN | POLLPRI | POLLOUT | POLLERR;
+ cnt++;
+ }
+ if (nat->hs_ag_rfcomm_channel > 0) {
+// LOGI("Setting HS AG server socket %d to RFCOMM port %d!",
+// nat->hs_ag_rfcomm_sock,
+// nat->hs_ag_rfcomm_channel);
+ fds[cnt].fd = nat->hs_ag_rfcomm_sock;
+ fds[cnt].events = POLLIN | POLLPRI | POLLOUT | POLLERR;
+ cnt++;
+ }
+ if (cnt == 0) {
+ LOGE("Neither HF nor HS listening sockets are open!");
+ return JNI_FALSE;
+ }
+ n = poll(fds, cnt, timeout_ms);
+ if (n <= 0) {
+ if (n < 0) {
+ LOGE("listening poll() on RFCOMM sockets: %s (%d)",
+ strerror(errno),
+ errno);
+ }
+ else {
+ env->SetIntField(object, field_mTimeoutRemainingMs, 0);
+// LOGI("listening poll() on RFCOMM socket timed out");
+ }
+ return JNI_FALSE;
+ }
+
+ //LOGI("listening poll() on RFCOMM socket returned %d", n);
+ int err = 0;
+ for (cnt = 0; cnt < (int)(sizeof(fds)/sizeof(fds[0])); cnt++) {
+ //LOGI("Poll on fd %d revent = %d.", fds[cnt].fd, fds[cnt].revents);
+ if (fds[cnt].fd == nat->hf_ag_rfcomm_sock) {
+ if (fds[cnt].revents & (POLLIN | POLLPRI | POLLOUT)) {
+ LOGI("Accepting HF connection.\n");
+ err += do_accept(env, object, fds[cnt].fd,
+ field_mConnectingHandsfreeSocketFd,
+ field_mConnectingHandsfreeAddress,
+ field_mConnectingHandsfreeRfcommChannel);
+ n--;
+ }
+ }
+ else if (fds[cnt].fd == nat->hs_ag_rfcomm_sock) {
+ if (fds[cnt].revents & (POLLIN | POLLPRI | POLLOUT)) {
+ LOGI("Accepting HS connection.\n");
+ err += do_accept(env, object, fds[cnt].fd,
+ field_mConnectingHeadsetSocketFd,
+ field_mConnectingHeadsetAddress,
+ field_mConnectingHeadsetRfcommChannel);
+ n--;
+ }
+ }
+ } /* for */
+
+ if (n != 0) {
+ LOGI("Bogus poll(): %d fake pollfd entrie(s)!", n);
+ return JNI_FALSE;
+ }
+
+ return !err ? JNI_TRUE : JNI_FALSE;
+#endif /* USE_SELECT */
+#endif /* USE_ACCEPT_DIRECTLY */
+#else
+ return JNI_FALSE;
+#endif /* HAVE_BLUETOOTH */
+}
+
+static jboolean setUpListeningSocketsNative(JNIEnv* env, jobject object) {
+ LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+ native_data_t *nat = get_native_data(env, object);
+
+ nat->hf_ag_rfcomm_sock =
+ setup_listening_socket(nat->hcidev, nat->hf_ag_rfcomm_channel);
+ if (nat->hf_ag_rfcomm_sock < 0)
+ return JNI_FALSE;
+
+ nat->hs_ag_rfcomm_sock =
+ setup_listening_socket(nat->hcidev, nat->hs_ag_rfcomm_channel);
+ if (nat->hs_ag_rfcomm_sock < 0) {
+ close(nat->hf_ag_rfcomm_sock);
+ nat->hf_ag_rfcomm_sock = -1;
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+#else
+ return JNI_FALSE;
+#endif /* HAVE_BLUETOOTH */
+}
+
+#ifdef HAVE_BLUETOOTH
+static int setup_listening_socket(int dev, int channel) {
+ struct sockaddr_rc laddr;
+ int sk, lm;
+
+ sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+ if (sk < 0) {
+ LOGE("Can't create RFCOMM socket");
+ return -1;
+ }
+
+ if (debug_no_encrypt()) {
+ lm = RFCOMM_LM_AUTH;
+ } else {
+ lm = RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT;
+ }
+
+ if (lm && setsockopt(sk, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm)) < 0) {
+ LOGE("Can't set RFCOMM link mode");
+ close(sk);
+ return -1;
+ }
+
+ laddr.rc_family = AF_BLUETOOTH;
+ bacpy(&laddr.rc_bdaddr, BDADDR_ANY);
+ laddr.rc_channel = channel;
+
+ if (bind(sk, (struct sockaddr *)&laddr, sizeof(laddr)) < 0) {
+ LOGE("Can't bind RFCOMM socket");
+ close(sk);
+ return -1;
+ }
+
+ listen(sk, 10);
+ return sk;
+}
+#endif /* HAVE_BLUETOOTH */
+
+/*
+ private native void tearDownListeningSocketsNative();
+*/
+static void tearDownListeningSocketsNative(JNIEnv *env, jobject object) {
+ LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+ native_data_t *nat = get_native_data(env, object);
+
+ if (nat->hf_ag_rfcomm_sock > 0) {
+ if (close(nat->hf_ag_rfcomm_sock) < 0) {
+ LOGE("Could not close HF server socket: %s (%d)\n",
+ strerror(errno), errno);
+ }
+ nat->hf_ag_rfcomm_sock = -1;
+ }
+ if (nat->hs_ag_rfcomm_sock > 0) {
+ if (close(nat->hs_ag_rfcomm_sock) < 0) {
+ LOGE("Could not close HS server socket: %s (%d)\n",
+ strerror(errno), errno);
+ }
+ nat->hs_ag_rfcomm_sock = -1;
+ }
+#endif /* HAVE_BLUETOOTH */
+}
+
+static JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+
+ {"classInitNative", "()V", (void*)classInitNative},
+ {"initializeNativeDataNative", "()V", (void *)initializeNativeDataNative},
+ {"cleanupNativeDataNative", "()V", (void *)cleanupNativeDataNative},
+
+ {"setUpListeningSocketsNative", "()Z", (void *)setUpListeningSocketsNative},
+ {"tearDownListeningSocketsNative", "()V", (void *)tearDownListeningSocketsNative},
+ {"waitForHandsfreeConnectNative", "(I)Z", (void *)waitForHandsfreeConnectNative},
+};
+
+int register_android_bluetooth_BluetoothAudioGateway(JNIEnv *env) {
+ return AndroidRuntime::registerNativeMethods(env,
+ "android/bluetooth/BluetoothAudioGateway", sMethods,
+ NELEM(sMethods));
+}
+
+} /* namespace android */