OSDN Git Service

Initial implementation of hdp service and jni
authorMatthew Xie <mattx@google.com>
Fri, 23 Mar 2012 00:32:29 +0000 (17:32 -0700)
committerAndroid (Google) Code Review <android-gerrit@google.com>
Tue, 17 Jul 2012 04:54:06 +0000 (21:54 -0700)
Change-Id: Ieca906080835098383cabbc13fa914363459f555

AndroidManifest.xml
jni/Android.mk
jni/com_android_bluetooth.h
jni/com_android_bluetooth_btservice_AdapterService.cpp
jni/com_android_bluetooth_hdp.cpp [new file with mode: 0644]
jni/com_android_bluetooth_hfp.cpp
src/com/android/bluetooth/btservice/AdapterService.java
src/com/android/bluetooth/hdp/HealthService.java [new file with mode: 0644]

index f757a49..0620831 100644 (file)
                 <action android:name="android.bluetooth.IBluetoothInputDevice" />
             </intent-filter>
         </service>
+        <service
+            android:process="@string/process"
+            android:name = ".hdp.HealthService">
+            <intent-filter>
+                <action android:name="android.bluetooth.IBluetoothHealth" />
+            </intent-filter>
+        </service>
     </application>
 </manifest>
index 21c5bc3..5f07ab5 100644 (file)
@@ -7,6 +7,7 @@ LOCAL_SRC_FILES:= \
     com_android_bluetooth_hfp.cpp \
     com_android_bluetooth_a2dp.cpp \
     com_android_bluetooth_hid.cpp \
+    com_android_bluetooth_hdp.cpp
 
 LOCAL_C_INCLUDES += \
     $(JNI_H_INCLUDE) \
index 9d4f67d..7f84267 100644 (file)
@@ -25,6 +25,8 @@ int register_com_android_bluetooth_a2dp(JNIEnv* env);
 
 int register_com_android_bluetooth_hid(JNIEnv* env);
 
+int register_com_android_bluetooth_hdp(JNIEnv* env);
+
 }
 
 #endif /* COM_ANDROID_BLUETOOTH_H */
index 9d49b65..a7826bb 100755 (executable)
@@ -54,7 +54,10 @@ void checkAndClearExceptionFromCallback(JNIEnv* env,
 
 static bool checkCallbackThread() {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
-    if (callbackEnv != env || callbackEnv == NULL) return false;
+    if (callbackEnv != env || callbackEnv == NULL) {
+        LOGE("Callback env check fail: env: %p, callback: %p", env, callbackEnv);
+        return false;
+    }
     return true;
 }
 
@@ -763,6 +766,11 @@ jint JNI_OnLoad(JavaVM *jvm, void *reserved)
 
    if ((status = android::register_com_android_bluetooth_hid(e)) < 0) {
        LOGE("jni hid registration failure: %d", status);
+       return JNI_ERR;
+   }
+
+   if ((status = android::register_com_android_bluetooth_hdp(e)) < 0) {
+       LOGE("jni hdp registration failure: %d", status);
       return JNI_ERR;
    }
 
diff --git a/jni/com_android_bluetooth_hdp.cpp b/jni/com_android_bluetooth_hdp.cpp
new file mode 100644 (file)
index 0000000..07c0e4f
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ */
+
+#define LOG_TAG "BluetoothHealthServiceJni"
+
+#define LOG_NDEBUG 0
+
+#define CHECK_CALLBACK_ENV                                                      \
+   if (!checkCallbackThread()) {                                                \
+       LOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);\
+       return;                                                                  \
+   }
+
+#include "com_android_bluetooth.h"
+#include "hardware/bt_hl.h"
+#include "utils/Log.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <string.h>
+
+namespace android {
+
+static jmethodID method_onAppRegistrationState;
+static jmethodID method_onChannelStateChanged;
+
+static const bthl_interface_t *sBluetoothHdpInterface = NULL;
+static jobject mCallbacksObj = NULL;
+static JNIEnv *sCallbackEnv = NULL;
+
+static bool checkCallbackThread() {
+    sCallbackEnv = getCallbackEnv();
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    if (sCallbackEnv != env || sCallbackEnv == NULL) {
+        LOGE("Callback env check fail: env: %p, callback: %p", env, sCallbackEnv);
+        return false;
+    }
+    return true;
+}
+
+// Define callback functions
+static void app_registration_state_callback(int app_id, bthl_app_reg_state_t state) {
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAppRegistrationState, app_id,
+                                 (jint) state);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void channel_state_callback(int app_id, bt_bdaddr_t *bd_addr, int mdep_cfg_index,
+                                   int channel_id, bthl_channel_state_t state, int fd) {
+    jbyteArray addr;
+    jobject fileDescriptor = NULL;
+
+    CHECK_CALLBACK_ENV
+    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+    if (!addr) {
+        LOGE("Fail to new jbyteArray bd addr for channel state");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        return;
+    }
+
+    // TODO(BT) check if fd is only valid for BTHH_CONN_STATE_CONNECTED state
+    if (state == BTHL_CONN_STATE_CONNECTED) {
+        fileDescriptor = jniCreateFileDescriptor(sCallbackEnv, fd);
+        if (!fileDescriptor) {
+            LOGE("Failed to convert file descriptor, fd: %d", fd);
+            sCallbackEnv->DeleteLocalRef(addr);
+            return;
+        }
+    }
+
+    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onChannelStateChanged, app_id, addr,
+                                 mdep_cfg_index, channel_id, (jint) state, fileDescriptor);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+    sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static bthl_callbacks_t sBluetoothHdpCallbacks = {
+    sizeof(sBluetoothHdpCallbacks),
+    app_registration_state_callback,
+    channel_state_callback
+};
+
+// Define native functions
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+    int err;
+    const bt_interface_t* btInf;
+    bt_status_t status;
+
+    method_onAppRegistrationState = env->GetMethodID(clazz, "onAppRegistrationState", "(II)V");
+    method_onChannelStateChanged = env->GetMethodID(clazz, "onChannelStateChanged",
+                                                    "(I[BIIILjava/io/FileDescriptor;)V");
+
+    if ( (btInf = getBluetoothInterface()) == NULL) {
+        LOGE("Bluetooth module is not loaded");
+        return;
+    }
+
+    if ( (sBluetoothHdpInterface = (bthl_interface_t *)
+          btInf->get_profile_interface(BT_PROFILE_HEALTH_ID)) == NULL) {
+        LOGE("Failed to get Bluetooth Handsfree Interface");
+        return;
+    }
+
+    // TODO(BT) do this only once or
+    //          Do we need to do this every time the BT reenables?
+    if ( (status = sBluetoothHdpInterface->init(&sBluetoothHdpCallbacks)) != BT_STATUS_SUCCESS) {
+        LOGE("Failed to initialize Bluetooth HDP, status: %d", status);
+        sBluetoothHdpInterface = NULL;
+        return;
+    }
+
+    LOGI("%s: succeeds", __FUNCTION__);
+}
+
+static void initializeNativeDataNative(JNIEnv *env, jobject object) {
+    // TODO(BT) clean it up when hdp service is stopped
+    mCallbacksObj = env->NewGlobalRef(object);
+}
+
+static jint registerHealthAppNative(JNIEnv *env, jobject object, jint data_type,
+                                       jint role, jstring name, jint channel_type) {
+    bt_status_t status;
+    bthl_mdep_cfg_t mdep_cfg;
+    bthl_reg_param_t reg_param;
+    int app_id;
+
+    if (!sBluetoothHdpInterface) return NULL;
+
+    mdep_cfg.mdep_role = (bthl_mdep_role_t) role;
+    mdep_cfg.data_type = data_type;
+    mdep_cfg.channel_type = (bthl_channel_type_t) channel_type;
+    // TODO(BT) pass all the followings in from java instead of reuse name
+    mdep_cfg.mdep_description = env->GetStringUTFChars(name, NULL);
+    reg_param.application_name = env->GetStringUTFChars(name, NULL);
+    reg_param.provider_name = NULL;
+    reg_param.srv_name = NULL;
+    reg_param.srv_desp = NULL;
+    reg_param.number_of_mdeps = 1;
+    reg_param.mdep_cfg = &mdep_cfg;
+
+    if ( (status = sBluetoothHdpInterface->register_application(&reg_param, &app_id)) !=
+         BT_STATUS_SUCCESS) {
+        LOGE("Failed register health app, status: %d", status);
+        return -1;
+    }
+
+    env->ReleaseStringUTFChars(name, mdep_cfg.mdep_description);
+    env->ReleaseStringUTFChars(name, reg_param.application_name);
+    return app_id;
+}
+
+static jboolean unregisterHealthAppNative(JNIEnv *env, jobject object, int app_id) {
+    bt_status_t status;
+    if (!sBluetoothHdpInterface) return JNI_FALSE;
+
+    if ((status = sBluetoothHdpInterface->unregister_application(app_id)) != BT_STATUS_SUCCESS) {
+        LOGE("Failed to unregister app %d, status: %d", app_id, status);
+    }
+    return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jint connectChannelNative(JNIEnv *env, jobject object,
+                                 jbyteArray address, jint app_id) {
+    bt_status_t status;
+    jbyte *addr;
+    jint chan_id;
+    if (!sBluetoothHdpInterface) return -1;
+
+    addr = env->GetByteArrayElements(address, NULL);
+    if (!addr) {
+        LOGE("Bluetooth device address null");
+        return -1;
+    }
+
+    if ( (status = sBluetoothHdpInterface->connect_channel(app_id, (bt_bdaddr_t *) addr,
+                                                           0, &chan_id)) !=
+         BT_STATUS_SUCCESS) {
+        LOGE("Failed HDP channel connection, status: %d", status);
+        chan_id = -1;
+    }
+    env->ReleaseByteArrayElements(address, addr, 0);
+
+    return chan_id;
+}
+
+static jboolean disconnectChannelNative(JNIEnv *env, jobject object, jint channel_id) {
+    bt_status_t status;
+    if (!sBluetoothHdpInterface) return JNI_FALSE;
+
+    if ( (status = sBluetoothHdpInterface->destroy_channel(channel_id)) !=
+         BT_STATUS_SUCCESS) {
+        LOGE("Failed disconnect health channel, status: %d", status);
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+static JNINativeMethod sMethods[] = {
+    {"classInitNative", "()V", (void *) classInitNative},
+    {"initializeNativeDataNative", "()V", (void *) initializeNativeDataNative},
+    {"registerHealthAppNative", "(IILjava/lang/String;I)I", (void *) registerHealthAppNative},
+    {"unregisterHealthAppNative", "(I)Z", (void *) unregisterHealthAppNative},
+    {"connectChannelNative", "([BI)I", (void *) connectChannelNative},
+    {"disconnectChannelNative", "(I)Z", (void *) disconnectChannelNative},
+    // TBD
+};
+
+int register_com_android_bluetooth_hdp(JNIEnv* env)
+{
+    return jniRegisterNativeMethods(env, "com/android/bluetooth/hdp/HealthService",
+                                    sMethods, NELEM(sMethods));
+}
+
+}
index f8b0b16..ef84252 100644 (file)
@@ -254,14 +254,12 @@ static void initializeNativeDataNative(JNIEnv *env, jobject object) {
 
 static jboolean connectHfpNative(JNIEnv *env, jobject object, jbyteArray address) {
     jbyte *addr;
-    bt_bdaddr_t * btAddr;
     bt_status_t status;
 
     LOGI("%s: sBluetoothHfpInterface: %p", __FUNCTION__, sBluetoothHfpInterface);
     if (!sBluetoothHfpInterface) return JNI_FALSE;
 
     addr = env->GetByteArrayElements(address, NULL);
-    btAddr = (bt_bdaddr_t *) addr;
     if (!addr) {
         jniThrowIOException(env, EINVAL);
         return JNI_FALSE;
index 52028bb..6832a54 100755 (executable)
@@ -29,9 +29,10 @@ import android.provider.Settings;
 import android.util.Log;
 import android.util.Pair;
 
-import com.android.bluetooth.hfp.HeadsetService;
 import com.android.bluetooth.a2dp.A2dpService;
 import com.android.bluetooth.hid.HidService;
+import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.hdp.HealthService;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
 
@@ -97,6 +98,7 @@ public class AdapterService extends Application {
         startService(new Intent(this, HeadsetService.class));
         startService(new Intent(this, A2dpService.class));
         startService(new Intent(this, HidService.class));
+        startService(new Intent(this, HealthService.class));
     }
 
     @Override
diff --git a/src/com/android/bluetooth/hdp/HealthService.java b/src/com/android/bluetooth/hdp/HealthService.java
new file mode 100644 (file)
index 0000000..2ae3f65
--- /dev/null
@@ -0,0 +1,724 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ */
+
+package com.android.bluetooth.hdp;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHealth;
+import android.bluetooth.BluetoothHealthAppConfiguration;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetooth;
+import android.bluetooth.IBluetoothHealth;
+import android.bluetooth.IBluetoothHealthCallback;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import com.android.bluetooth.Utils;
+
+/**
+ * Provides Bluetooth Health Device profile, as a service in
+ * the Bluetooth application.
+ * @hide
+ */
+public class HealthService extends Service {
+    private static final String TAG = "BluetoothHealthService";
+    private static final boolean DBG = true;
+
+    static final String BLUETOOTH_ADMIN_PERM =
+        android.Manifest.permission.BLUETOOTH_ADMIN;
+    static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+
+    private BluetoothAdapter mAdapter;
+    private List<HealthChannel> mHealthChannels;
+    private Map <BluetoothHealthAppConfiguration, AppInfo> mApps;
+    private Map <BluetoothDevice, Integer> mHealthDevices;
+    private HealthServiceMessageHandler mHandler;
+    private IBluetooth mAdapterService;
+
+    private static final int MESSAGE_REGISTER_APPLICATION = 1;
+    private static final int MESSAGE_UNREGISTER_APPLICATION = 2;
+    private static final int MESSAGE_CONNECT_CHANNEL = 3;
+    private static final int MESSAGE_DISCONNECT_CHANNEL = 4;
+    private static final int MESSAGE_APP_REGISTRATION_CALLBACK = 11;
+    private static final int MESSAGE_CHANNEL_STATE_CALLBACK = 12;
+
+    static {
+        classInitNative();
+    }
+
+    @Override
+    public void onCreate() {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mHealthChannels = Collections.synchronizedList(new ArrayList<HealthChannel>());
+        mApps = Collections.synchronizedMap(new HashMap<BluetoothHealthAppConfiguration,
+                                            AppInfo>());
+        mHealthDevices = Collections.synchronizedMap(new HashMap<BluetoothDevice, Integer>());
+
+        HandlerThread thread = new HandlerThread("BluetoothHdpHandler");
+        thread.start();
+        Looper looper = thread.getLooper();
+        mHandler = new HealthServiceMessageHandler(looper);
+        mAdapterService = IBluetooth.Stub.asInterface(ServiceManager.getService("bluetooth"));
+
+        initializeNativeDataNative();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        log("onBind");
+        return mBinder;
+    }
+
+    @Override
+    public void onStart(Intent intent, int startId) {
+        log("onStart");
+        if (mAdapter == null) {
+            Log.w(TAG, "Stopping Bluetooth HealthService: device does not have BT");
+            stopSelf();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (DBG) log("Stopping Bluetooth HealthService");
+        // TBD
+    }
+
+    private final class HealthServiceMessageHandler extends Handler {
+        private HealthServiceMessageHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_REGISTER_APPLICATION:
+                {
+                    BluetoothHealthAppConfiguration appConfig =
+                        (BluetoothHealthAppConfiguration) msg.obj;
+                    int halRole = convertRoleToHal(appConfig.getRole());
+                    int halChannelType = convertChannelTypeToHal(appConfig.getChannelType());
+                    if (DBG) log("register datatype: " + appConfig.getDataType() + " role: " +
+                                 halRole + " name: " + appConfig.getName() + " channeltype: " +
+                                 halChannelType);
+                    int appId = registerHealthAppNative(appConfig.getDataType(), halRole,
+                                                        appConfig.getName(), halChannelType);
+
+                    if (appId == -1) {
+                        callStatusCallback(appConfig,
+                                           BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE);
+                        mApps.remove(appConfig);
+                    } else {
+                        (mApps.get(appConfig)).mAppId = appId;
+                        callStatusCallback(appConfig,
+                                           BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS);
+                    }
+                }
+                    break;
+                case MESSAGE_UNREGISTER_APPLICATION:
+                {
+                    BluetoothHealthAppConfiguration appConfig =
+                        (BluetoothHealthAppConfiguration) msg.obj;
+                    int appId = (mApps.get(appConfig)).mAppId;
+                    if (!unregisterHealthAppNative(appId)) {
+                        Log.e(TAG, "Failed to unregister application: id: " + appId);
+                        callStatusCallback(appConfig,
+                                           BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE);
+                    }
+                }
+                    break;
+                case MESSAGE_CONNECT_CHANNEL:
+                {
+                    HealthChannel chan = (HealthChannel) msg.obj;
+                    byte[] devAddr = getByteAddress(chan.mDevice);
+                    int appId = (mApps.get(chan.mConfig)).mAppId;
+                    chan.mChannelId = connectChannelNative(devAddr, appId);
+                    if (chan.mChannelId == -1) {
+                        callHealthChannelCallback(chan.mConfig, chan.mDevice,
+                                                  BluetoothHealth.STATE_CHANNEL_DISCONNECTING,
+                                                  BluetoothHealth.STATE_CHANNEL_DISCONNECTED,
+                                                  chan.mChannelFd, chan.mChannelId);
+                        callHealthChannelCallback(chan.mConfig, chan.mDevice,
+                                                  BluetoothHealth.STATE_CHANNEL_DISCONNECTED,
+                                                  BluetoothHealth.STATE_CHANNEL_DISCONNECTING,
+                                                  chan.mChannelFd, chan.mChannelId);
+                        mHealthChannels.remove(chan);
+                    }
+                }
+                    break;
+                case MESSAGE_DISCONNECT_CHANNEL:
+                {
+                    HealthChannel chan = (HealthChannel) msg.obj;
+                    if (!disconnectChannelNative(chan.mChannelId)) {
+                        callHealthChannelCallback(chan.mConfig, chan.mDevice,
+                                                  BluetoothHealth.STATE_CHANNEL_DISCONNECTING,
+                                                  BluetoothHealth.STATE_CHANNEL_CONNECTED,
+                                                  chan.mChannelFd, chan.mChannelId);
+                        callHealthChannelCallback(chan.mConfig, chan.mDevice,
+                                                  BluetoothHealth.STATE_CHANNEL_CONNECTED,
+                                                  BluetoothHealth.STATE_CHANNEL_DISCONNECTING,
+                                                  chan.mChannelFd, chan.mChannelId);
+                    }
+                }
+                    break;
+                case MESSAGE_APP_REGISTRATION_CALLBACK:
+                {
+                    BluetoothHealthAppConfiguration appConfig = findAppConfigByAppId(msg.arg1);
+                    if (appConfig == null) break;
+
+                    int regStatus = convertHalRegStatus(msg.arg2);
+                    callStatusCallback(appConfig, regStatus);
+                    if (regStatus == BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE ||
+                        regStatus == BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS) {
+                        mApps.remove(appConfig);
+                    }
+                }   
+                    break;
+                case MESSAGE_CHANNEL_STATE_CALLBACK:
+                {
+                    ChannelStateEvent channelStateEvent = (ChannelStateEvent) msg.obj;
+                    HealthChannel chan = findChannelById(channelStateEvent.mChannelId);
+                    int newState;
+                    if (chan == null) {
+                        // incoming connection
+                        BluetoothHealthAppConfiguration appConfig =
+                            findAppConfigByAppId(channelStateEvent.mAppId);
+                        BluetoothDevice device = getDevice(channelStateEvent.mAddr);
+                        chan = new HealthChannel(device, appConfig, appConfig.getChannelType());
+                        chan.mChannelId = channelStateEvent.mChannelId;
+                    }
+                    newState = convertHalChannelState(channelStateEvent.mState);
+                    if (newState == BluetoothHealth.STATE_CHANNEL_CONNECTED) {
+                        try {
+                            chan.mChannelFd = ParcelFileDescriptor.dup(channelStateEvent.mFd);
+                        } catch (IOException e) {
+                            Log.e(TAG, "failed to dup ParcelFileDescriptor");
+                            break;
+                        }
+                    }
+                    callHealthChannelCallback(chan.mConfig, chan.mDevice, newState,
+                                              chan.mState, chan.mChannelFd, chan.mChannelId);
+                    chan.mState = newState;
+                    if (channelStateEvent.mState == CONN_STATE_DESTROYED) {
+                        mHealthChannels.remove(chan);
+                    }
+                }
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Handlers for incoming service calls
+     */
+    private final IBluetoothHealth.Stub mBinder = new IBluetoothHealth.Stub() {
+        public boolean registerAppConfiguration(BluetoothHealthAppConfiguration config,
+                                                IBluetoothHealthCallback callback) {
+            enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                                           "Need BLUETOOTH permission");
+            if (mApps.get(config) != null) {
+                if (DBG) log("Config has already been registered");
+                return false;
+            }
+            mApps.put(config, new AppInfo(callback));
+            Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_APPLICATION);
+            msg.obj = config;
+            mHandler.sendMessage(msg);
+            return true;
+        }
+
+        public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
+            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            if (mApps.get(config) == null) {
+                if (DBG) log("unregisterAppConfiguration: no app found");
+                return false;
+            }
+
+            Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_APPLICATION);
+            msg.obj = config;
+            mHandler.sendMessage(msg);
+            return true;
+        }
+
+        public boolean connectChannelToSource(BluetoothDevice device,
+                                              BluetoothHealthAppConfiguration config) {
+            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            return connectChannel(device, config, BluetoothHealth.CHANNEL_TYPE_ANY);
+        }
+
+        public boolean connectChannelToSink(BluetoothDevice device,
+                           BluetoothHealthAppConfiguration config, int channelType) {
+            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            return connectChannel(device, config, channelType);
+        }
+
+        public boolean disconnectChannel(BluetoothDevice device,
+                                         BluetoothHealthAppConfiguration config, int channelId) {
+            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            HealthChannel chan = findChannelById(channelId);
+            if (chan == null) {
+                if (DBG) log("disconnectChannel: no channel found");
+                return false;
+            }
+            Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT_CHANNEL);
+            msg.obj = chan;
+            mHandler.sendMessage(msg);
+            return true;
+        }
+
+        public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
+                                                     BluetoothHealthAppConfiguration config) {
+            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            HealthChannel healthChan = null;
+            for (HealthChannel chan: mHealthChannels) {
+                if (chan.mDevice.equals(device) && chan.mConfig.equals(config)) {
+                    healthChan = chan;
+                }
+            }
+            if (healthChan == null) {
+                Log.e(TAG, "No channel found for device: " + device + " config: " + config);
+                return null;
+            }
+            return healthChan.mChannelFd;
+        }
+
+        public int getHealthDeviceConnectionState(BluetoothDevice device) {
+            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            return getConnectionState(device);
+        }
+
+        public List<BluetoothDevice> getConnectedHealthDevices() {
+            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(
+                    new int[] {BluetoothHealth.STATE_CONNECTED});
+            return devices;
+        }
+
+        public List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) {
+            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(states);
+            return devices;
+        }
+    };
+
+    private void onAppRegistrationState(int appId, int state) {
+        Message msg = mHandler.obtainMessage(MESSAGE_APP_REGISTRATION_CALLBACK);
+        msg.arg1 = appId;
+        msg.arg2 = state;
+        mHandler.sendMessage(msg);
+    }
+
+    private void onChannelStateChanged(int appId, byte[] addr, int cfgIndex,
+                                       int channelId, int state, FileDescriptor pfd) {
+        Message msg = mHandler.obtainMessage(MESSAGE_CHANNEL_STATE_CALLBACK);
+        ChannelStateEvent channelStateEvent = new ChannelStateEvent(appId, addr, cfgIndex,
+                                                                    channelId, state, pfd);
+        msg.obj = channelStateEvent;
+        mHandler.sendMessage(msg);
+    }
+
+    private String getStringChannelType(int type) {
+        if (type == BluetoothHealth.CHANNEL_TYPE_RELIABLE) {
+            return "Reliable";
+        } else if (type == BluetoothHealth.CHANNEL_TYPE_STREAMING) {
+            return "Streaming";
+        } else {
+            return "Any";
+        }
+    }
+
+    private void callStatusCallback(BluetoothHealthAppConfiguration config, int status) {
+        if (DBG) log ("Health Device Application: " + config + " State Change: status:" + status);
+        IBluetoothHealthCallback callback = (mApps.get(config)).mCallback;
+        if (callback == null) {
+            Log.e(TAG, "Callback object null");
+        }
+
+        try {
+            callback.onHealthAppConfigurationStatusChange(config, status);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Remote Exception:" + e);
+        }
+    }
+
+    private BluetoothHealthAppConfiguration findAppConfigByAppId(int appId) {
+        BluetoothHealthAppConfiguration appConfig = null;
+        for (Entry<BluetoothHealthAppConfiguration, AppInfo> e : mApps.entrySet()) {
+            if (appId == (e.getValue()).mAppId) {
+                appConfig = e.getKey();
+                break;
+            }
+        }
+        if (appConfig == null) {
+            Log.e(TAG, "No appConfig found for " + appId);
+        }
+        return appConfig;
+    }
+
+    private int convertHalRegStatus(int halRegStatus) {
+        switch (halRegStatus) {
+            case APP_REG_STATE_REG_SUCCESS:
+                return BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS;
+            case APP_REG_STATE_REG_FAILED:
+                return BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE;
+            case APP_REG_STATE_DEREG_SUCCESS:
+                return BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS;
+            case APP_REG_STATE_DEREG_FAILED:
+                return BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE;
+        }
+        Log.e(TAG, "Unexpected App Registration state: " + halRegStatus);
+        return BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE;
+    }
+
+    private int convertHalChannelState(int halChannelState) {
+        switch (halChannelState) {
+            case CONN_STATE_CONNECTED:
+                return BluetoothHealth.STATE_CHANNEL_CONNECTED;
+            case CONN_STATE_CONNECTING:
+                return BluetoothHealth.STATE_CHANNEL_CONNECTING;
+            case CONN_STATE_DISCONNECTING:
+                return BluetoothHealth.STATE_CHANNEL_DISCONNECTING;
+            case CONN_STATE_DISCONNECTED:
+                return BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+            case CONN_STATE_DESTROYED:
+                // TODO(BT) add BluetoothHealth.STATE_CHANNEL_DESTROYED;
+                return BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+            default:
+                Log.e(TAG, "Unexpected channel state: " + halChannelState);
+                return BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+        }
+    }
+
+    private boolean connectChannel(BluetoothDevice device,
+                                   BluetoothHealthAppConfiguration config, int channelType) {
+        if (mApps.get(config) == null) {
+            Log.e(TAG, "connectChannel fail to get a app id from config");
+            return false;
+        }
+
+        HealthChannel chan = new HealthChannel(device, config, channelType);
+        mHealthChannels.add(chan);
+
+        Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_CHANNEL);
+        msg.obj = chan;
+        mHandler.sendMessage(msg);
+
+        return true;
+    }
+
+    private void callHealthChannelCallback(BluetoothHealthAppConfiguration config,
+            BluetoothDevice device, int state, int prevState, ParcelFileDescriptor fd, int id) {
+        broadcastHealthDeviceStateChange(device, state);
+
+        if (DBG) log("Health Device Callback: " + device + " State Change: " + prevState + "->" +
+                     state);
+
+        ParcelFileDescriptor dupedFd = null;
+        if (fd != null) {
+            try {
+                dupedFd = fd.dup();
+            } catch (IOException e) {
+                dupedFd = null;
+                Log.e(TAG, "Exception while duping: " + e);
+            }
+        }
+
+        IBluetoothHealthCallback callback = (mApps.get(config)).mCallback;
+        if (callback == null) {
+            Log.e(TAG, "No callback found for config: " + config);
+            return;
+        }
+
+        try {
+            callback.onHealthChannelStateChange(config, device, prevState, state, dupedFd, id);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Remote Exception:" + e);
+        }
+    }
+
+    /**
+     * This function sends the intent for the updates on the connection status to the remote device.
+     * Note that multiple channels can be connected to the remote device by multiple applications.
+     * This sends an intent for the update to the device connection status and not the channel
+     * connection status. Only the following state transitions are possible:
+     *
+     * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTING}
+     * {@link BluetoothHealth#STATE_CONNECTING} to {@link BluetoothHealth#STATE_CONNECTED}
+     * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTING}
+     * {@link BluetoothHealth#STATE_DISCONNECTING} to {@link BluetoothHealth#STATE_DISCONNECTED}
+     * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTED}
+     * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTED}
+     * {@link BluetoothHealth#STATE_CONNECTING} to {{@link BluetoothHealth#STATE_DISCONNECTED}
+     *
+     * @param device
+     * @param prevChannelState
+     * @param newChannelState
+     * @hide
+     */
+    private void broadcastHealthDeviceStateChange(BluetoothDevice device, int newChannelState) {
+        if (mHealthDevices.get(device) == null) {
+            mHealthDevices.put(device, BluetoothHealth.STATE_DISCONNECTED);
+        }
+
+        int currDeviceState = mHealthDevices.get(device);
+        int newDeviceState = convertState(newChannelState);
+
+        if (currDeviceState == newDeviceState) return;
+
+        boolean sendIntent = false;
+        List<HealthChannel> chan;
+        switch (currDeviceState) {
+            case BluetoothHealth.STATE_DISCONNECTED:
+                // there was no connection or connect/disconnect attemp with the remote device
+                sendIntent = true;
+                break;
+            case BluetoothHealth.STATE_CONNECTING:
+                // there was no connection, there was a connecting attempt going on
+
+                // Channel got connected.
+                if (newDeviceState == BluetoothHealth.STATE_CONNECTED) {
+                    sendIntent = true;
+                } else {
+                    // Channel got disconnected
+                    chan = findChannelByStates(device, new int [] {
+                            BluetoothHealth.STATE_CHANNEL_CONNECTING,
+                            BluetoothHealth.STATE_CHANNEL_DISCONNECTING});
+                    if (chan.isEmpty()) {
+                        sendIntent = true;
+                    }
+                }
+                break;
+            case BluetoothHealth.STATE_CONNECTED:
+                // there was at least one connection
+
+                // Channel got disconnected or is in disconnecting state.
+                chan = findChannelByStates(device, new int [] {
+                        BluetoothHealth.STATE_CHANNEL_CONNECTING,
+                        BluetoothHealth.STATE_CHANNEL_CONNECTED});
+                if (chan.isEmpty()) {
+                    sendIntent = true;
+                }
+                break;
+            case BluetoothHealth.STATE_DISCONNECTING:
+                // there was no connected channel with the remote device
+                // We were disconnecting all the channels with the remote device
+
+                // Channel got disconnected.
+                chan = findChannelByStates(device, new int [] {
+                        BluetoothHealth.STATE_CHANNEL_CONNECTING,
+                        BluetoothHealth.STATE_CHANNEL_DISCONNECTING});
+                if (chan.isEmpty()) {
+                    updateAndSendIntent(device, newDeviceState, currDeviceState);
+                }
+                break;
+        }
+        if (sendIntent)
+            updateAndSendIntent(device, newDeviceState, currDeviceState);
+    }
+
+    private void updateAndSendIntent(BluetoothDevice device, int newDeviceState,
+            int prevDeviceState) {
+        if (newDeviceState == BluetoothHealth.STATE_DISCONNECTED) {
+            mHealthDevices.remove(device);
+        } else {
+            mHealthDevices.put(device, newDeviceState);
+        }
+        try {
+            mAdapterService.sendConnectionStateChange(device, BluetoothProfile.HEALTH,
+                                                      newDeviceState, prevDeviceState);
+        } catch (RemoteException e) {
+            Log.e(TAG, Log.getStackTraceString(new Throwable()));
+        }
+    }
+
+    /**
+     * This function converts the channel connection state to device connection state.
+     *
+     * @param state
+     * @return
+     */
+    private int convertState(int state) {
+        switch (state) {
+            case BluetoothHealth.STATE_CHANNEL_CONNECTED:
+                return BluetoothHealth.STATE_CONNECTED;
+            case BluetoothHealth.STATE_CHANNEL_CONNECTING:
+                return BluetoothHealth.STATE_CONNECTING;
+            case BluetoothHealth.STATE_CHANNEL_DISCONNECTING:
+                return BluetoothHealth.STATE_DISCONNECTING;
+            case BluetoothHealth.STATE_CHANNEL_DISCONNECTED:
+                return BluetoothHealth.STATE_DISCONNECTED;
+        }
+        Log.e(TAG, "Mismatch in Channel and Health Device State: " + state);
+        return BluetoothHealth.STATE_DISCONNECTED;
+    }
+
+    private int convertRoleToHal(int role) {
+        if (role == BluetoothHealth.SOURCE_ROLE) return MDEP_ROLE_SOURCE;
+        if (role == BluetoothHealth.SINK_ROLE) return MDEP_ROLE_SINK;
+        Log.e(TAG, "unkonw role: " + role);
+        return MDEP_ROLE_SINK;
+    }
+
+    private int convertChannelTypeToHal(int channelType) {
+        if (channelType == BluetoothHealth.CHANNEL_TYPE_RELIABLE) return CHANNEL_TYPE_RELIABLE;
+        if (channelType == BluetoothHealth.CHANNEL_TYPE_STREAMING) return CHANNEL_TYPE_STREAMING;
+        if (channelType == BluetoothHealth.CHANNEL_TYPE_ANY) return CHANNEL_TYPE_ANY;
+        Log.e(TAG, "unkonw channel type: " + channelType);
+        return CHANNEL_TYPE_ANY;
+    }
+
+    private HealthChannel findChannelById(int id) {
+        for (HealthChannel chan : mHealthChannels) {
+            if (chan.mChannelId == id) return chan;
+        }
+        Log.e(TAG, "No channel found by id: " + id);
+        return null;
+    }
+
+    private List<HealthChannel> findChannelByStates(BluetoothDevice device, int[] states) {
+        List<HealthChannel> channels = new ArrayList<HealthChannel>();
+        for (HealthChannel chan: mHealthChannels) {
+            if (chan.mDevice.equals(device)) {
+                for (int state : states) {
+                    if (chan.mState == state) {
+                        channels.add(chan);
+                    }
+                }
+            }
+        }
+        return channels;
+    }
+
+    private BluetoothDevice getDevice(byte[] address) {
+        return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
+    }
+
+    private byte[] getByteAddress(BluetoothDevice device) {
+        return Utils.getBytesFromAddress(device.getAddress());
+    }
+
+    private int getConnectionState(BluetoothDevice device) {
+        if (mHealthDevices.get(device) == null) {
+            return BluetoothHealth.STATE_DISCONNECTED;
+        }
+        return mHealthDevices.get(device);
+    }
+
+    List<BluetoothDevice> lookupHealthDevicesMatchingStates(int[] states) {
+        List<BluetoothDevice> healthDevices = new ArrayList<BluetoothDevice>();
+
+        for (BluetoothDevice device: mHealthDevices.keySet()) {
+            int healthDeviceState = getConnectionState(device);
+            for (int state : states) {
+                if (state == healthDeviceState) {
+                    healthDevices.add(device);
+                    break;
+                }
+            }
+        }
+        return healthDevices;
+    }
+
+    private void log(String msg) {
+        Log.d(TAG, msg);
+    }
+
+    private class AppInfo {
+        private IBluetoothHealthCallback mCallback;
+        private int mAppId;
+
+        private AppInfo(IBluetoothHealthCallback callback) {
+            mCallback = callback;
+            mAppId = -1;
+        }
+    }
+
+    private class HealthChannel {
+        private ParcelFileDescriptor mChannelFd;
+        private BluetoothDevice mDevice;
+        private BluetoothHealthAppConfiguration mConfig;
+        // BluetoothHealth channel state
+        private int mState;
+        private int mChannelType;
+        private int mChannelId;
+
+        private HealthChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config,
+                      int channelType) {
+             mChannelFd = null;
+             mDevice = device;
+             mConfig = config;
+             mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+             mChannelType = channelType;
+             mChannelId = -1;
+        }
+    }
+
+    // Channel state event from Hal
+    private class ChannelStateEvent {
+        int mAppId;
+        byte[] mAddr;
+        int mCfgIndex;
+        int mChannelId;
+        int mState;
+        FileDescriptor mFd;
+
+        private ChannelStateEvent(int appId, byte[] addr, int cfgIndex,
+                                  int channelId, int state, FileDescriptor fileDescriptor) {
+            mAppId = appId;
+            mAddr = addr;
+            mCfgIndex = cfgIndex;
+            mState = state;
+            mChannelId = channelId;
+            mFd = fileDescriptor;
+        }
+    }
+
+    // Constants matching Hal header file bt_hl.h
+    // bthl_app_reg_state_t
+    private static final int APP_REG_STATE_REG_SUCCESS = 0;
+    private static final int APP_REG_STATE_REG_FAILED = 1;
+    private static final int APP_REG_STATE_DEREG_SUCCESS = 2;
+    private static final int APP_REG_STATE_DEREG_FAILED = 3;
+
+    // bthl_channel_state_t
+    private static final int CONN_STATE_CONNECTING = 0;
+    private static final int CONN_STATE_CONNECTED = 1;
+    private static final int CONN_STATE_DISCONNECTING = 2;
+    private static final int CONN_STATE_DISCONNECTED = 3;
+    private static final int CONN_STATE_DESTROYED = 4;
+
+    // bthl_mdep_role_t
+    private static final int MDEP_ROLE_SOURCE = 0;
+    private static final int MDEP_ROLE_SINK = 1;
+
+    // bthl_channel_type_t
+    private static final int CHANNEL_TYPE_RELIABLE = 0;
+    private static final int CHANNEL_TYPE_STREAMING = 1;
+    private static final int CHANNEL_TYPE_ANY =2;
+
+    private native static void classInitNative();
+    private native void initializeNativeDataNative();
+    private native int registerHealthAppNative(int dataType, int role, String name, int channelType);
+    private native boolean unregisterHealthAppNative(int appId);
+    private native int connectChannelNative(byte[] btAddress, int appId);
+    private native boolean disconnectChannelNative(int channelId);
+}