Home | History | Annotate | Download | only in jni
      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 "BluetoothA2dpService.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 <ctype.h>
     27 #include <errno.h>
     28 #include <stdio.h>
     29 #include <string.h>
     30 #include <stdlib.h>
     31 #include <unistd.h>
     32 
     33 #ifdef HAVE_BLUETOOTH
     34 #include <dbus/dbus.h>
     35 #endif
     36 
     37 namespace android {
     38 
     39 #ifdef HAVE_BLUETOOTH
     40 static jmethodID method_onSinkPropertyChanged;
     41 static jmethodID method_onConnectSinkResult;
     42 
     43 typedef struct {
     44     JavaVM *vm;
     45     int envVer;
     46     DBusConnection *conn;
     47     jobject me;  // for callbacks to java
     48 } native_data_t;
     49 
     50 static native_data_t *nat = NULL;  // global native data
     51 static void onConnectSinkResult(DBusMessage *msg, void *user, void *n);
     52 
     53 static Properties sink_properties[] = {
     54         {"State", DBUS_TYPE_STRING},
     55         {"Connected", DBUS_TYPE_BOOLEAN},
     56         {"Playing", DBUS_TYPE_BOOLEAN},
     57       };
     58 #endif
     59 
     60 /* Returns true on success (even if adapter is present but disabled).
     61  * Return false if dbus is down, or another serious error (out of memory)
     62 */
     63 static bool initNative(JNIEnv* env, jobject object) {
     64     LOGV("%s", __FUNCTION__);
     65 #ifdef HAVE_BLUETOOTH
     66     nat = (native_data_t *)calloc(1, sizeof(native_data_t));
     67     if (NULL == nat) {
     68         LOGE("%s: out of memory!", __FUNCTION__);
     69         return false;
     70     }
     71     env->GetJavaVM( &(nat->vm) );
     72     nat->envVer = env->GetVersion();
     73     nat->me = env->NewGlobalRef(object);
     74 
     75     DBusError err;
     76     dbus_error_init(&err);
     77     dbus_threads_init_default();
     78     nat->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
     79     if (dbus_error_is_set(&err)) {
     80         LOGE("Could not get onto the system bus: %s", err.message);
     81         dbus_error_free(&err);
     82         return false;
     83     }
     84     dbus_connection_set_exit_on_disconnect(nat->conn, FALSE);
     85 #endif  /*HAVE_BLUETOOTH*/
     86     return true;
     87 }
     88 
     89 static void cleanupNative(JNIEnv* env, jobject object) {
     90 #ifdef HAVE_BLUETOOTH
     91     LOGV("%s", __FUNCTION__);
     92     if (nat) {
     93         dbus_connection_close(nat->conn);
     94         env->DeleteGlobalRef(nat->me);
     95         free(nat);
     96         nat = NULL;
     97     }
     98 #endif
     99 }
    100 
    101 static jobjectArray getSinkPropertiesNative(JNIEnv *env, jobject object,
    102                                             jstring path) {
    103 #ifdef HAVE_BLUETOOTH
    104     LOGV("%s", __FUNCTION__);
    105     if (nat) {
    106         DBusMessage *msg, *reply;
    107         DBusError err;
    108         dbus_error_init(&err);
    109 
    110         const char *c_path = env->GetStringUTFChars(path, NULL);
    111         reply = dbus_func_args_timeout(env,
    112                                    nat->conn, -1, c_path,
    113                                    "org.bluez.AudioSink", "GetProperties",
    114                                    DBUS_TYPE_INVALID);
    115         env->ReleaseStringUTFChars(path, c_path);
    116         if (!reply && dbus_error_is_set(&err)) {
    117             LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
    118             return NULL;
    119         } else if (!reply) {
    120             LOGE("DBus reply is NULL in function %s", __FUNCTION__);
    121             return NULL;
    122         }
    123         DBusMessageIter iter;
    124         if (dbus_message_iter_init(reply, &iter))
    125             return parse_properties(env, &iter, (Properties *)&sink_properties,
    126                                  sizeof(sink_properties) / sizeof(Properties));
    127     }
    128 #endif
    129     return NULL;
    130 }
    131 
    132 
    133 static jboolean connectSinkNative(JNIEnv *env, jobject object, jstring path) {
    134 #ifdef HAVE_BLUETOOTH
    135     LOGV("%s", __FUNCTION__);
    136     if (nat) {
    137         const char *c_path = env->GetStringUTFChars(path, NULL);
    138         int len = env->GetStringLength(path) + 1;
    139         char *context_path = (char *)calloc(len, sizeof(char));
    140         strlcpy(context_path, c_path, len);  // for callback
    141 
    142         bool ret = dbus_func_args_async(env, nat->conn, -1, onConnectSinkResult, context_path,
    143                                     nat, c_path, "org.bluez.AudioSink", "Connect",
    144                                     DBUS_TYPE_INVALID);
    145 
    146         env->ReleaseStringUTFChars(path, c_path);
    147         return ret ? JNI_TRUE : JNI_FALSE;
    148     }
    149 #endif
    150     return JNI_FALSE;
    151 }
    152 
    153 static jboolean disconnectSinkNative(JNIEnv *env, jobject object,
    154                                      jstring path) {
    155 #ifdef HAVE_BLUETOOTH
    156     LOGV("%s", __FUNCTION__);
    157     if (nat) {
    158         const char *c_path = env->GetStringUTFChars(path, NULL);
    159 
    160         bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
    161                                     c_path, "org.bluez.AudioSink", "Disconnect",
    162                                     DBUS_TYPE_INVALID);
    163 
    164         env->ReleaseStringUTFChars(path, c_path);
    165         return ret ? JNI_TRUE : JNI_FALSE;
    166     }
    167 #endif
    168     return JNI_FALSE;
    169 }
    170 
    171 static jboolean suspendSinkNative(JNIEnv *env, jobject object,
    172                                      jstring path) {
    173 #ifdef HAVE_BLUETOOTH
    174     LOGV("%s", __FUNCTION__);
    175     if (nat) {
    176         const char *c_path = env->GetStringUTFChars(path, NULL);
    177         bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
    178                            c_path, "org.bluez.audio.Sink", "Suspend",
    179                            DBUS_TYPE_INVALID);
    180         env->ReleaseStringUTFChars(path, c_path);
    181         return ret ? JNI_TRUE : JNI_FALSE;
    182     }
    183 #endif
    184     return JNI_FALSE;
    185 }
    186 
    187 static jboolean resumeSinkNative(JNIEnv *env, jobject object,
    188                                      jstring path) {
    189 #ifdef HAVE_BLUETOOTH
    190     LOGV("%s", __FUNCTION__);
    191     if (nat) {
    192         const char *c_path = env->GetStringUTFChars(path, NULL);
    193         bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
    194                            c_path, "org.bluez.audio.Sink", "Resume",
    195                            DBUS_TYPE_INVALID);
    196         env->ReleaseStringUTFChars(path, c_path);
    197         return ret ? JNI_TRUE : JNI_FALSE;
    198     }
    199 #endif
    200     return JNI_FALSE;
    201 }
    202 
    203 static jboolean avrcpVolumeUpNative(JNIEnv *env, jobject object,
    204                                      jstring path) {
    205 #ifdef HAVE_BLUETOOTH
    206     LOGV("%s", __FUNCTION__);
    207     if (nat) {
    208         const char *c_path = env->GetStringUTFChars(path, NULL);
    209         bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
    210                            c_path, "org.bluez.Control", "VolumeUp",
    211                            DBUS_TYPE_INVALID);
    212         env->ReleaseStringUTFChars(path, c_path);
    213         return ret ? JNI_TRUE : JNI_FALSE;
    214     }
    215 #endif
    216     return JNI_FALSE;
    217 }
    218 
    219 static jboolean avrcpVolumeDownNative(JNIEnv *env, jobject object,
    220                                      jstring path) {
    221 #ifdef HAVE_BLUETOOTH
    222     LOGV("%s", __FUNCTION__);
    223     if (nat) {
    224         const char *c_path = env->GetStringUTFChars(path, NULL);
    225         bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
    226                            c_path, "org.bluez.Control", "VolumeDown",
    227                            DBUS_TYPE_INVALID);
    228         env->ReleaseStringUTFChars(path, c_path);
    229         return ret ? JNI_TRUE : JNI_FALSE;
    230     }
    231 #endif
    232     return JNI_FALSE;
    233 }
    234 
    235 #ifdef HAVE_BLUETOOTH
    236 DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) {
    237     DBusError err;
    238 
    239     if (!nat) {
    240         LOGV("... skipping %s\n", __FUNCTION__);
    241         LOGV("... ignored\n");
    242         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    243     }
    244 
    245     dbus_error_init(&err);
    246 
    247     if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) {
    248         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    249     }
    250 
    251     DBusHandlerResult result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    252 
    253     if (dbus_message_is_signal(msg, "org.bluez.AudioSink",
    254                                       "PropertyChanged")) {
    255         jobjectArray str_array =
    256                     parse_property_change(env, msg, (Properties *)&sink_properties,
    257                                 sizeof(sink_properties) / sizeof(Properties));
    258         const char *c_path = dbus_message_get_path(msg);
    259         jstring path = env->NewStringUTF(c_path);
    260         env->CallVoidMethod(nat->me,
    261                             method_onSinkPropertyChanged,
    262                             path,
    263                             str_array);
    264         env->DeleteLocalRef(path);
    265         result = DBUS_HANDLER_RESULT_HANDLED;
    266         return result;
    267     } else {
    268         LOGV("... ignored");
    269     }
    270     if (env->ExceptionCheck()) {
    271         LOGE("VM Exception occurred while handling %s.%s (%s) in %s,"
    272              " leaving for VM",
    273              dbus_message_get_interface(msg), dbus_message_get_member(msg),
    274              dbus_message_get_path(msg), __FUNCTION__);
    275     }
    276 
    277     return result;
    278 }
    279 
    280 void onConnectSinkResult(DBusMessage *msg, void *user, void *n) {
    281     LOGV("%s", __FUNCTION__);
    282 
    283     native_data_t *nat = (native_data_t *)n;
    284     const char *path = (const char *)user;
    285     DBusError err;
    286     dbus_error_init(&err);
    287     JNIEnv *env;
    288     nat->vm->GetEnv((void**)&env, nat->envVer);
    289 
    290 
    291     bool result = JNI_TRUE;
    292     if (dbus_set_error_from_message(&err, msg)) {
    293         LOG_AND_FREE_DBUS_ERROR(&err);
    294         result = JNI_FALSE;
    295     }
    296     LOGV("... Device Path = %s, result = %d", path, result);
    297 
    298     jstring jPath = env->NewStringUTF(path);
    299     env->CallVoidMethod(nat->me,
    300                         method_onConnectSinkResult,
    301                         jPath,
    302                         result);
    303     env->DeleteLocalRef(jPath);
    304     free(user);
    305 }
    306 
    307 
    308 #endif
    309 
    310 
    311 static JNINativeMethod sMethods[] = {
    312     {"initNative", "()Z", (void *)initNative},
    313     {"cleanupNative", "()V", (void *)cleanupNative},
    314 
    315     /* Bluez audio 4.47 API */
    316     {"connectSinkNative", "(Ljava/lang/String;)Z", (void *)connectSinkNative},
    317     {"disconnectSinkNative", "(Ljava/lang/String;)Z", (void *)disconnectSinkNative},
    318     {"suspendSinkNative", "(Ljava/lang/String;)Z", (void*)suspendSinkNative},
    319     {"resumeSinkNative", "(Ljava/lang/String;)Z", (void*)resumeSinkNative},
    320     {"getSinkPropertiesNative", "(Ljava/lang/String;)[Ljava/lang/Object;",
    321                                     (void *)getSinkPropertiesNative},
    322     {"avrcpVolumeUpNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeUpNative},
    323     {"avrcpVolumeDownNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeDownNative},
    324 };
    325 
    326 int register_android_server_BluetoothA2dpService(JNIEnv *env) {
    327     jclass clazz = env->FindClass("android/server/BluetoothA2dpService");
    328     if (clazz == NULL) {
    329         LOGE("Can't find android/server/BluetoothA2dpService");
    330         return -1;
    331     }
    332 
    333 #ifdef HAVE_BLUETOOTH
    334     method_onSinkPropertyChanged = env->GetMethodID(clazz, "onSinkPropertyChanged",
    335                                           "(Ljava/lang/String;[Ljava/lang/String;)V");
    336     method_onConnectSinkResult = env->GetMethodID(clazz, "onConnectSinkResult",
    337                                                          "(Ljava/lang/String;Z)V");
    338 #endif
    339 
    340     return AndroidRuntime::registerNativeMethods(env,
    341                 "android/server/BluetoothA2dpService", sMethods, NELEM(sMethods));
    342 }
    343 
    344 } /* namespace android */
    345