1/* 2** Copyright 2008, The Android Open Source Project 3** 4** Licensed under the Apache License, Version 2.0 (the "License"); 5** you may not use this file except in compliance with the License. 6** You may obtain a copy of the License at 7** 8** http://www.apache.org/licenses/LICENSE-2.0 9** 10** Unless required by applicable law or agreed to in writing, software 11** distributed under the License is distributed on an "AS IS" BASIS, 12** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13** See the License for the specific language governing permissions and 14** limitations under the License. 15*/ 16 17#define LOG_TAG "bluetooth_ScoSocket.cpp" 18 19#include "android_bluetooth_common.h" 20#include "android_runtime/AndroidRuntime.h" 21#include "JNIHelp.h" 22#include "jni.h" 23#include "utils/Log.h" 24#include "utils/misc.h" 25 26#include <stdio.h> 27#include <string.h> 28#include <stdlib.h> 29#include <errno.h> 30#include <unistd.h> 31#include <pthread.h> 32#include <sys/socket.h> 33#include <sys/types.h> 34#include <sys/uio.h> 35#include <sys/poll.h> 36 37#ifdef HAVE_BLUETOOTH 38#include <bluetooth/bluetooth.h> 39#include <bluetooth/sco.h> 40#include <bluetooth/hci.h> 41 42#define MAX_LINE 255 43 44/* 45 * Defines the module strings used in the blacklist file. 46 * These are used by consumers of the blacklist file to see if the line is 47 * used by that module. 48 */ 49#define SCO_BLACKLIST_MODULE_NAME "scoSocket" 50 51 52/* Define the type strings used in the blacklist file. */ 53#define BLACKLIST_BY_NAME "name" 54#define BLACKLIST_BY_PARTIAL_NAME "partial_name" 55#define BLACKLIST_BY_OUI "vendor_oui" 56 57#endif 58 59/* Ideally, blocking I/O on a SCO socket would return when another thread 60 * calls close(). However it does not right now, in fact close() on a SCO 61 * socket has strange behavior (returns a bogus value) when other threads 62 * are performing blocking I/O on that socket. So, to workaround, we always 63 * call close() from the same thread that does blocking I/O. This requires the 64 * use of a socketpair to signal the blocking I/O to abort. 65 * 66 * Unfortunately I don't know a way to abort connect() yet, but at least this 67 * times out after the BT page timeout (10 seconds currently), so the thread 68 * will die eventually. The fact that the thread can outlive 69 * the Java object forces us to use a mutex in destoryNative(). 70 * 71 * The JNI API is entirely async. 72 * 73 * Also note this class deals only with SCO connections, not with data 74 * transmission. 75 */ 76namespace android { 77#ifdef HAVE_BLUETOOTH 78 79static JavaVM *jvm; 80static jfieldID field_mNativeData; 81static jmethodID method_onAccepted; 82static jmethodID method_onConnected; 83static jmethodID method_onClosed; 84 85struct thread_data_t; 86static void *work_thread(void *arg); 87static int connect_work(const char *address, uint16_t sco_pkt_type); 88static int accept_work(int signal_sk); 89static void wait_for_close(int sk, int signal_sk); 90static void closeNative(JNIEnv *env, jobject object); 91 92static void parseBlacklist(void); 93static uint16_t getScoType(char *address, const char *name); 94 95#define COMPARE_STRING(key, s) (!strncmp(key, s, strlen(s))) 96 97/* Blacklist data */ 98typedef struct scoBlacklist { 99 int fieldType; 100 char *value; 101 uint16_t scoType; 102 struct scoBlacklist *next; 103} scoBlacklist_t; 104 105#define BL_TYPE_NAME 1 // Field type is name string 106 107static scoBlacklist_t *blacklist = NULL; 108 109/* shared native data - protected by mutex */ 110typedef struct { 111 pthread_mutex_t mutex; 112 int signal_sk; // socket to signal blocked I/O to unblock 113 jobject object; // JNI global ref to the Java object 114 thread_data_t *thread_data; // pointer to thread local data 115 // max 1 thread per sco socket 116} native_data_t; 117 118/* thread local data */ 119struct thread_data_t { 120 native_data_t *nat; 121 bool is_accept; // accept (listening) or connect (outgoing) thread 122 int signal_sk; // socket for thread to listen for unblock signal 123 char address[BTADDR_SIZE]; // BT addres as string 124 uint16_t sco_pkt_type; // SCO packet types supported 125}; 126 127static inline native_data_t * get_native_data(JNIEnv *env, jobject object) { 128 return (native_data_t *)(env->GetIntField(object, field_mNativeData)); 129} 130 131static uint16_t str2scoType (char *key) { 132 LOGV("%s: key = %s", __FUNCTION__, key); 133 if (COMPARE_STRING(key, "ESCO_HV1")) 134 return ESCO_HV1; 135 if (COMPARE_STRING(key, "ESCO_HV2")) 136 return ESCO_HV2; 137 if (COMPARE_STRING(key, "ESCO_HV3")) 138 return ESCO_HV3; 139 if (COMPARE_STRING(key, "ESCO_EV3")) 140 return ESCO_EV3; 141 if (COMPARE_STRING(key, "ESCO_EV4")) 142 return ESCO_EV4; 143 if (COMPARE_STRING(key, "ESCO_EV5")) 144 return ESCO_EV5; 145 if (COMPARE_STRING(key, "ESCO_2EV3")) 146 return ESCO_2EV3; 147 if (COMPARE_STRING(key, "ESCO_3EV3")) 148 return ESCO_3EV3; 149 if (COMPARE_STRING(key, "ESCO_2EV5")) 150 return ESCO_2EV5; 151 if (COMPARE_STRING(key, "ESCO_3EV5")) 152 return ESCO_3EV5; 153 if (COMPARE_STRING(key, "SCO_ESCO_MASK")) 154 return SCO_ESCO_MASK; 155 if (COMPARE_STRING(key, "EDR_ESCO_MASK")) 156 return EDR_ESCO_MASK; 157 if (COMPARE_STRING(key, "ALL_ESCO_MASK")) 158 return ALL_ESCO_MASK; 159 LOGE("Unknown SCO Type (%s) skipping",key); 160 return 0; 161} 162 163static void parseBlacklist(void) { 164 const char *filename = "/etc/bluetooth/blacklist.conf"; 165 char line[MAX_LINE]; 166 scoBlacklist_t *list = NULL; 167 scoBlacklist_t *newelem; 168 169 LOGV(__FUNCTION__); 170 171 /* Open file */ 172 FILE *fp = fopen(filename, "r"); 173 if(!fp) { 174 LOGE("Error(%s)opening blacklist file", strerror(errno)); 175 return; 176 } 177 178 while (fgets(line, MAX_LINE, fp) != NULL) { 179 if ((COMPARE_STRING(line, "//")) || (!strcmp(line, ""))) 180 continue; 181 char *module = strtok(line,":"); 182 if (COMPARE_STRING(module, SCO_BLACKLIST_MODULE_NAME)) { 183 newelem = (scoBlacklist_t *)calloc(1, sizeof(scoBlacklist_t)); 184 if (newelem == NULL) { 185 LOGE("%s: out of memory!", __FUNCTION__); 186 return; 187 } 188 // parse line 189 char *type = strtok(NULL, ","); 190 char *valueList = strtok(NULL, ","); 191 char *paramList = strtok(NULL, ","); 192 if (COMPARE_STRING(type, BLACKLIST_BY_NAME)) { 193 // Extract Name from Value list 194 newelem->fieldType = BL_TYPE_NAME; 195 newelem->value = (char *)calloc(1, strlen(valueList)); 196 if (newelem->value == NULL) { 197 LOGE("%s: out of memory!", __FUNCTION__); 198 continue; 199 } 200 valueList++; // Skip open quote 201 strncpy(newelem->value, valueList, strlen(valueList) - 1); 202 203 // Get Sco Settings from Parameters 204 char *param = strtok(paramList, ";"); 205 uint16_t scoTypes = 0; 206 while (param != NULL) { 207 uint16_t sco; 208 if (param[0] == '-') { 209 param++; 210 sco = str2scoType(param); 211 if (sco != 0) 212 scoTypes &= ~sco; 213 } else if (param[0] == '+') { 214 param++; 215 sco = str2scoType(param); 216 if (sco != 0) 217 scoTypes |= sco; 218 } else if (param[0] == '=') { 219 param++; 220 sco = str2scoType(param); 221 if (sco != 0) 222 scoTypes = sco; 223 } else { 224 LOGE("Invalid SCO type must be =, + or -"); 225 } 226 param = strtok(NULL, ";"); 227 } 228 newelem->scoType = scoTypes; 229 } else { 230 LOGE("Unknown SCO type entry in Blacklist file"); 231 continue; 232 } 233 if (list) { 234 list->next = newelem; 235 list = newelem; 236 } else { 237 blacklist = list = newelem; 238 } 239 LOGI("Entry name = %s ScoTypes = 0x%x", newelem->value, 240 newelem->scoType); 241 } 242 } 243 fclose(fp); 244 return; 245} 246static uint16_t getScoType(char *address, const char *name) { 247 uint16_t ret = 0; 248 scoBlacklist_t *list = blacklist; 249 250 while (list != NULL) { 251 if (list->fieldType == BL_TYPE_NAME) { 252 if (COMPARE_STRING(name, list->value)) { 253 ret = list->scoType; 254 break; 255 } 256 } 257 list = list->next; 258 } 259 LOGI("%s %s - 0x%x", __FUNCTION__, name, ret); 260 return ret; 261} 262#endif 263 264static void classInitNative(JNIEnv* env, jclass clazz) { 265 LOGV(__FUNCTION__); 266#ifdef HAVE_BLUETOOTH 267 if (env->GetJavaVM(&jvm) < 0) { 268 LOGE("Could not get handle to the VM"); 269 } 270 field_mNativeData = get_field(env, clazz, "mNativeData", "I"); 271 method_onAccepted = env->GetMethodID(clazz, "onAccepted", "(I)V"); 272 method_onConnected = env->GetMethodID(clazz, "onConnected", "(I)V"); 273 method_onClosed = env->GetMethodID(clazz, "onClosed", "()V"); 274 275 /* Read the blacklist file in here */ 276 parseBlacklist(); 277#endif 278} 279 280/* Returns false if a serious error occured */ 281static jboolean initNative(JNIEnv* env, jobject object) { 282 LOGV(__FUNCTION__); 283#ifdef HAVE_BLUETOOTH 284 285 native_data_t *nat = (native_data_t *) calloc(1, sizeof(native_data_t)); 286 if (nat == NULL) { 287 LOGE("%s: out of memory!", __FUNCTION__); 288 return JNI_FALSE; 289 } 290 291 pthread_mutex_init(&nat->mutex, NULL); 292 env->SetIntField(object, field_mNativeData, (jint)nat); 293 nat->signal_sk = -1; 294 nat->object = NULL; 295 nat->thread_data = NULL; 296 297#endif 298 return JNI_TRUE; 299} 300 301static void destroyNative(JNIEnv* env, jobject object) { 302 LOGV(__FUNCTION__); 303#ifdef HAVE_BLUETOOTH 304 native_data_t *nat = get_native_data(env, object); 305 306 closeNative(env, object); 307 308 pthread_mutex_lock(&nat->mutex); 309 if (nat->thread_data != NULL) { 310 nat->thread_data->nat = NULL; 311 } 312 pthread_mutex_unlock(&nat->mutex); 313 pthread_mutex_destroy(&nat->mutex); 314 315 free(nat); 316#endif 317} 318 319static jboolean acceptNative(JNIEnv *env, jobject object) { 320 LOGV(__FUNCTION__); 321#ifdef HAVE_BLUETOOTH 322 native_data_t *nat = get_native_data(env, object); 323 int signal_sks[2]; 324 pthread_t thread; 325 struct thread_data_t *data = NULL; 326 327 pthread_mutex_lock(&nat->mutex); 328 if (nat->signal_sk != -1) { 329 pthread_mutex_unlock(&nat->mutex); 330 return JNI_FALSE; 331 } 332 333 // setup socketpair to pass messages between threads 334 if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sks) < 0) { 335 LOGE("%s: socketpair() failed: %s", __FUNCTION__, strerror(errno)); 336 pthread_mutex_unlock(&nat->mutex); 337 return JNI_FALSE; 338 } 339 nat->signal_sk = signal_sks[0]; 340 nat->object = env->NewGlobalRef(object); 341 342 data = (thread_data_t *)calloc(1, sizeof(thread_data_t)); 343 if (data == NULL) { 344 LOGE("%s: out of memory", __FUNCTION__); 345 pthread_mutex_unlock(&nat->mutex); 346 return JNI_FALSE; 347 } 348 nat->thread_data = data; 349 pthread_mutex_unlock(&nat->mutex); 350 351 data->signal_sk = signal_sks[1]; 352 data->nat = nat; 353 data->is_accept = true; 354 355 if (pthread_create(&thread, NULL, &work_thread, (void *)data) < 0) { 356 LOGE("%s: pthread_create() failed: %s", __FUNCTION__, strerror(errno)); 357 return JNI_FALSE; 358 } 359 return JNI_TRUE; 360 361#endif 362 return JNI_FALSE; 363} 364 365static jboolean connectNative(JNIEnv *env, jobject object, jstring address, 366 jstring name) { 367 368 LOGV(__FUNCTION__); 369#ifdef HAVE_BLUETOOTH 370 native_data_t *nat = get_native_data(env, object); 371 int signal_sks[2]; 372 pthread_t thread; 373 struct thread_data_t *data; 374 const char *c_address; 375 const char *c_name; 376 377 pthread_mutex_lock(&nat->mutex); 378 if (nat->signal_sk != -1) { 379 pthread_mutex_unlock(&nat->mutex); 380 return JNI_FALSE; 381 } 382 383 // setup socketpair to pass messages between threads 384 if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sks) < 0) { 385 LOGE("%s: socketpair() failed: %s\n", __FUNCTION__, strerror(errno)); 386 pthread_mutex_unlock(&nat->mutex); 387 return JNI_FALSE; 388 } 389 nat->signal_sk = signal_sks[0]; 390 nat->object = env->NewGlobalRef(object); 391 392 data = (thread_data_t *)calloc(1, sizeof(thread_data_t)); 393 if (data == NULL) { 394 LOGE("%s: out of memory", __FUNCTION__); 395 pthread_mutex_unlock(&nat->mutex); 396 return JNI_FALSE; 397 } 398 pthread_mutex_unlock(&nat->mutex); 399 400 data->signal_sk = signal_sks[1]; 401 data->nat = nat; 402 c_address = env->GetStringUTFChars(address, NULL); 403 strlcpy(data->address, c_address, BTADDR_SIZE); 404 env->ReleaseStringUTFChars(address, c_address); 405 data->is_accept = false; 406 407 if (name == NULL) { 408 LOGE("%s: Null pointer passed in for device name", __FUNCTION__); 409 data->sco_pkt_type = 0; 410 } else { 411 c_name = env->GetStringUTFChars(name, NULL); 412 /* See if this device is in the black list */ 413 data->sco_pkt_type = getScoType(data->address, c_name); 414 env->ReleaseStringUTFChars(name, c_name); 415 } 416 if (pthread_create(&thread, NULL, &work_thread, (void *)data) < 0) { 417 LOGE("%s: pthread_create() failed: %s", __FUNCTION__, strerror(errno)); 418 return JNI_FALSE; 419 } 420 return JNI_TRUE; 421 422#endif 423 return JNI_FALSE; 424} 425 426static void closeNative(JNIEnv *env, jobject object) { 427 LOGV(__FUNCTION__); 428#ifdef HAVE_BLUETOOTH 429 native_data_t *nat = get_native_data(env, object); 430 int signal_sk; 431 432 pthread_mutex_lock(&nat->mutex); 433 signal_sk = nat->signal_sk; 434 nat->signal_sk = -1; 435 env->DeleteGlobalRef(nat->object); 436 nat->object = NULL; 437 pthread_mutex_unlock(&nat->mutex); 438 439 if (signal_sk >= 0) { 440 LOGV("%s: signal_sk = %d", __FUNCTION__, signal_sk); 441 unsigned char dummy; 442 write(signal_sk, &dummy, sizeof(dummy)); 443 close(signal_sk); 444 } 445#endif 446} 447 448#ifdef HAVE_BLUETOOTH 449/* thread entry point */ 450static void *work_thread(void *arg) { 451 JNIEnv* env; 452 thread_data_t *data = (thread_data_t *)arg; 453 int sk; 454 455 LOGV(__FUNCTION__); 456 if (jvm->AttachCurrentThread(&env, NULL) != JNI_OK) { 457 LOGE("%s: AttachCurrentThread() failed", __FUNCTION__); 458 return NULL; 459 } 460 461 /* connect the SCO socket */ 462 if (data->is_accept) { 463 LOGV("SCO OBJECT %p ACCEPT #####", data->nat->object); 464 sk = accept_work(data->signal_sk); 465 LOGV("SCO OBJECT %p END ACCEPT *****", data->nat->object); 466 } else { 467 sk = connect_work(data->address, data->sco_pkt_type); 468 } 469 470 /* callback with connection result */ 471 if (data->nat == NULL) { 472 LOGV("%s: object destroyed!", __FUNCTION__); 473 goto done; 474 } 475 pthread_mutex_lock(&data->nat->mutex); 476 if (data->nat->object == NULL) { 477 pthread_mutex_unlock(&data->nat->mutex); 478 LOGV("%s: callback cancelled", __FUNCTION__); 479 goto done; 480 } 481 if (data->is_accept) { 482 env->CallVoidMethod(data->nat->object, method_onAccepted, sk); 483 } else { 484 env->CallVoidMethod(data->nat->object, method_onConnected, sk); 485 } 486 pthread_mutex_unlock(&data->nat->mutex); 487 488 if (sk < 0) { 489 goto done; 490 } 491 492 LOGV("SCO OBJECT %p %d CONNECTED +++ (%s)", data->nat->object, sk, 493 data->is_accept ? "in" : "out"); 494 495 /* wait for the socket to close */ 496 LOGV("wait_for_close()..."); 497 wait_for_close(sk, data->signal_sk); 498 LOGV("wait_for_close() returned"); 499 500 /* callback with close result */ 501 if (data->nat == NULL) { 502 LOGV("%s: object destroyed!", __FUNCTION__); 503 goto done; 504 } 505 pthread_mutex_lock(&data->nat->mutex); 506 if (data->nat->object == NULL) { 507 LOGV("%s: callback cancelled", __FUNCTION__); 508 } else { 509 env->CallVoidMethod(data->nat->object, method_onClosed); 510 } 511 pthread_mutex_unlock(&data->nat->mutex); 512 513done: 514 if (sk >= 0) { 515 close(sk); 516 LOGV("SCO OBJECT %p %d CLOSED --- (%s)", data->nat->object, sk, data->is_accept ? "in" : "out"); 517 } 518 if (data->signal_sk >= 0) { 519 close(data->signal_sk); 520 } 521 LOGV("SCO socket closed"); 522 523 if (data->nat != NULL) { 524 pthread_mutex_lock(&data->nat->mutex); 525 env->DeleteGlobalRef(data->nat->object); 526 data->nat->object = NULL; 527 data->nat->thread_data = NULL; 528 pthread_mutex_unlock(&data->nat->mutex); 529 } 530 531 free(data); 532 if (jvm->DetachCurrentThread() != JNI_OK) { 533 LOGE("%s: DetachCurrentThread() failed", __FUNCTION__); 534 } 535 536 LOGV("work_thread() done"); 537 return NULL; 538} 539 540static int accept_work(int signal_sk) { 541 LOGV(__FUNCTION__); 542 int sk; 543 int nsk; 544 int addr_sz; 545 int max_fd; 546 fd_set fds; 547 struct sockaddr_sco addr; 548 549 sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); 550 if (sk < 0) { 551 LOGE("%s socket() failed: %s", __FUNCTION__, strerror(errno)); 552 return -1; 553 } 554 555 memset(&addr, 0, sizeof(addr)); 556 addr.sco_family = AF_BLUETOOTH; 557 memcpy(&addr.sco_bdaddr, BDADDR_ANY, sizeof(bdaddr_t)); 558 if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { 559 LOGE("%s bind() failed: %s", __FUNCTION__, strerror(errno)); 560 goto error; 561 } 562 563 if (listen(sk, 1)) { 564 LOGE("%s: listen() failed: %s", __FUNCTION__, strerror(errno)); 565 goto error; 566 } 567 568 memset(&addr, 0, sizeof(addr)); 569 addr_sz = sizeof(addr); 570 571 FD_ZERO(&fds); 572 FD_SET(sk, &fds); 573 FD_SET(signal_sk, &fds); 574 575 max_fd = (sk > signal_sk) ? sk : signal_sk; 576 LOGI("Listening SCO socket..."); 577 while (select(max_fd + 1, &fds, NULL, NULL, NULL) < 0) { 578 if (errno != EINTR) { 579 LOGE("%s: select() failed: %s", __FUNCTION__, strerror(errno)); 580 goto error; 581 } 582 LOGV("%s: select() EINTR, retrying", __FUNCTION__); 583 } 584 LOGV("select() returned"); 585 if (FD_ISSET(signal_sk, &fds)) { 586 // signal to cancel listening 587 LOGV("cancelled listening socket, closing"); 588 goto error; 589 } 590 if (!FD_ISSET(sk, &fds)) { 591 LOGE("error: select() returned >= 0 with no fds set"); 592 goto error; 593 } 594 595 nsk = accept(sk, (struct sockaddr *)&addr, &addr_sz); 596 if (nsk < 0) { 597 LOGE("%s: accept() failed: %s", __FUNCTION__, strerror(errno)); 598 goto error; 599 } 600 LOGI("Connected SCO socket (incoming)"); 601 close(sk); // The listening socket 602 603 return nsk; 604 605error: 606 close(sk); 607 608 return -1; 609} 610 611static int connect_work(const char *address, uint16_t sco_pkt_type) { 612 LOGV(__FUNCTION__); 613 struct sockaddr_sco addr; 614 int sk = -1; 615 616 sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); 617 if (sk < 0) { 618 LOGE("%s: socket() failed: %s", __FUNCTION__, strerror(errno)); 619 return -1; 620 } 621 622 /* Bind to local address */ 623 memset(&addr, 0, sizeof(addr)); 624 addr.sco_family = AF_BLUETOOTH; 625 memcpy(&addr.sco_bdaddr, BDADDR_ANY, sizeof(bdaddr_t)); 626 if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { 627 LOGE("%s: bind() failed: %s", __FUNCTION__, strerror(errno)); 628 goto error; 629 } 630 631 memset(&addr, 0, sizeof(addr)); 632 addr.sco_family = AF_BLUETOOTH; 633 get_bdaddr(address, &addr.sco_bdaddr); 634 addr.sco_pkt_type = sco_pkt_type; 635 LOGI("Connecting to socket"); 636 while (connect(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) { 637 if (errno != EINTR) { 638 LOGE("%s: connect() failed: %s", __FUNCTION__, strerror(errno)); 639 goto error; 640 } 641 LOGV("%s: connect() EINTR, retrying", __FUNCTION__); 642 } 643 LOGI("SCO socket connected (outgoing)"); 644 645 return sk; 646 647error: 648 if (sk >= 0) close(sk); 649 return -1; 650} 651 652static void wait_for_close(int sk, int signal_sk) { 653 LOGV(__FUNCTION__); 654 pollfd p[2]; 655 656 memset(p, 0, 2 * sizeof(pollfd)); 657 p[0].fd = sk; 658 p[1].fd = signal_sk; 659 p[1].events = POLLIN | POLLPRI; 660 661 LOGV("poll..."); 662 663 while (poll(p, 2, -1) < 0) { // blocks 664 if (errno != EINTR) { 665 LOGE("%s: poll() failed: %s", __FUNCTION__, strerror(errno)); 666 break; 667 } 668 LOGV("%s: poll() EINTR, retrying", __FUNCTION__); 669 } 670 671 LOGV("poll() returned"); 672} 673#endif 674 675static JNINativeMethod sMethods[] = { 676 {"classInitNative", "()V", (void*)classInitNative}, 677 {"initNative", "()V", (void *)initNative}, 678 {"destroyNative", "()V", (void *)destroyNative}, 679 {"connectNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *)connectNative}, 680 {"acceptNative", "()Z", (void *)acceptNative}, 681 {"closeNative", "()V", (void *)closeNative}, 682}; 683 684int register_android_bluetooth_ScoSocket(JNIEnv *env) { 685 return AndroidRuntime::registerNativeMethods(env, 686 "android/bluetooth/ScoSocket", sMethods, NELEM(sMethods)); 687} 688 689} /* namespace android */ 690