From: Matthew Xie Date: Fri, 23 Mar 2012 00:32:29 +0000 (-0700) Subject: Initial implementation of hdp service and jni X-Git-Tag: android-7.1.2_r17~1147 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=676cb1bdd1c14c7af56562bab51f168e7f8f6d62;p=android-x86%2Fpackages-apps-Bluetooth.git Initial implementation of hdp service and jni Change-Id: Ieca906080835098383cabbc13fa914363459f555 --- diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f757a497..06208317 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -201,5 +201,12 @@ + + + + + diff --git a/jni/Android.mk b/jni/Android.mk index 21c5bc3c..5f07ab52 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -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) \ diff --git a/jni/com_android_bluetooth.h b/jni/com_android_bluetooth.h index 9d4f67dc..7f842677 100644 --- a/jni/com_android_bluetooth.h +++ b/jni/com_android_bluetooth.h @@ -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 */ diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp index 9d49b65c..a7826bb9 100755 --- a/jni/com_android_bluetooth_btservice_AdapterService.cpp +++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp @@ -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 index 00000000..07c0e4f2 --- /dev/null +++ b/jni/com_android_bluetooth_hdp.cpp @@ -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 + +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(®_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)); +} + +} diff --git a/jni/com_android_bluetooth_hfp.cpp b/jni/com_android_bluetooth_hfp.cpp index f8b0b16d..ef84252b 100644 --- a/jni/com_android_bluetooth_hfp.cpp +++ b/jni/com_android_bluetooth_hfp.cpp @@ -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; diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java index 52028bbd..6832a544 100755 --- a/src/com/android/bluetooth/btservice/AdapterService.java +++ b/src/com/android/bluetooth/btservice/AdapterService.java @@ -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 index 00000000..2ae3f65e --- /dev/null +++ b/src/com/android/bluetooth/hdp/HealthService.java @@ -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 mHealthChannels; + private Map mApps; + private Map 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()); + mApps = Collections.synchronizedMap(new HashMap()); + mHealthDevices = Collections.synchronizedMap(new HashMap()); + + 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 getConnectedHealthDevices() { + enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + List devices = lookupHealthDevicesMatchingStates( + new int[] {BluetoothHealth.STATE_CONNECTED}); + return devices; + } + + public List getHealthDevicesMatchingConnectionStates(int[] states) { + enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + List 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 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 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 findChannelByStates(BluetoothDevice device, int[] states) { + List channels = new ArrayList(); + 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 lookupHealthDevicesMatchingStates(int[] states) { + List healthDevices = new ArrayList(); + + 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); +}