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