Merge "deleted ScoSocket.java and android_bluetooth_ScoSocket.cpp"
This commit is contained in:
@@ -1,206 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
package android.bluetooth;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* The Android Bluetooth API is not finalized, and *will* change. Use at your
|
||||
* own risk.
|
||||
*
|
||||
* Simple SCO Socket.
|
||||
* Currently in Android, there is no support for sending data over a SCO
|
||||
* socket - this is managed by the hardware link to the Bluetooth Chip. This
|
||||
* class is instead intended for management of the SCO socket lifetime,
|
||||
* and is tailored for use with the headset / handsfree profiles.
|
||||
* @hide
|
||||
*/
|
||||
public class ScoSocket {
|
||||
private static final String TAG = "ScoSocket";
|
||||
private static final boolean DBG = true;
|
||||
private static final boolean VDBG = false; // even more logging
|
||||
|
||||
public static final int STATE_READY = 1; // Ready for use. No threads or sockets
|
||||
public static final int STATE_ACCEPT = 2; // accept() thread running
|
||||
public static final int STATE_CONNECTING = 3; // connect() thread running
|
||||
public static final int STATE_CONNECTED = 4; // connected, waiting for close()
|
||||
public static final int STATE_CLOSED = 5; // was connected, now closed.
|
||||
|
||||
private int mState;
|
||||
private int mNativeData;
|
||||
private Handler mHandler;
|
||||
private int mAcceptedCode;
|
||||
private int mConnectedCode;
|
||||
private int mClosedCode;
|
||||
|
||||
private WakeLock mWakeLock; // held while in STATE_CONNECTING
|
||||
|
||||
static {
|
||||
classInitNative();
|
||||
}
|
||||
private native static void classInitNative();
|
||||
|
||||
public ScoSocket(PowerManager pm, Handler handler, int acceptedCode, int connectedCode,
|
||||
int closedCode) {
|
||||
initNative();
|
||||
mState = STATE_READY;
|
||||
mHandler = handler;
|
||||
mAcceptedCode = acceptedCode;
|
||||
mConnectedCode = connectedCode;
|
||||
mClosedCode = closedCode;
|
||||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ScoSocket");
|
||||
mWakeLock.setReferenceCounted(false);
|
||||
if (VDBG) log(this + " SCO OBJECT CTOR");
|
||||
}
|
||||
private native void initNative();
|
||||
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
if (VDBG) log(this + " SCO OBJECT DTOR");
|
||||
destroyNative();
|
||||
releaseWakeLockNow();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
private native void destroyNative();
|
||||
|
||||
/** Connect this SCO socket to the given BT address.
|
||||
* Does not block.
|
||||
*/
|
||||
public synchronized boolean connect(String address, String name) {
|
||||
if (DBG) log("connect() " + this);
|
||||
if (mState != STATE_READY) {
|
||||
if (DBG) log("connect(): Bad state");
|
||||
return false;
|
||||
}
|
||||
acquireWakeLock();
|
||||
if (connectNative(address, name)) {
|
||||
mState = STATE_CONNECTING;
|
||||
return true;
|
||||
} else {
|
||||
mState = STATE_CLOSED;
|
||||
releaseWakeLockNow();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private native boolean connectNative(String address, String name);
|
||||
|
||||
/** Accept incoming SCO connections.
|
||||
* Does not block.
|
||||
*/
|
||||
public synchronized boolean accept() {
|
||||
if (VDBG) log("accept() " + this);
|
||||
if (mState != STATE_READY) {
|
||||
if (DBG) log("Bad state");
|
||||
return false;
|
||||
}
|
||||
if (acceptNative()) {
|
||||
mState = STATE_ACCEPT;
|
||||
return true;
|
||||
} else {
|
||||
mState = STATE_CLOSED;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private native boolean acceptNative();
|
||||
|
||||
public synchronized void close() {
|
||||
if (DBG) log(this + " SCO OBJECT close() mState = " + mState);
|
||||
acquireWakeLock();
|
||||
mState = STATE_CLOSED;
|
||||
closeNative();
|
||||
releaseWakeLock();
|
||||
}
|
||||
private native void closeNative();
|
||||
|
||||
public synchronized int getState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
private synchronized void onConnected(int result) {
|
||||
if (VDBG) log(this + " onConnected() mState = " + mState + " " + this);
|
||||
if (mState != STATE_CONNECTING) {
|
||||
if (DBG) log("Strange state, closing " + mState + " " + this);
|
||||
return;
|
||||
}
|
||||
if (result >= 0) {
|
||||
mState = STATE_CONNECTED;
|
||||
} else {
|
||||
mState = STATE_CLOSED;
|
||||
}
|
||||
mHandler.obtainMessage(mConnectedCode, mState, -1, this).sendToTarget();
|
||||
releaseWakeLockNow();
|
||||
}
|
||||
|
||||
private synchronized void onAccepted(int result) {
|
||||
if (VDBG) log("onAccepted() " + this);
|
||||
if (mState != STATE_ACCEPT) {
|
||||
if (DBG) log("Strange state " + this);
|
||||
return;
|
||||
}
|
||||
if (result >= 0) {
|
||||
mState = STATE_CONNECTED;
|
||||
} else {
|
||||
mState = STATE_CLOSED;
|
||||
}
|
||||
mHandler.obtainMessage(mAcceptedCode, mState, -1, this).sendToTarget();
|
||||
}
|
||||
|
||||
private synchronized void onClosed() {
|
||||
if (DBG) log("onClosed() " + this);
|
||||
if (mState != STATE_CLOSED) {
|
||||
mState = STATE_CLOSED;
|
||||
mHandler.obtainMessage(mClosedCode, mState, -1, this).sendToTarget();
|
||||
releaseWakeLock();
|
||||
}
|
||||
}
|
||||
|
||||
private void acquireWakeLock() {
|
||||
if (!mWakeLock.isHeld()) {
|
||||
mWakeLock.acquire();
|
||||
if (VDBG) log("mWakeLock.acquire() " + this);
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseWakeLock() {
|
||||
if (mWakeLock.isHeld()) {
|
||||
// Keep apps processor awake for a further 2 seconds.
|
||||
// This is a hack to resolve issue http://b/1616263 - in which
|
||||
// we are left in a 80 mA power state when remotely terminating a
|
||||
// call while connected to BT headset "HTC BH S100 " with A2DP and
|
||||
// HFP profiles.
|
||||
if (VDBG) log("mWakeLock.release() in 2 sec" + this);
|
||||
mWakeLock.acquire(2000);
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseWakeLockNow() {
|
||||
if (mWakeLock.isHeld()) {
|
||||
if (VDBG) log("mWakeLock.release() now" + this);
|
||||
mWakeLock.release();
|
||||
}
|
||||
}
|
||||
|
||||
private void log(String msg) {
|
||||
Log.d(TAG, msg);
|
||||
}
|
||||
}
|
||||
@@ -1,689 +0,0 @@
|
||||
/*
|
||||
** 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>
|
||||
#include <bluetooth/hci.h>
|
||||
|
||||
#define MAX_LINE 255
|
||||
|
||||
/*
|
||||
* Defines the module strings used in the blacklist file.
|
||||
* These are used by consumers of the blacklist file to see if the line is
|
||||
* used by that module.
|
||||
*/
|
||||
#define SCO_BLACKLIST_MODULE_NAME "scoSocket"
|
||||
|
||||
|
||||
/* Define the type strings used in the blacklist file. */
|
||||
#define BLACKLIST_BY_NAME "name"
|
||||
#define BLACKLIST_BY_PARTIAL_NAME "partial_name"
|
||||
#define BLACKLIST_BY_OUI "vendor_oui"
|
||||
|
||||
#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, uint16_t sco_pkt_type);
|
||||
static int accept_work(int signal_sk);
|
||||
static void wait_for_close(int sk, int signal_sk);
|
||||
static void closeNative(JNIEnv *env, jobject object);
|
||||
|
||||
static void parseBlacklist(void);
|
||||
static uint16_t getScoType(char *address, const char *name);
|
||||
|
||||
#define COMPARE_STRING(key, s) (!strncmp(key, s, strlen(s)))
|
||||
|
||||
/* Blacklist data */
|
||||
typedef struct scoBlacklist {
|
||||
int fieldType;
|
||||
char *value;
|
||||
uint16_t scoType;
|
||||
struct scoBlacklist *next;
|
||||
} scoBlacklist_t;
|
||||
|
||||
#define BL_TYPE_NAME 1 // Field type is name string
|
||||
|
||||
static scoBlacklist_t *blacklist = NULL;
|
||||
|
||||
/* 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
|
||||
uint16_t sco_pkt_type; // SCO packet types supported
|
||||
};
|
||||
|
||||
static inline native_data_t * get_native_data(JNIEnv *env, jobject object) {
|
||||
return (native_data_t *)(env->GetIntField(object, field_mNativeData));
|
||||
}
|
||||
|
||||
static uint16_t str2scoType (char *key) {
|
||||
LOGV("%s: key = %s", __FUNCTION__, key);
|
||||
if (COMPARE_STRING(key, "ESCO_HV1"))
|
||||
return ESCO_HV1;
|
||||
if (COMPARE_STRING(key, "ESCO_HV2"))
|
||||
return ESCO_HV2;
|
||||
if (COMPARE_STRING(key, "ESCO_HV3"))
|
||||
return ESCO_HV3;
|
||||
if (COMPARE_STRING(key, "ESCO_EV3"))
|
||||
return ESCO_EV3;
|
||||
if (COMPARE_STRING(key, "ESCO_EV4"))
|
||||
return ESCO_EV4;
|
||||
if (COMPARE_STRING(key, "ESCO_EV5"))
|
||||
return ESCO_EV5;
|
||||
if (COMPARE_STRING(key, "ESCO_2EV3"))
|
||||
return ESCO_2EV3;
|
||||
if (COMPARE_STRING(key, "ESCO_3EV3"))
|
||||
return ESCO_3EV3;
|
||||
if (COMPARE_STRING(key, "ESCO_2EV5"))
|
||||
return ESCO_2EV5;
|
||||
if (COMPARE_STRING(key, "ESCO_3EV5"))
|
||||
return ESCO_3EV5;
|
||||
if (COMPARE_STRING(key, "SCO_ESCO_MASK"))
|
||||
return SCO_ESCO_MASK;
|
||||
if (COMPARE_STRING(key, "EDR_ESCO_MASK"))
|
||||
return EDR_ESCO_MASK;
|
||||
if (COMPARE_STRING(key, "ALL_ESCO_MASK"))
|
||||
return ALL_ESCO_MASK;
|
||||
LOGE("Unknown SCO Type (%s) skipping",key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void parseBlacklist(void) {
|
||||
const char *filename = "/etc/bluetooth/blacklist.conf";
|
||||
char line[MAX_LINE];
|
||||
scoBlacklist_t *list = NULL;
|
||||
scoBlacklist_t *newelem;
|
||||
|
||||
LOGV(__FUNCTION__);
|
||||
|
||||
/* Open file */
|
||||
FILE *fp = fopen(filename, "r");
|
||||
if(!fp) {
|
||||
LOGE("Error(%s)opening blacklist file", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
while (fgets(line, MAX_LINE, fp) != NULL) {
|
||||
if ((COMPARE_STRING(line, "//")) || (!strcmp(line, "")))
|
||||
continue;
|
||||
char *module = strtok(line,":");
|
||||
if (COMPARE_STRING(module, SCO_BLACKLIST_MODULE_NAME)) {
|
||||
newelem = (scoBlacklist_t *)calloc(1, sizeof(scoBlacklist_t));
|
||||
if (newelem == NULL) {
|
||||
LOGE("%s: out of memory!", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
// parse line
|
||||
char *type = strtok(NULL, ",");
|
||||
char *valueList = strtok(NULL, ",");
|
||||
char *paramList = strtok(NULL, ",");
|
||||
if (COMPARE_STRING(type, BLACKLIST_BY_NAME)) {
|
||||
// Extract Name from Value list
|
||||
newelem->fieldType = BL_TYPE_NAME;
|
||||
newelem->value = (char *)calloc(1, strlen(valueList));
|
||||
if (newelem->value == NULL) {
|
||||
LOGE("%s: out of memory!", __FUNCTION__);
|
||||
continue;
|
||||
}
|
||||
valueList++; // Skip open quote
|
||||
strncpy(newelem->value, valueList, strlen(valueList) - 1);
|
||||
|
||||
// Get Sco Settings from Parameters
|
||||
char *param = strtok(paramList, ";");
|
||||
uint16_t scoTypes = 0;
|
||||
while (param != NULL) {
|
||||
uint16_t sco;
|
||||
if (param[0] == '-') {
|
||||
param++;
|
||||
sco = str2scoType(param);
|
||||
if (sco != 0)
|
||||
scoTypes &= ~sco;
|
||||
} else if (param[0] == '+') {
|
||||
param++;
|
||||
sco = str2scoType(param);
|
||||
if (sco != 0)
|
||||
scoTypes |= sco;
|
||||
} else if (param[0] == '=') {
|
||||
param++;
|
||||
sco = str2scoType(param);
|
||||
if (sco != 0)
|
||||
scoTypes = sco;
|
||||
} else {
|
||||
LOGE("Invalid SCO type must be =, + or -");
|
||||
}
|
||||
param = strtok(NULL, ";");
|
||||
}
|
||||
newelem->scoType = scoTypes;
|
||||
} else {
|
||||
LOGE("Unknown SCO type entry in Blacklist file");
|
||||
continue;
|
||||
}
|
||||
if (list) {
|
||||
list->next = newelem;
|
||||
list = newelem;
|
||||
} else {
|
||||
blacklist = list = newelem;
|
||||
}
|
||||
LOGI("Entry name = %s ScoTypes = 0x%x", newelem->value,
|
||||
newelem->scoType);
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
return;
|
||||
}
|
||||
static uint16_t getScoType(char *address, const char *name) {
|
||||
uint16_t ret = 0;
|
||||
scoBlacklist_t *list = blacklist;
|
||||
|
||||
while (list != NULL) {
|
||||
if (list->fieldType == BL_TYPE_NAME) {
|
||||
if (COMPARE_STRING(name, list->value)) {
|
||||
ret = list->scoType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
list = list->next;
|
||||
}
|
||||
LOGI("%s %s - 0x%x", __FUNCTION__, name, ret);
|
||||
return ret;
|
||||
}
|
||||
#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");
|
||||
|
||||
/* Read the blacklist file in here */
|
||||
parseBlacklist();
|
||||
#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,
|
||||
jstring name) {
|
||||
|
||||
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;
|
||||
const char *c_name;
|
||||
|
||||
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 (name == NULL) {
|
||||
LOGE("%s: Null pointer passed in for device name", __FUNCTION__);
|
||||
data->sco_pkt_type = 0;
|
||||
} else {
|
||||
c_name = env->GetStringUTFChars(name, NULL);
|
||||
/* See if this device is in the black list */
|
||||
data->sco_pkt_type = getScoType(data->address, c_name);
|
||||
env->ReleaseStringUTFChars(name, c_name);
|
||||
}
|
||||
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, data->sco_pkt_type);
|
||||
}
|
||||
|
||||
/* 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, uint16_t sco_pkt_type) {
|
||||
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);
|
||||
addr.sco_pkt_type = sco_pkt_type;
|
||||
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;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 */
|
||||
Reference in New Issue
Block a user