blob: 8361212e2ca2831a28c598828f11951b7cceecdc [file] [log] [blame]
/*
** 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 "bluetooth_common.cpp"
#include "android_bluetooth_common.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 <cutils/properties.h>
#ifdef HAVE_BLUETOOTH
#include <dbus/dbus.h>
#endif
namespace android {
#ifdef HAVE_BLUETOOTH
static Properties remote_device_properties[] = {
{"Address", DBUS_TYPE_STRING},
{"Name", DBUS_TYPE_STRING},
{"Icon", DBUS_TYPE_STRING},
{"Class", DBUS_TYPE_UINT32},
{"UUIDs", DBUS_TYPE_ARRAY},
{"Paired", DBUS_TYPE_BOOLEAN},
{"Connected", DBUS_TYPE_BOOLEAN},
{"Trusted", DBUS_TYPE_BOOLEAN},
{"Alias", DBUS_TYPE_STRING},
{"Nodes", DBUS_TYPE_ARRAY},
{"Adapter", DBUS_TYPE_OBJECT_PATH},
{"LegacyPairing", DBUS_TYPE_BOOLEAN},
{"RSSI", DBUS_TYPE_INT16},
{"TX", DBUS_TYPE_UINT32}
};
static Properties adapter_properties[] = {
{"Address", DBUS_TYPE_STRING},
{"Name", DBUS_TYPE_STRING},
{"Class", DBUS_TYPE_UINT32},
{"Powered", DBUS_TYPE_BOOLEAN},
{"Discoverable", DBUS_TYPE_BOOLEAN},
{"DiscoverableTimeout", DBUS_TYPE_UINT32},
{"Pairable", DBUS_TYPE_BOOLEAN},
{"PairableTimeout", DBUS_TYPE_UINT32},
{"Discovering", DBUS_TYPE_BOOLEAN},
{"Devices", DBUS_TYPE_ARRAY},
};
jfieldID get_field(JNIEnv *env, jclass clazz, const char *member,
const char *mtype) {
jfieldID field = env->GetFieldID(clazz, member, mtype);
if (field == NULL) {
LOGE("Can't find member %s", member);
}
return field;
}
typedef struct {
void (*user_cb)(DBusMessage *, void *, void *);
void *user;
void *nat;
JNIEnv *env;
} dbus_async_call_t;
void dbus_func_args_async_callback(DBusPendingCall *call, void *data) {
dbus_async_call_t *req = (dbus_async_call_t *)data;
DBusMessage *msg;
/* This is guaranteed to be non-NULL, because this function is called only
when once the remote method invokation returns. */
msg = dbus_pending_call_steal_reply(call);
if (msg) {
if (req->user_cb) {
// The user may not deref the message object.
req->user_cb(msg, req->user, req->nat);
}
dbus_message_unref(msg);
}
//dbus_message_unref(req->method);
dbus_pending_call_cancel(call);
dbus_pending_call_unref(call);
free(req);
}
static dbus_bool_t dbus_func_args_async_valist(JNIEnv *env,
DBusConnection *conn,
int timeout_ms,
void (*user_cb)(DBusMessage *,
void *,
void*),
void *user,
void *nat,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
va_list args) {
DBusMessage *msg = NULL;
const char *name;
dbus_async_call_t *pending;
dbus_bool_t reply = FALSE;
/* Compose the command */
msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, path, ifc, func);
if (msg == NULL) {
LOGE("Could not allocate D-Bus message object!");
goto done;
}
/* append arguments */
if (!dbus_message_append_args_valist(msg, first_arg_type, args)) {
LOGE("Could not append argument to method call!");
goto done;
}
/* Make the call. */
pending = (dbus_async_call_t *)malloc(sizeof(dbus_async_call_t));
if (pending) {
DBusPendingCall *call;
pending->env = env;
pending->user_cb = user_cb;
pending->user = user;
pending->nat = nat;
//pending->method = msg;
reply = dbus_connection_send_with_reply(conn, msg,
&call,
timeout_ms);
if (reply == TRUE) {
dbus_pending_call_set_notify(call,
dbus_func_args_async_callback,
pending,
NULL);
}
}
done:
if (msg) dbus_message_unref(msg);
return reply;
}
dbus_bool_t dbus_func_args_async(JNIEnv *env,
DBusConnection *conn,
int timeout_ms,
void (*reply)(DBusMessage *, void *, void*),
void *user,
void *nat,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
...) {
dbus_bool_t ret;
va_list lst;
va_start(lst, first_arg_type);
ret = dbus_func_args_async_valist(env, conn,
timeout_ms,
reply, user, nat,
path, ifc, func,
first_arg_type, lst);
va_end(lst);
return ret;
}
// If err is NULL, then any errors will be LOGE'd, and free'd and the reply
// will be NULL.
// If err is not NULL, then it is assumed that dbus_error_init was already
// called, and error's will be returned to the caller without logging. The
// return value is NULL iff an error was set. The client must free the error if
// set.
DBusMessage * dbus_func_args_timeout_valist(JNIEnv *env,
DBusConnection *conn,
int timeout_ms,
DBusError *err,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
va_list args) {
DBusMessage *msg = NULL, *reply = NULL;
const char *name;
bool return_error = (err != NULL);
if (!return_error) {
err = (DBusError*)malloc(sizeof(DBusError));
dbus_error_init(err);
}
/* Compose the command */
msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, path, ifc, func);
if (msg == NULL) {
LOGE("Could not allocate D-Bus message object!");
goto done;
}
/* append arguments */
if (!dbus_message_append_args_valist(msg, first_arg_type, args)) {
LOGE("Could not append argument to method call!");
goto done;
}
/* Make the call. */
reply = dbus_connection_send_with_reply_and_block(conn, msg, timeout_ms, err);
if (!return_error && dbus_error_is_set(err)) {
LOG_AND_FREE_DBUS_ERROR_WITH_MSG(err, msg);
}
done:
if (!return_error) {
free(err);
}
if (msg) dbus_message_unref(msg);
return reply;
}
DBusMessage * dbus_func_args_timeout(JNIEnv *env,
DBusConnection *conn,
int timeout_ms,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
...) {
DBusMessage *ret;
va_list lst;
va_start(lst, first_arg_type);
ret = dbus_func_args_timeout_valist(env, conn, timeout_ms, NULL,
path, ifc, func,
first_arg_type, lst);
va_end(lst);
return ret;
}
DBusMessage * dbus_func_args(JNIEnv *env,
DBusConnection *conn,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
...) {
DBusMessage *ret;
va_list lst;
va_start(lst, first_arg_type);
ret = dbus_func_args_timeout_valist(env, conn, -1, NULL,
path, ifc, func,
first_arg_type, lst);
va_end(lst);
return ret;
}
DBusMessage * dbus_func_args_error(JNIEnv *env,
DBusConnection *conn,
DBusError *err,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
...) {
DBusMessage *ret;
va_list lst;
va_start(lst, first_arg_type);
ret = dbus_func_args_timeout_valist(env, conn, -1, err,
path, ifc, func,
first_arg_type, lst);
va_end(lst);
return ret;
}
jint dbus_returns_int32(JNIEnv *env, DBusMessage *reply) {
DBusError err;
jint ret = -1;
dbus_error_init(&err);
if (!dbus_message_get_args(reply, &err,
DBUS_TYPE_INT32, &ret,
DBUS_TYPE_INVALID)) {
LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
}
dbus_message_unref(reply);
return ret;
}
jint dbus_returns_uint32(JNIEnv *env, DBusMessage *reply) {
DBusError err;
jint ret = -1;
dbus_error_init(&err);
if (!dbus_message_get_args(reply, &err,
DBUS_TYPE_UINT32, &ret,
DBUS_TYPE_INVALID)) {
LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
}
dbus_message_unref(reply);
return ret;
}
jstring dbus_returns_string(JNIEnv *env, DBusMessage *reply) {
DBusError err;
jstring ret = NULL;
const char *name;
dbus_error_init(&err);
if (dbus_message_get_args(reply, &err,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID)) {
ret = env->NewStringUTF(name);
} else {
LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
}
dbus_message_unref(reply);
return ret;
}
jboolean dbus_returns_boolean(JNIEnv *env, DBusMessage *reply) {
DBusError err;
jboolean ret = JNI_FALSE;
dbus_bool_t val = FALSE;
dbus_error_init(&err);
/* Check the return value. */
if (dbus_message_get_args(reply, &err,
DBUS_TYPE_BOOLEAN, &val,
DBUS_TYPE_INVALID)) {
ret = val == TRUE ? JNI_TRUE : JNI_FALSE;
} else {
LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
}
dbus_message_unref(reply);
return ret;
}
static void set_object_array_element(JNIEnv *env, jobjectArray strArray,
const char *value, int index) {
jstring obj;
obj = env->NewStringUTF(value);
env->SetObjectArrayElement(strArray, index, obj);
env->DeleteLocalRef(obj);
}
jobjectArray dbus_returns_array_of_object_path(JNIEnv *env,
DBusMessage *reply) {
DBusError err;
char **list;
int i, len;
jobjectArray strArray = NULL;
dbus_error_init(&err);
if (dbus_message_get_args (reply,
&err,
DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH,
&list, &len,
DBUS_TYPE_INVALID)) {
jclass stringClass;
jstring classNameStr;
stringClass = env->FindClass("java/lang/String");
strArray = env->NewObjectArray(len, stringClass, NULL);
for (i = 0; i < len; i++)
set_object_array_element(env, strArray, list[i], i);
} else {
LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
}
dbus_message_unref(reply);
return strArray;
}
jobjectArray dbus_returns_array_of_strings(JNIEnv *env, DBusMessage *reply) {
DBusError err;
char **list;
int i, len;
jobjectArray strArray = NULL;
dbus_error_init(&err);
if (dbus_message_get_args (reply,
&err,
DBUS_TYPE_ARRAY, DBUS_TYPE_STRING,
&list, &len,
DBUS_TYPE_INVALID)) {
jclass stringClass;
jstring classNameStr;
//LOGV("%s: there are %d elements in string array!", __FUNCTION__, len);
stringClass = env->FindClass("java/lang/String");
strArray = env->NewObjectArray(len, stringClass, NULL);
for (i = 0; i < len; i++)
set_object_array_element(env, strArray, list[i], i);
} else {
LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
}
dbus_message_unref(reply);
return strArray;
}
jbyteArray dbus_returns_array_of_bytes(JNIEnv *env, DBusMessage *reply) {
DBusError err;
int i, len;
jbyte *list;
jbyteArray byteArray = NULL;
dbus_error_init(&err);
if (dbus_message_get_args(reply, &err,
DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &list, &len,
DBUS_TYPE_INVALID)) {
//LOGV("%s: there are %d elements in byte array!", __FUNCTION__, len);
byteArray = env->NewByteArray(len);
if (byteArray)
env->SetByteArrayRegion(byteArray, 0, len, list);
} else {
LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
}
dbus_message_unref(reply);
return byteArray;
}
void append_variant(DBusMessageIter *iter, int type, void *val)
{
DBusMessageIter value_iter;
char var_type[2] = { type, '\0'};
dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, var_type, &value_iter);
dbus_message_iter_append_basic(&value_iter, type, val);
dbus_message_iter_close_container(iter, &value_iter);
}
//TODO(): Remove code duplication between parse_properties and
//parse_property_change
jobjectArray parse_properties(JNIEnv *env, DBusMessageIter *iter, Properties *properties,
const int max_num_properties) {
DBusMessageIter dict_entry, dict, prop_val, device_val, array_val_iter;
jobjectArray strArray = NULL;
char * property;
char values[max_num_properties][256];
char **uuid_array = NULL;
char **device_path = NULL;
char **array_elements = NULL;
char *string_val;
uint32_t int_val, bool_val;
int i, j, k, type, array_type, num_array_elements = 0;
int ret, num_properties = 0, num_uuids = 0, num_devices = 0;
jclass stringClass = env->FindClass("java/lang/String");
DBusError err;
dbus_error_init(&err);
for (i = 0; i < max_num_properties; i++)
memset(values[i], '\0', 128 * sizeof(char));
if(dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
goto failure;
dbus_message_iter_recurse(iter, &dict);
do {
if (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_DICT_ENTRY)
goto failure;
dbus_message_iter_recurse(&dict, &dict_entry);
if (dbus_message_iter_get_arg_type(&dict_entry) != DBUS_TYPE_STRING)
goto failure;
dbus_message_iter_get_basic(&dict_entry, &property);
if (!dbus_message_iter_next(&dict_entry))
goto failure;
if (dbus_message_iter_get_arg_type(&dict_entry) != DBUS_TYPE_VARIANT)
goto failure;
for (i = 0; i < max_num_properties; i++) {
if (!strncmp(properties[i].name, property, strlen(property))) {
num_properties ++;
break;
}
}
if (i == max_num_properties)
goto failure;
type = properties[i].type;
dbus_message_iter_recurse(&dict_entry, &prop_val);
if(dbus_message_iter_get_arg_type(&prop_val) != type) {
LOGE("Property type mismatch in parse_properties: %d, expected:%d",
dbus_message_iter_get_arg_type(&prop_val), type);
goto failure;
}
switch(type) {
case DBUS_TYPE_STRING:
case DBUS_TYPE_OBJECT_PATH:
dbus_message_iter_get_basic(&prop_val, &string_val);
strcpy(values[i], string_val);
break;
case DBUS_TYPE_UINT32:
case DBUS_TYPE_INT16:
dbus_message_iter_get_basic(&prop_val, &int_val);
sprintf(values[i], "%d", int_val);
break;
case DBUS_TYPE_BOOLEAN:
dbus_message_iter_get_basic(&prop_val, &bool_val);
sprintf(values[i], "%s", bool_val ? "true" : "false");
break;
case DBUS_TYPE_ARRAY:
dbus_message_iter_recurse(&prop_val, &array_val_iter);
array_type = dbus_message_iter_get_arg_type(&array_val_iter);
num_array_elements = 0;
if (array_type == DBUS_TYPE_OBJECT_PATH ||
array_type == DBUS_TYPE_STRING){
do {
num_array_elements++;
} while(dbus_message_iter_next(&array_val_iter));
dbus_message_iter_recurse(&prop_val, &array_val_iter);
// Allocate an array
array_elements = (char **)malloc(sizeof(char *) *
num_array_elements);
if (!array_elements)
goto failure;
j = 0;
do {
dbus_message_iter_get_basic(&array_val_iter, &array_elements[j]);
j++;
} while(dbus_message_iter_next(&array_val_iter));
if (!strncmp(property, "UUIDs", strlen("UUIDs"))) {
num_uuids = num_array_elements;
uuid_array = array_elements;
} else {
num_devices = num_array_elements;
device_path = array_elements;
}
}
break;
default:
goto failure;
}
} while(dbus_message_iter_next(&dict));
// Convert it to a array of strings.
strArray = env->NewObjectArray((num_properties + num_array_elements) * 2,
stringClass, NULL);
j = 0;
for (i = 0; i < max_num_properties; i++) {
if (properties[i].type == DBUS_TYPE_ARRAY) {
if (!strncmp(properties[i].name, "UUIDs", strlen("UUIDs"))) {
num_array_elements = num_uuids;
array_elements = uuid_array;
} else {
num_array_elements = num_devices;
array_elements = device_path;
}
for (k = 0; k < num_array_elements; k++) {
set_object_array_element(env, strArray, properties[i].name, j++);
set_object_array_element(env, strArray, array_elements[k], j++);
}
} else if (values[i][0] != '\0') {
set_object_array_element(env, strArray, properties[i].name, j++);
set_object_array_element(env, strArray, values[i], j++);
}
}
if (uuid_array)
free(uuid_array);
if (device_path)
free(device_path);
return strArray;
failure:
if (dbus_error_is_set(&err))
LOG_AND_FREE_DBUS_ERROR(&err);
if (uuid_array)
free(uuid_array);
if (device_path)
free(device_path);
return NULL;
}
jobjectArray create_prop_array(JNIEnv *env, Properties *properties,
int prop_index, void *value, int len ) {
jclass stringClass= env->FindClass("java/lang/String");
char **prop_val = NULL;
char buf[32] = {'\0'};
int i, j;
jobjectArray strArray = env->NewObjectArray(1 + len, stringClass, NULL);
j = 0;
set_object_array_element(env, strArray, properties[prop_index].name, j++);
if (properties[prop_index].type == DBUS_TYPE_UINT32) {
sprintf(buf, "%d", *(int *) value);
set_object_array_element(env, strArray, buf, j++);
} else if (properties[prop_index].type == DBUS_TYPE_BOOLEAN) {
sprintf(buf, "%s", *(int *) value ? "true" : "false");
set_object_array_element(env, strArray, buf, j++);
} else if (properties[prop_index].type == DBUS_TYPE_ARRAY) {
prop_val = (char **) value;
for (i = 0; i < len; i++)
set_object_array_element(env, strArray, prop_val[i], j++);
} else {
set_object_array_element(env, strArray, (const char *) value, j++);
}
if (prop_val)
free (prop_val);
return strArray;
}
jobjectArray parse_property_change(JNIEnv *env, DBusMessage *msg,
Properties *properties, int max_num_properties) {
DBusMessageIter iter, prop_val, array_val_iter;
DBusError err;
void *value;
char *property;
uint32_t array_type;
int i, j, type, len, prop_index;
dbus_error_init(&err);
if (!dbus_message_iter_init(msg, &iter))
goto failure;
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
goto failure;
dbus_message_iter_get_basic(&iter, &property);
if (!dbus_message_iter_next(&iter))
goto failure;
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
goto failure;
for (i = 0; i < max_num_properties; i++) {
if (!strncmp(property, properties[i].name, strlen(properties[i].name)))
break;
}
prop_index = i;
if (i == max_num_properties)
goto failure;
dbus_message_iter_recurse(&iter, &prop_val);
type = properties[prop_index].type;
if (dbus_message_iter_get_arg_type(&prop_val) != type) {
LOGE("Property type mismatch in parse_properties: %d, expected:%d",
dbus_message_iter_get_arg_type(&prop_val), type);
goto failure;
}
switch(type) {
case DBUS_TYPE_STRING:
case DBUS_TYPE_OBJECT_PATH:
dbus_message_iter_get_basic(&prop_val, &value);
len = 1;
break;
case DBUS_TYPE_UINT32:
case DBUS_TYPE_INT16:
case DBUS_TYPE_BOOLEAN:
uint32_t int_val;
dbus_message_iter_get_basic(&prop_val, &int_val);
value = &int_val;
len = 1;
break;
case DBUS_TYPE_ARRAY:
dbus_message_iter_recurse(&prop_val, &array_val_iter);
array_type = dbus_message_iter_get_arg_type(&array_val_iter);
len = 0;
if (array_type == DBUS_TYPE_OBJECT_PATH ||
array_type == DBUS_TYPE_STRING){
do {
len ++;
} while(dbus_message_iter_next(&array_val_iter));
dbus_message_iter_recurse(&prop_val, &array_val_iter);
// Allocate an array of char *
char **tmp = (char **)malloc(sizeof(char *) * len);
if (!tmp)
goto failure;
j = 0;
do {
dbus_message_iter_get_basic(&array_val_iter, &tmp[j]);
j ++;
} while(dbus_message_iter_next(&array_val_iter));
value = (char **) tmp;
}
break;
default:
goto failure;
}
return create_prop_array(env, properties, prop_index, value, len);
failure:
LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
return NULL;
}
jobjectArray parse_adapter_property_change(JNIEnv *env, DBusMessage *msg) {
return parse_property_change(env, msg, (Properties *) &adapter_properties,
sizeof(adapter_properties) / sizeof(Properties));
}
jobjectArray parse_remote_device_property_change(JNIEnv *env, DBusMessage *msg) {
return parse_property_change(env, msg, (Properties *) &remote_device_properties,
sizeof(remote_device_properties) / sizeof(Properties));
}
jobjectArray parse_adapter_properties(JNIEnv *env, DBusMessageIter *iter) {
return parse_properties(env, iter, (Properties *) &adapter_properties,
sizeof(adapter_properties) / sizeof(Properties));
}
jobjectArray parse_remote_device_properties(JNIEnv *env, DBusMessageIter *iter) {
return parse_properties(env, iter, (Properties *) &remote_device_properties,
sizeof(remote_device_properties) / sizeof(Properties));
}
int get_bdaddr(const char *str, bdaddr_t *ba) {
char *d = ((char *)ba) + 5, *endp;
int i;
for(i = 0; i < 6; i++) {
*d-- = strtol(str, &endp, 16);
if (*endp != ':' && i != 5) {
memset(ba, 0, sizeof(bdaddr_t));
return -1;
}
str = endp + 1;
}
return 0;
}
void get_bdaddr_as_string(const bdaddr_t *ba, char *str) {
const uint8_t *b = (const uint8_t *)ba;
sprintf(str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
b[5], b[4], b[3], b[2], b[1], b[0]);
}
bool debug_no_encrypt() {
return false;
#if 0
char value[PROPERTY_VALUE_MAX] = "";
property_get("debug.bt.no_encrypt", value, "");
if (!strncmp("true", value, PROPERTY_VALUE_MAX) ||
!strncmp("1", value, PROPERTY_VALUE_MAX)) {
LOGD("mandatory bluetooth encryption disabled");
return true;
} else {
return false;
}
#endif
}
#endif
} /* namespace android */