Android Bluetooth

Introducción a bluetooth en Android

La implementación que se utiliza en Android para gestionar bluetooth es BlueZ.

Servicios asociados a Bluetooth

Para implementar las funcionalidades de bluetooth, como por ejemplo la de descubrimiento se utilizan "system service".

Inicio de Bluetooth

Para que funcione bluetooth es necesario por un lado el soporte del mismo en el núcleo Linux. Cualquier núcleo moderno de Linux lo soporta. Para ello se maneja el chipset de bz como si fuera un puerto serie UART (Universal Asynchronous Receiver-Transmitter) utilizando para llegar al chipset la herramienta hciattach, que normalmente se incluye en los scripts de inicio del sistema. Por ejemplo en el HCT N1 podemos observar en "device/htc/passion/init.mahimahi.rc" la configuración:

service hciattach /system/bin/brcm_patchram_plus --enable_hci --enable_lpm \
    --baudrate 3000000 --patchram /etc/firmware/bcm4329.hcd /dev/ttyHS0
    user bluetooth
    group bluetooth net_bt_admin
    disabled

Vemos que es un servicio que no se arranca por defecto por parte de "service manager".

Mirando en un N1 ya en funcionamiento:

adb shell
# grep -A4 hciattach init.mahimahi.rc
service hciattach /system/bin/brcm_patchram_plus --enable_hci --enable_lpm \
    --baudrate 3000000 --patchram /etc/firmware/bcm4329.hcd /dev/ttyHS0
    user bluetooth
    group bluetooth net_bt_admin
    disabled

Por defecto como el servicio no está iniciado como podemos ver en un sistema recién iniciado:

# ps | grep blue
root      13    2     0      0     c006722c 00000000 S bluetooth
bluetooth 87    1     1308   716   c00c5f44 afd0ebac S /system/bin/dbus-daemon
# ps | grep hci
#

Los procesos que se inician al activar bluetooth

bluetooth 723   1     848    332   c006f564 afd0e3bc S /system/bin/brcm_patchram_plus
app_21    724   85    112664 22608 ffffffff afd0edc8 S com.android.bluetooth
bluetooth 731   1     1952   1100  c00c5f44 afd0ebac S /system/bin/bluetoothd

Los servicios que tenemos asociados a bluetooth son:

# ./system/bin/service list | grep blue
26      bluetooth_hid: [android.bluetooth.IBluetoothHid]
27      bluetooth_a2dp: [android.bluetooth.IBluetoothA2dp]
28      bluetooth: [android.bluetooth.IBluetooth]

que son de comunicaciones generales bluetooth, la parte de manos libres (a2dp) y la de comunicación con teclados y otros dispositivos bz.

Es probable que nuestro servicio Manager de HDP acabe siendo un servicio que se apoye sobre bz, entre otros.

Interfaces Java a bluetooth

La API Java de Android está implementada sobre la de bluez que se ofrece por dbus.

Uso de bluez desde Java

El soporte de bz en Android se realiza a través de bluez. ¿Cómo se hace? Pues las llamadas a la API de bz al final tienen que acabar en invocaciones nativas. Vamos a hacer el seguimiento viendo como se implementa la API de Bluetooth. Y para ello nada mejor que seguir por ejemplo la llamada [[http://developer.android.com/intl/es/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery%28%29 | de descubrimiento de otros dispositivos]] que se hace desde la clase BluetoothAdapter.

public boolean startDiscovery() {
        try {
            return mService.startDiscovery();
        } catch (RemoteException e) {Log.e(TAG, "", e);}
        return false;
    }

Vemos que se hace uso del objeto "mService" que es:

private final IBluetooth mService;

...
public BluetoothAdapter(IBluetooth service) {
        if (service == null) {
            throw new IllegalArgumentException("service is null");
        }
        mService = service;
    }

El servicio se lo pasamos al crear el BluetoothAdapter. Vamos a ver donde se hace.

public static synchronized BluetoothAdapter getDefaultAdapter() {
        if (sAdapter == null) {
            IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
            if (b != null) {
                IBluetooth service = IBluetooth.Stub.asInterface(b);
                sAdapter = new BluetoothAdapter(service);
            }
        }
        return sAdapter;
    }

Vemos que la forma de acceder al dispositivo es con "getDefaultAdapter". Y la llamada clave es:

IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
            if (b != null) {
                IBluetooth service = IBluetooth.Stub.asInterface(b);

Esta forma de acceder a los servicios se hace en muchas otras partes, así que es la forma estándar. En realidad, esta es la forma de acceder a interfaces remotas que están en otros procesos. Estas interfaces se definen mediante aidl.

Esta interfaz de servicio "BluetoothAdapter.BLUETOOTH_SERVICE" estará implementada utilizando bluez. HDP es algo específico de bluetooth por lo que es normal que esté dentro del servicio "BLUETOOTH_SERVICE".

Vamos a estudiar como funcionan los servicios gestionados por android.os.ServiceManager.

private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }
....
public static IBinder getService(String name) {
        try {
               ...
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

Bien, podemos ver claro que ServiceManagerNative es quien nos da la funcionalidad para poder acceder a las APIs de los servicios.

public abstract class ServiceManagerNative 
...

es una clase abstracta que está implementada en "BinderInternal.getContextObject()". Vamos a ver hasta donde nos lleva este BinderInternal:

public class BinderInternal {
...
/**
     * Return the global "context object" of the system.  This is usually
     * an implementation of IServiceManager, which you can use to find
     * other services.
     */
    public static final native IBinder getContextObject();

Vemos que esto ya nos lleva a métodos nativos que están implementados en:

./frameworks/base/libs/binder/ProcessState.cpp
./frameworks/base/libs/binder/IServiceManager.cpp
...
./frameworks/base/core/jni/android_util_Binder.cpp

En vez de meternos con la parte genérica que permite enlazar Java y las librerías nativas, vamos a centrarnos en el servicio de Bluetooth. Dentro de "SystemServer.java" tenemos todos los servicios disponibles en la plataforma.

Si nos centramos en bz:

bluetooth = new BluetoothService(context);
                ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, bluetooth);
                bluetooth.initAfterRegistration();
                bluetoothA2dp = new BluetoothA2dpService(context, bluetooth);
                ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE,
                                          bluetoothA2dp);

                int bluetoothOn = Settings.Secure.getInt(mContentResolver,
                    Settings.Secure.BLUETOOTH_ON, 0);
                if (bluetoothOn > 0) {
                    bluetooth.enable();
                }

Lo ideal sería modelar HDP como un BluetoothA2dpService probablemente. Aunque HDP no deja de ser un protocolo, no un servicio, así que vamos a ir a BluetoothService.

public class BluetoothService extends IBluetooth.Stub {

De leer esta clase vemos que los servicios que necesitan de interacción con librerías nativas los tenemos en:

android-2.2$ tree -L 2 ./frameworks/base/services
./frameworks/base/services
|-- java
|   |-- Android.mk
|   `-- com
|-- jni
|   |-- Android.mk
|   |-- com_android_server_AlarmManagerService.cpp
|   |-- com_android_server_BatteryService.cpp
|   |-- com_android_server_KeyInputQueue.cpp
|   |-- com_android_server_LightsService.cpp
|   |-- com_android_server_SensorService.cpp
|   |-- com_android_server_SystemServer.cpp
|   |-- com_android_server_VibratorService.cpp
|   `-- onload.cpp
`-- tests
    `-- servicestests

Están luego dentro de "com" el resto de servicios que no necesitan de esta parte nativa también. Se dice que el servicio de Bluetooth debería de estar aquí, en vez de "./frameworks/base/core" que hace que "BluetoothService" termine en un directorio sólo (raro): "frameworks/base/core/java/android/server".

Volviendo sobre el servicio de Bluetooth podemos ver en su implementación que para implementar algunas funcionalidades se realizan llamadas nativas como:

   private native Object[] getAdapterPropertiesNative();
    private native Object[] getDevicePropertiesNative(String objectPath);
    private native boolean setAdapterPropertyStringNative(String key, String value);
    private native boolean setAdapterPropertyIntegerNative(String key, int value);
    private native boolean setAdapterPropertyBooleanNative(String key, int value);

    private native boolean startDiscoveryNative();
    private native boolean stopDiscoveryNative();

    private native boolean createPairedDeviceNative(String address, int timeout_ms);
    private native boolean cancelDeviceCreationNative(String address);
    private native boolean removeDeviceNative(String objectPath);
    private native int getDeviceServiceChannelNative(String objectPath, String uuid,
            int attributeId);

    private native boolean cancelPairingUserInputNative(String address, int nativeData);
    private native boolean setPinNative(String address, String pin, int nativeData);
    private native boolean setPasskeyNative(String address, int passkey, int nativeData);
    private native boolean setPairingConfirmationNative(String address, boolean confirm,
            int nativeData);
    private native boolean setDevicePropertyBooleanNative(String objectPath, String key,
            int value);
    private native boolean createDeviceNative(String address);

Una forma de implementar HDP es añadir a esta clase la funcionalidad del nuevo API de HDP. Vamos a ver como el método nativo "startDiscoveryNative()" acaba llamando a bluez.

Este método se llama desde:

public synchronized boolean startDiscovery() {
        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                                "Need BLUETOOTH_ADMIN permission");
        if (!isEnabledInternal()) return false;

        return startDiscoveryNative();
    }

y buscando en las fuentes C de android localizamos este método en ./frameworks/base/core/jni/android_server_BluetoothService.cpp:

...
#ifdef HAVE_BLUETOOTH
#include <dbus/dbus.h>
#include <bluedroid/bluetooth.h>
#endif
....
static jboolean startDiscoveryNative(JNIEnv *env, jobject object) {
    LOGV(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
    DBusMessage *msg = NULL;
    DBusMessage *reply = NULL;
    DBusError err;
    const char *name;
    jboolean ret = JNI_FALSE;

    native_data_t *nat = get_native_data(env, object);
    if (nat == NULL) {
        goto done;
    }

    dbus_error_init(&err);

    /* Compose the command */
    msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,
                                       get_adapter_path(env, object),
                                       DBUS_ADAPTER_IFACE, "StartDiscovery");

    if (msg == NULL) {
        if (dbus_error_is_set(&err)) {
            LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
        }
        goto done;
    }

    /* Send the command. */
    reply = dbus_connection_send_with_reply_and_block(nat->conn, msg, -1, &err);
    if (dbus_error_is_set(&err)) {
         LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
         ret = JNI_FALSE;
         goto done;
    }

    ret = JNI_TRUE;
done:
    if (reply) dbus_message_unref(reply);
    if (msg) dbus_message_unref(msg);
    return ret;
#else
    return JNI_FALSE;
#endif
}
...

Queda claro como se llama a DBUS con llamadas JNI para lanzar el comando de StartDiscovery que es parte de la API DBUS de bluez. En nuestro caso lo que haremos será ir añadiendo a este fichero los nuevos métodos de HDP y luego incluir sus métodos parejos en Java.

Un estupendo ejemplo que podemos utilizar de referencia es "BluetoothChatService.java" que es una clase que maneja toda la parte de conectividad bz de un cliente de chat por bz. En nuestro caso, sería en el Manager donde habría que meter algo similar. Y el asistente haría el papel de cliente de chat.

Activación del sistema de bluetooth

Cuando se activa el sistema de bluetooth en Android, utilizando las preferencias, si miramos los logs de Android podemos ver que:

I/ActivityManager(  110): Starting activity: Intent { act=android.intent.action.MAIN cmp=com.android.settings/.WirelessSettings }
D/skia    (  206): purging 195K from font cache [23 entries]
I/ActivityManager(  110): Displayed activity com.android.settings/.WirelessSettings: 331 ms (total 331 ms)
D/BluetoothService(  110): Bluetooth state 10 -> 11
V/BluetoothEventRedirector(  206): Received android.bluetooth.adapter.action.STATE_CHANGED
I/bluedroid(  110): Starting hciattach daemon
I/ActivityManager(  110): Start proc com.android.bluetooth for broadcast com.android.bluetooth/.opp.BluetoothOppReceiver: pid=6675 uid=10021 gids={3003, 3002, 3001, 1015}
I/ActivityThread( 6675): Publishing provider com.android.bluetooth.opp: com.android.bluetooth.opp.BluetoothOppProvider
D/ConnectivityService(  110): getMobileDataEnabled returning false
I/bluedroid(  110): Starting bluetoothd deamon
I/bluetoothd( 6682): Bluetooth daemon 
I/bluetoothd( 6682): Starting SDP server
I/bluetoothd( 6682): Got Unix socket fd '12' from environment
I/bluetoothd( 6682): Adding device id record for 000a:0000
I/bluetoothd( 6682): HCI dev 0 registered
I/bluetoothd( 6682): HCI dev 0 up
I/bluetoothd( 6682): Starting security manager 0
I/bluetoothd( 6682): Adapter /org/bluez/6682/hci0 has been enabled
E/bluetoothd( 6682): Failed to open RFKILL control device
E/bluetoothd( 6682): Inquiry Failed with status 0x12
E/BluetoothEventLoop.cpp(  110): event_filter: Received signal org.freedesktop.DBus:NameAcquired from /org/freedesktop/DBus
D/BluetoothHidService.cpp(  110): hid_event_filter...
I/global  (  110): Default buffer size used in BufferedReader constructor. It would be better to be explicit if an 8k-char buffer is required.
D/BluetoothService(  110): Bluetooth state 11 -> 12
V/BluetoothEventRedirector(  206): Received android.bluetooth.adapter.action.STATE_CHANGED
I/bluetooth_ScoSocket.cpp(  190): Listening SCO socket...
D/ConnectivityService(  110): getMobileDataEnabled returning false
D/BluetoothService(  110): Registering hfag record
E/BluetoothEventLoop.cpp(  110): event_filter: Received signal org.bluez.Adapter:PropertyChanged from /org/bluez/6682/hci0
D/BluetoothService(  110): Registering hsag record
D/BluetoothService(  110): Registering opush record
E/BluetoothEventLoop.cpp(  110): event_filter: Received signal org.bluez.Adapter:PropertyChanged from /org/bluez/6682/hci0
D/BluetoothService(  110): Registering pbap record
D/dalvikvm(  197): GC_EXPLICIT freed 3448 objects / 176728 bytes in 99ms

Si desactivamos bluetooth:

D/ConnectivityService(  110): getMobileDataEnabled returning false
D/BluetoothService(  110): Bluetooth state 12 -> 13
D/ScoSocket(  190): android.bluetooth.ScoSocket@43f9b3f0 SCO OBJECT close() mState = 2
V/BluetoothEventRedirector(  206): Received android.bluetooth.adapter.action.STATE_CHANGED
I/BtOppRfcommListener( 8412): stopping Accept Thread
E/BtOppRfcommListener( 8412): Error accept connection java.io.IOException: Operation Canceled
I/BtOppRfcommListener( 8412): BluetoothSocket listen thread finished
D/ConnectivityService(  110): getMobileDataEnabled returning false
I/bluedroid(  110): Stopping bluetoothd deamon
I/bluetoothd( 8419): Stopping SDP server
I/bluetoothd( 8419): Exit
I/bluedroid(  110): Stopping hciattach deamon
D/BluetoothService(  110): Bluetooth state 13 -> 10
V/BluetoothEventRedirector(  206): Received android.bluetooth.adapter.action.STATE_CHANGED
D/ConnectivityService(  110): getMobileDataEnabled returning false
D/dalvikvm( 6770): GC_EXPLICIT freed 499 objects / 21864 bytes in 73ms

Referencias

AndroidBluetooth (last edited 2010-10-05 14:29:17 by AlvaroDelCastillo)