From 6654f5c903de510a70f9e72cd5ad7837b615d93f Mon Sep 17 00:00:00 2001 From: fredc Date: Thu, 12 Apr 2012 00:18:52 -0700 Subject: Non persistent adapter service Change-Id: I65e1c18e2899cea0a1e5c0102c4d24d39dce0249 Conflicts: jni/com_android_bluetooth_hdp.cpp jni/com_android_bluetooth_hid.cpp Conflicts: jni/com_android_bluetooth_hid.cpp --- AndroidManifest.xml | 21 +- jni/Android.mk | 3 +- jni/com_android_bluetooth.h | 2 + jni/com_android_bluetooth_a2dp.cpp | 65 ++- ..._android_bluetooth_btservice_AdapterService.cpp | 9 +- jni/com_android_bluetooth_hdp.cpp | 67 ++- jni/com_android_bluetooth_hfp.cpp | 67 ++- jni/com_android_bluetooth_hid.cpp | 73 ++- jni/com_android_bluetooth_pan.cpp | 239 +++++++++ src/com/android/bluetooth/Utils.java | 15 + src/com/android/bluetooth/a2dp/A2dpService.java | 63 ++- .../android/bluetooth/a2dp/A2dpStateMachine.java | 9 +- .../android/bluetooth/btservice/AdapterApp.java | 36 ++ .../bluetooth/btservice/AdapterProperties.java | 12 +- .../bluetooth/btservice/AdapterService.java | 290 +++++++++-- .../android/bluetooth/btservice/AdapterState.java | 25 +- .../bluetooth/btservice/BondStateMachine.java | 21 +- .../android/bluetooth/btservice/JniCallbacks.java | 12 +- .../android/bluetooth/btservice/RemoteDevices.java | 14 +- src/com/android/bluetooth/hdp/HealthService.java | 82 +++- src/com/android/bluetooth/hfp/HeadsetService.java | 69 ++- .../android/bluetooth/hfp/HeadsetStateMachine.java | 20 +- src/com/android/bluetooth/hid/HidService.java | 59 ++- src/com/android/bluetooth/pan/PanService.java | 544 +++++++++++++++++++++ .../bluetooth/pbap/BluetoothPbapReceiver.java | 6 +- .../bluetooth/pbap/BluetoothPbapService.java | 33 +- 26 files changed, 1711 insertions(+), 145 deletions(-) create mode 100644 jni/com_android_bluetooth_pan.cpp create mode 100644 src/com/android/bluetooth/btservice/AdapterApp.java create mode 100644 src/com/android/bluetooth/pan/PanService.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index df07838df..a104eebbd 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -39,10 +39,13 @@ + + + + + + + + + + + + + diff --git a/jni/Android.mk b/jni/Android.mk index 5f07ab525..4098907f7 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -7,7 +7,8 @@ LOCAL_SRC_FILES:= \ com_android_bluetooth_hfp.cpp \ com_android_bluetooth_a2dp.cpp \ com_android_bluetooth_hid.cpp \ - com_android_bluetooth_hdp.cpp + com_android_bluetooth_hdp.cpp \ + com_android_bluetooth_pan.cpp LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ diff --git a/jni/com_android_bluetooth.h b/jni/com_android_bluetooth.h index 7f8426771..366fbe1c4 100644 --- a/jni/com_android_bluetooth.h +++ b/jni/com_android_bluetooth.h @@ -27,6 +27,8 @@ int register_com_android_bluetooth_hid(JNIEnv* env); int register_com_android_bluetooth_hdp(JNIEnv* env); +int register_com_android_bluetooth_pan(JNIEnv* env); + } #endif /* COM_ANDROID_BLUETOOTH_H */ diff --git a/jni/com_android_bluetooth_a2dp.cpp b/jni/com_android_bluetooth_a2dp.cpp index ebc23a5a8..710530e29 100644 --- a/jni/com_android_bluetooth_a2dp.cpp +++ b/jni/com_android_bluetooth_a2dp.cpp @@ -69,6 +69,7 @@ static void classInitNative(JNIEnv* env, jclass clazz) { method_onConnectionStateChanged = env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V"); + /* if ( (btInf = getBluetoothInterface()) == NULL) { LOGE("Bluetooth module is not loaded"); return; @@ -79,24 +80,76 @@ static void classInitNative(JNIEnv* env, jclass clazz) { LOGE("Failed to get Bluetooth A2DP Interface"); return; } + */ // TODO(BT) do this only once or // Do we need to do this every time the BT reenables? + /* if ( (status = sBluetoothA2dpInterface->init(&sBluetoothA2dpCallbacks)) != BT_STATUS_SUCCESS) { LOGE("Failed to initialize Bluetooth A2DP, status: %d", status); sBluetoothA2dpInterface = NULL; return; - } + }*/ LOGI("%s: succeeds", __FUNCTION__); } -static void initializeNativeDataNative(JNIEnv *env, jobject object) { - // TODO(BT) clean it up when a2dp service is stopped - // Is there a need to do cleanup since A2DP is always present for phone and tablet? +static void initNative(JNIEnv *env, jobject object) { + const bt_interface_t* btInf; + bt_status_t status; + + if ( (btInf = getBluetoothInterface()) == NULL) { + LOGE("Bluetooth module is not loaded"); + return; + } + + if (sBluetoothA2dpInterface !=NULL) { + LOGW("Cleaning up A2DP Interface before initializing..."); + sBluetoothA2dpInterface->cleanup(); + sBluetoothA2dpInterface = NULL; + } + + if (mCallbacksObj != NULL) { + LOGW("Cleaning up A2DP callback object"); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = NULL; + } + + if ( (sBluetoothA2dpInterface = (btav_interface_t *) + btInf->get_profile_interface(BT_PROFILE_ADVANCED_AUDIO_ID)) == NULL) { + LOGE("Failed to get Bluetooth A2DP Interface"); + return; + } + + if ( (status = sBluetoothA2dpInterface->init(&sBluetoothA2dpCallbacks)) != BT_STATUS_SUCCESS) { + LOGE("Failed to initialize Bluetooth A2DP, status: %d", status); + sBluetoothA2dpInterface = NULL; + return; + } + mCallbacksObj = env->NewGlobalRef(object); } +static void cleanupNative(JNIEnv *env, jobject object) { + const bt_interface_t* btInf; + bt_status_t status; + + if ( (btInf = getBluetoothInterface()) == NULL) { + LOGE("Bluetooth module is not loaded"); + return; + } + + if (sBluetoothA2dpInterface !=NULL) { + sBluetoothA2dpInterface->cleanup(); + sBluetoothA2dpInterface = NULL; + } + + if (mCallbacksObj != NULL) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = NULL; + } +} + static jboolean connectA2dpNative(JNIEnv *env, jobject object, jbyteArray address) { jbyte *addr; bt_bdaddr_t * btAddr; @@ -140,10 +193,10 @@ static jboolean disconnectA2dpNative(JNIEnv *env, jobject object, jbyteArray add static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void *) classInitNative}, - {"initializeNativeDataNative", "()V", (void *) initializeNativeDataNative}, + {"initNative", "()V", (void *) initNative}, + {"cleanupNative", "()V", (void *) cleanupNative}, {"connectA2dpNative", "([B)Z", (void *) connectA2dpNative}, {"disconnectA2dpNative", "([B)Z", (void *) disconnectA2dpNative}, - // TODO(BT) clean up }; int register_com_android_bluetooth_a2dp(JNIEnv* env) diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp index 2af399b85..a9c276f93 100755 --- a/jni/com_android_bluetooth_btservice_AdapterService.cpp +++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp @@ -496,6 +496,8 @@ static bool cleanupNative(JNIEnv *env, jobject obj) { if (!sBluetoothInterface) return result; sBluetoothInterface->cleanup(); + LOGV("%s: return from cleanup",__FUNCTION__); + env->DeleteGlobalRef(sJniCallbacksObj); return JNI_TRUE; } @@ -837,7 +839,7 @@ static int createSocketChannelNative(JNIEnv *env, jobject object, jint type, LOGE("failed to get uuid"); goto Fail; } - + LOGE("SOCK FLAG = %x ***********************",flag); if ( (status = sBluetoothSocketInterface->listen((btsock_type_t) type, service_name, (const uint8_t*) uuid, channel, &socket_fd, flag)) != BT_STATUS_SUCCESS) { LOGE("Socket listen failed: %d", status); @@ -934,5 +936,10 @@ jint JNI_OnLoad(JavaVM *jvm, void *reserved) return JNI_ERR; } + if ((status = android::register_com_android_bluetooth_pan(e)) < 0) { + LOGE("jni pan registration failure: %d", status); + return JNI_ERR; + } + return JNI_VERSION_1_6; } diff --git a/jni/com_android_bluetooth_hdp.cpp b/jni/com_android_bluetooth_hdp.cpp index 07c0e4f21..2389307e8 100644 --- a/jni/com_android_bluetooth_hdp.cpp +++ b/jni/com_android_bluetooth_hdp.cpp @@ -87,13 +87,14 @@ static bthl_callbacks_t sBluetoothHdpCallbacks = { static void classInitNative(JNIEnv* env, jclass clazz) { int err; - const bt_interface_t* btInf; - bt_status_t status; +// 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; @@ -112,15 +113,69 @@ static void classInitNative(JNIEnv* env, jclass clazz) { sBluetoothHdpInterface = NULL; return; } +*/ LOGI("%s: succeeds", __FUNCTION__); } -static void initializeNativeDataNative(JNIEnv *env, jobject object) { - // TODO(BT) clean it up when hdp service is stopped +static void initializeNative(JNIEnv *env, jobject object) { + const bt_interface_t* btInf; + bt_status_t status; + + if ( (btInf = getBluetoothInterface()) == NULL) { + LOGE("Bluetooth module is not loaded"); + return; + } + + if (sBluetoothHdpInterface !=NULL) { + LOGW("Cleaning up Bluetooth Health Interface before initializing..."); + sBluetoothHdpInterface->cleanup(); + sBluetoothHdpInterface = NULL; + } + + if (mCallbacksObj != NULL) { + LOGW("Cleaning up Bluetooth Health callback object"); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = NULL; + } + + if ( (sBluetoothHdpInterface = (bthl_interface_t *) + btInf->get_profile_interface(BT_PROFILE_HEALTH_ID)) == NULL) { + LOGE("Failed to get Bluetooth Health Interface"); + return; + } + + if ( (status = sBluetoothHdpInterface->init(&sBluetoothHdpCallbacks)) != BT_STATUS_SUCCESS) { + LOGE("Failed to initialize Bluetooth HDP, status: %d", status); + sBluetoothHdpInterface = NULL; + return; + } + mCallbacksObj = env->NewGlobalRef(object); } +static void cleanupNative(JNIEnv *env, jobject object) { + const bt_interface_t* btInf; + bt_status_t status; + + if ( (btInf = getBluetoothInterface()) == NULL) { + LOGE("Bluetooth module is not loaded"); + return; + } + + if (sBluetoothHdpInterface !=NULL) { + LOGW("Cleaning up Bluetooth Health Interface..."); + sBluetoothHdpInterface->cleanup(); + sBluetoothHdpInterface = NULL; + } + + if (mCallbacksObj != NULL) { + LOGW("Cleaning up Bluetooth Health object"); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = NULL; + } +} + static jint registerHealthAppNative(JNIEnv *env, jobject object, jint data_type, jint role, jstring name, jint channel_type) { bt_status_t status; @@ -201,12 +256,12 @@ static jboolean disconnectChannelNative(JNIEnv *env, jobject object, jint channe static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void *) classInitNative}, - {"initializeNativeDataNative", "()V", (void *) initializeNativeDataNative}, + {"initializeNative", "()V", (void *) initializeNative}, + {"cleanupNative", "()V", (void *) cleanupNative}, {"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) diff --git a/jni/com_android_bluetooth_hfp.cpp b/jni/com_android_bluetooth_hfp.cpp index ef84252bb..c3d4c9225 100644 --- a/jni/com_android_bluetooth_hfp.cpp +++ b/jni/com_android_bluetooth_hfp.cpp @@ -202,8 +202,10 @@ static bthf_callbacks_t sBluetoothHfpCallbacks = { static void classInitNative(JNIEnv* env, jclass clazz) { int err; + /* const bt_interface_t* btInf; bt_status_t status; + */ method_onConnectionStateChanged = env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V"); @@ -223,6 +225,7 @@ static void classInitNative(JNIEnv* env, jclass clazz) { method_onUnknownAt = env->GetMethodID(clazz, "onUnknownAt", "(Ljava/lang/String;)V"); method_onKeyPressed = env->GetMethodID(clazz, "onKeyPressed", "()V"); + /* if ( (btInf = getBluetoothInterface()) == NULL) { LOGE("Bluetooth module is not loaded"); return; @@ -241,17 +244,69 @@ static void classInitNative(JNIEnv* env, jclass clazz) { sBluetoothHfpInterface = NULL; return; } + */ LOGI("%s: succeeds", __FUNCTION__); } -static void initializeNativeDataNative(JNIEnv *env, jobject object) { - // TODO(BT) clean it up when hfp service is stopped - // Is there a need to do cleanup since HFP is always present for phone? - // We need handle it for tablets. But should that be handle at compile time? +static void initializeNative(JNIEnv *env, jobject object) { + const bt_interface_t* btInf; + bt_status_t status; + + if ( (btInf = getBluetoothInterface()) == NULL) { + LOGE("Bluetooth module is not loaded"); + return; + } + + if (sBluetoothHfpInterface !=NULL) { + LOGW("Cleaning up Bluetooth Handsfree Interface before initializing..."); + sBluetoothHfpInterface->cleanup(); + sBluetoothHfpInterface = NULL; + } + + if (mCallbacksObj != NULL) { + LOGW("Cleaning up Bluetooth Handsfree callback object"); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = NULL; + } + + if ( (sBluetoothHfpInterface = (bthf_interface_t *) + btInf->get_profile_interface(BT_PROFILE_HANDSFREE_ID)) == NULL) { + LOGE("Failed to get Bluetooth Handsfree Interface"); + return; + } + + if ( (status = sBluetoothHfpInterface->init(&sBluetoothHfpCallbacks)) != BT_STATUS_SUCCESS) { + LOGE("Failed to initialize Bluetooth HFP, status: %d", status); + sBluetoothHfpInterface = NULL; + return; + } + mCallbacksObj = env->NewGlobalRef(object); } +static void cleanupNative(JNIEnv *env, jobject object) { + const bt_interface_t* btInf; + bt_status_t status; + + if ( (btInf = getBluetoothInterface()) == NULL) { + LOGE("Bluetooth module is not loaded"); + return; + } + + if (sBluetoothHfpInterface !=NULL) { + LOGW("Cleaning up Bluetooth Handsfree Interface..."); + sBluetoothHfpInterface->cleanup(); + sBluetoothHfpInterface = NULL; + } + + if (mCallbacksObj != NULL) { + LOGW("Cleaning up Bluetooth Handsfree callback object"); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = NULL; + } +} + static jboolean connectHfpNative(JNIEnv *env, jobject object, jbyteArray address) { jbyte *addr; bt_status_t status; @@ -468,7 +523,8 @@ static jboolean phoneStateChangeNative(JNIEnv *env, jobject object, jint num_act static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void *) classInitNative}, - {"initializeNativeDataNative", "()V", (void *) initializeNativeDataNative}, + {"initializeNative", "()V", (void *) initializeNative}, + {"cleanupNative", "()V", (void *) cleanupNative}, {"connectHfpNative", "([B)Z", (void *) connectHfpNative}, {"disconnectHfpNative", "([B)Z", (void *) disconnectHfpNative}, {"connectAudioNative", "([B)Z", (void *) connectAudioNative}, @@ -483,7 +539,6 @@ static JNINativeMethod sMethods[] = { {"atResponseCodeNative", "(I)Z", (void *)atResponseCodeNative}, {"clccResponseNative", "(IIIIZLjava/lang/String;I)Z", (void *) clccResponseNative}, {"phoneStateChangeNative", "(IIILjava/lang/String;I)Z", (void *) phoneStateChangeNative}, - // TODO(BT) clean up }; int register_com_android_bluetooth_hfp(JNIEnv* env) diff --git a/jni/com_android_bluetooth_hid.cpp b/jni/com_android_bluetooth_hid.cpp index d0c178daa..b517ad05a 100755 --- a/jni/com_android_bluetooth_hid.cpp +++ b/jni/com_android_bluetooth_hid.cpp @@ -132,13 +132,14 @@ static bthh_callbacks_t sBluetoothHidCallbacks = { static void classInitNative(JNIEnv* env, jclass clazz) { int err; - const bt_interface_t* btInf; - bt_status_t status; +// const bt_interface_t* btInf; +// bt_status_t status; method_onConnectStateChanged = env->GetMethodID(clazz, "onConnectStateChanged", "([BI)V"); method_onGetProtocolMode = env->GetMethodID(clazz, "onGetProtocolMode", "([BI)V"); method_onVirtualUnplug = env->GetMethodID(clazz, "onVirtualUnplug", "([BI)V"); +/* if ( (btInf = getBluetoothInterface()) == NULL) { LOGE("Bluetooth module is not loaded"); return; @@ -158,14 +159,73 @@ static void classInitNative(JNIEnv* env, jclass clazz) { return; } - LOGI("%s: succeeds", __FUNCTION__); + ALOGI("%s: succeeds", __FUNCTION__); +*/ } -static void initializeNativeDataNative(JNIEnv *env, jobject object) { - // TODO(BT) clean it up when hid service is stopped +static void initializeNative(JNIEnv *env, jobject object) { + const bt_interface_t* btInf; + bt_status_t status; + + if ( (btInf = getBluetoothInterface()) == NULL) { + LOGE("Bluetooth module is not loaded"); + return; + } + + if (sBluetoothHidInterface !=NULL) { + LOGW("Cleaning up Bluetooth HID Interface before initializing..."); + sBluetoothHidInterface->cleanup(); + sBluetoothHidInterface = NULL; + } + + if (mCallbacksObj != NULL) { + LOGW("Cleaning up Bluetooth GID callback object"); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = NULL; + } + + + if ( (sBluetoothHidInterface = (bthh_interface_t *) + btInf->get_profile_interface(BT_PROFILE_HIDHOST_ID)) == NULL) { + LOGE("Failed to get Bluetooth HID Interface"); + return; + } + + if ( (status = sBluetoothHidInterface->init(&sBluetoothHidCallbacks)) != BT_STATUS_SUCCESS) { + LOGE("Failed to initialize Bluetooth HID, status: %d", status); + sBluetoothHidInterface = NULL; + return; + } + + + mCallbacksObj = env->NewGlobalRef(object); } +static void cleanupNative(JNIEnv *env, jobject object) { + const bt_interface_t* btInf; + bt_status_t status; + + if ( (btInf = getBluetoothInterface()) == NULL) { + LOGE("Bluetooth module is not loaded"); + return; + } + + if (sBluetoothHidInterface !=NULL) { + LOGW("Cleaning up Bluetooth HID Interface..."); + sBluetoothHidInterface->cleanup(); + sBluetoothHidInterface = NULL; + } + + if (mCallbacksObj != NULL) { + LOGW("Cleaning up Bluetooth GID callback object"); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = NULL; + } + + env->DeleteGlobalRef(mCallbacksObj); +} + static jboolean connectHidNative(JNIEnv *env, jobject object, jbyteArray address) { bt_status_t status; jbyte *addr; @@ -372,7 +432,8 @@ static jboolean sendDataNative(JNIEnv *env, jobject object, jbyteArray address, static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void *) classInitNative}, - {"initializeNativeDataNative", "()V", (void *) initializeNativeDataNative}, + {"initializeNative", "()V", (void *) initializeNative}, + {"cleanupNative", "()V", (void *) cleanupNative}, {"connectHidNative", "([B)Z", (void *) connectHidNative}, {"disconnectHidNative", "([B)Z", (void *) disconnectHidNative}, {"getProtocolModeNative", "([B)Z", (void *) getProtocolModeNative}, diff --git a/jni/com_android_bluetooth_pan.cpp b/jni/com_android_bluetooth_pan.cpp new file mode 100644 index 000000000..ccdf33b6f --- /dev/null +++ b/jni/com_android_bluetooth_pan.cpp @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2012 Google Inc. + */ + +#define LOG_TAG "BluetoothPanServiceJni" + +#define LOG_NDEBUG 0 + +#define CHECK_CALLBACK_ENV \ + if (!checkCallbackThread()) { \ + error("Callback: '%s' is not called on the correct thread", __FUNCTION__);\ + return; \ + } + +#include "com_android_bluetooth.h" +#include "hardware/bt_pan.h" +#include "utils/Log.h" +#include "android_runtime/AndroidRuntime.h" + +#include + +#include +#define info(fmt, ...) LOGI ("%s(L%d): " fmt,__FUNCTION__, __LINE__, ## __VA_ARGS__) +#define debug(fmt, ...) LOGD ("%s(L%d): " fmt,__FUNCTION__, __LINE__, ## __VA_ARGS__) +#define warn(fmt, ...) LOGW ("## WARNING : %s(L%d): " fmt "##",__FUNCTION__, __LINE__, ## __VA_ARGS__) +#define error(fmt, ...) LOGE ("## ERROR : %s(L%d): " fmt "##",__FUNCTION__, __LINE__, ## __VA_ARGS__) +#define asrt(s) if(!(s)) LOGE ("## %s(L%d): ASSERT %s failed! ##",__FUNCTION__, __LINE__, #s) + + +namespace android { + +static jmethodID method_onConnectStateChanged; +static jmethodID method_onControlStateChanged; + +static const btpan_interface_t *sPanIf = NULL; +static jobject mCallbacksObj = NULL; +static JNIEnv *sCallbackEnv = NULL; + +static bool checkCallbackThread() { + sCallbackEnv = getCallbackEnv(); + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + if (sCallbackEnv != env || sCallbackEnv == NULL) return false; + return true; +} + +static void control_state_callback(btpan_control_state_t state, bt_status_t error, int local_role, + const char* ifname) { + debug("state:%d, local_role:%d, ifname:%s", state, local_role, ifname); + CHECK_CALLBACK_ENV + jstring js_ifname = sCallbackEnv->NewStringUTF(ifname); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onControlStateChanged, (jint)state, (jint)error, + (jint)local_role, js_ifname); +} + +static void connection_state_callback(btpan_connection_state_t state, bt_status_t error, const bt_bdaddr_t *bd_addr, + int local_role, int remote_role) { + jbyteArray addr; + debug("state:%d, local_role:%d, remote_role:%d", state, local_role, remote_role); + CHECK_CALLBACK_ENV + addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t)); + if (!addr) { + error("Fail to new jbyteArray bd addr for PAN channel state"); + checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__); + return; + } + sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr); + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectStateChanged, addr, (jint) state, + (jint)error, (jint)local_role, (jint)remote_role); + checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__); + sCallbackEnv->DeleteLocalRef(addr); +} + +static btpan_callbacks_t sBluetoothPanCallbacks = { + sizeof(sBluetoothPanCallbacks), + control_state_callback, + connection_state_callback +}; + +// Define native functions + +static void classInitNative(JNIEnv* env, jclass clazz) { + int err; + bt_status_t status; + + method_onConnectStateChanged = env->GetMethodID(clazz, "onConnectStateChanged", + "([BIIII)V"); + method_onControlStateChanged = env->GetMethodID(clazz, "onControlStateChanged", + "(IIILjava/lang/String;)V"); + + info("succeeds"); +} +static void initializeNative(JNIEnv *env, jobject object) { + static const bt_interface_t* btIf; + debug("pan"); + if(btIf) + return; + + if ( (btIf = getBluetoothInterface()) == NULL) { + error("Bluetooth module is not loaded"); + return; + } + + if (sPanIf !=NULL) { + LOGW("Cleaning up Bluetooth PAN Interface before initializing..."); + sPanIf->cleanup(); + sPanIf = NULL; + } + + if (mCallbacksObj != NULL) { + LOGW("Cleaning up Bluetooth PAN callback object"); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = NULL; + } + + if ( (sPanIf = (btpan_interface_t *) + btIf->get_profile_interface(BT_PROFILE_PAN_ID)) == NULL) { + error("Failed to get Bluetooth PAN Interface"); + return; + } + + bt_status_t status; + if ( (status = sPanIf->init(&sBluetoothPanCallbacks)) != BT_STATUS_SUCCESS) { + error("Failed to initialize Bluetooth PAN, status: %d", status); + sPanIf = NULL; + return; + } + + mCallbacksObj = env->NewGlobalRef(object); +} + +static void cleanupNative(JNIEnv *env, jobject object) { + const bt_interface_t* btInf; + bt_status_t status; + + if ( (btInf = getBluetoothInterface()) == NULL) { + LOGE("Bluetooth module is not loaded"); + return; + } + + if (sPanIf !=NULL) { + LOGW("Cleaning up Bluetooth PAN Interface..."); + sPanIf->cleanup(); + sPanIf = NULL; + } + + if (mCallbacksObj != NULL) { + LOGW("Cleaning up Bluetooth PAN callback object"); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = NULL; + } +} + +static jboolean enablePanNative(JNIEnv *env, jobject object, jint local_role) { + bt_status_t status = BT_STATUS_FAIL; + debug("in"); + jbyte *addr; + if (sPanIf) + status = sPanIf->enable(local_role); + debug("out"); + return status == BT_STATUS_SUCCESS ? JNI_TRUE : JNI_FALSE; +} +static jint getPanLocalRoleNative(JNIEnv *env, jobject object) { + debug("in"); + int local_role = 0; + jbyte *addr; + if (sPanIf) + local_role = sPanIf->get_local_role(); + debug("out"); + return (jint)local_role; +} + + + +static jboolean connectPanNative(JNIEnv *env, jobject object, jbyteArray address, + jint src_role, jint dest_role) { + debug("in"); + bt_status_t status; + jbyte *addr; + jboolean ret = JNI_TRUE; + if (!sPanIf) return JNI_FALSE; + + addr = env->GetByteArrayElements(address, NULL); + if (!addr) { + error("Bluetooth device address null"); + return JNI_FALSE; + } + + if ((status = sPanIf->connect((bt_bdaddr_t *) addr, src_role, dest_role)) != + BT_STATUS_SUCCESS) { + error("Failed PAN channel connection, status: %d", status); + ret = JNI_FALSE; + } + env->ReleaseByteArrayElements(address, addr, 0); + + return ret; +} + +static jboolean disconnectPanNative(JNIEnv *env, jobject object, jbyteArray address) { + bt_status_t status; + jbyte *addr; + jboolean ret = JNI_TRUE; + if (!sPanIf) return JNI_FALSE; + + addr = env->GetByteArrayElements(address, NULL); + if (!addr) { + error("Bluetooth device address null"); + return JNI_FALSE; + } + + if ( (status = sPanIf->disconnect((bt_bdaddr_t *) addr)) != + BT_STATUS_SUCCESS) { + error("Failed disconnect pan channel, status: %d", status); + ret = JNI_FALSE; + } + env->ReleaseByteArrayElements(address, addr, 0); + + return ret; +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void *) classInitNative}, + {"initializeNative", "()V", (void *) initializeNative}, + {"cleanupNative", "()V", (void *) cleanupNative}, + {"connectPanNative", "([BII)Z", (void *) connectPanNative}, + {"enablePanNative", "(I)Z", (void *) enablePanNative}, + {"getPanLocalRoleNative", "()I", (void *) getPanLocalRoleNative}, + {"disconnectPanNative", "([B)Z", (void *) disconnectPanNative}, + // TBD cleanup +}; + +int register_com_android_bluetooth_pan(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, "com/android/bluetooth/pan/PanService", + sMethods, NELEM(sMethods)); +} + +} diff --git a/src/com/android/bluetooth/Utils.java b/src/com/android/bluetooth/Utils.java index 22ab469b0..477f8c5e4 100644 --- a/src/com/android/bluetooth/Utils.java +++ b/src/com/android/bluetooth/Utils.java @@ -4,6 +4,7 @@ package com.android.bluetooth; +import android.bluetooth.BluetoothAdapter; import android.os.ParcelUuid; import java.nio.ByteBuffer; @@ -19,6 +20,10 @@ final public class Utils { static final int BD_UUID_LEN = 16; // bytes public static String getAddressStringFromByte(byte[] address) { + if (address == null || address.length !=6) { + return null; + } + return String.format("%02X:%02X:%02X:%02X:%02X:%02X", address[0], address[1], address[2], address[3], address[4], address[5]); @@ -107,4 +112,14 @@ final public class Utils { } return puuids; } + + public static String debugGetAdapterStateString(int state) { + switch (state) { + case BluetoothAdapter.STATE_OFF : return "STATE_OFF"; + case BluetoothAdapter.STATE_ON : return "STATE_ON"; + case BluetoothAdapter.STATE_TURNING_ON : return "STATE_TURNING_ON"; + case BluetoothAdapter.STATE_TURNING_OFF : return "STATE_TURNING_OFF"; + default : return "UNKNOWN"; + } + } } diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java index 728bcc29c..c33d5ed88 100755 --- a/src/com/android/bluetooth/a2dp/A2dpService.java +++ b/src/com/android/bluetooth/a2dp/A2dpService.java @@ -17,6 +17,10 @@ import android.os.Message; import android.provider.Settings; import android.util.Log; import java.util.List; +import java.util.Iterator; +import java.util.Map; +import android.content.pm.PackageManager; +import com.android.bluetooth.btservice.AdapterService; /** * Provides Bluetooth A2DP profile, as a service in the Bluetooth application. @@ -38,12 +42,6 @@ public class A2dpService extends Service { log("onCreate"); super.onCreate(); mAdapter = BluetoothAdapter.getDefaultAdapter(); - if (mAdapter == null) { - // no bluetooth on this device - return; - } - mStateMachine = new A2dpStateMachine(this); - mStateMachine.start(); } @Override @@ -54,16 +52,63 @@ public class A2dpService extends Service { public void onStart(Intent intent, int startId) { log("onStart"); + if (mAdapter == null) { - Log.w(TAG, "Stopping Bluetooth A2dpService: device does not have BT"); - stopSelf(); + Log.w(TAG, "Stopping profile service: device does not have BT"); + stop(); + } + + if (checkCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM)!=PackageManager.PERMISSION_GRANTED) { + Log.e(TAG, "Permission denied!"); + return; + } + + String action = intent.getStringExtra(AdapterService.EXTRA_ACTION); + if (!AdapterService.ACTION_SERVICE_STATE_CHANGED.equals(action)) { + Log.e(TAG, "Invalid action " + action); + return; + } + + int state= intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); + if(state==BluetoothAdapter.STATE_OFF) { + stop(); + } else if (state== BluetoothAdapter.STATE_ON){ + start(); } } @Override public void onDestroy() { super.onDestroy(); - if (DBG) log("Stopping Bluetooth A2dpService"); + if (DBG) log("Destroying service."); + } + + private void start() { + if (DBG) log("startService()"); + mStateMachine = new A2dpStateMachine(this); + mStateMachine.start(); + + //Notify adapter service + AdapterService sAdapter = AdapterService.getAdapterService(); + if (sAdapter!= null) { + sAdapter.onProfileServiceStateChanged(getClass().getName(), BluetoothAdapter.STATE_ON); + } + } + + private void stop() { + if (DBG) log("stopService()"); + if (mStateMachine!= null) { + mStateMachine.quit(); + mStateMachine.cleanup(); + mStateMachine=null; + } + + //Notify adapter service + AdapterService sAdapter = AdapterService.getAdapterService(); + if (sAdapter!= null) { + sAdapter.onProfileServiceStateChanged(getClass().getName(), BluetoothAdapter.STATE_OFF); + } + stopSelf(); } /** diff --git a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java index 38e71709b..c4dd1e045 100755 --- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java +++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java @@ -93,7 +93,7 @@ final class A2dpStateMachine extends StateMachine { mAdapter = BluetoothAdapter.getDefaultAdapter(); mAdapterService = IBluetooth.Stub.asInterface(ServiceManager.getService("bluetooth")); - initializeNativeDataNative(); + initNative(); mDisconnected = new Disconnected(); mPending = new Pending(); @@ -106,6 +106,10 @@ final class A2dpStateMachine extends StateMachine { setInitialState(mDisconnected); } + public void cleanup() { + cleanupNative(); + } + private class Disconnected extends State { @Override public void enter() { @@ -611,7 +615,8 @@ final class A2dpStateMachine extends StateMachine { final static int CONNECTION_STATE_DISCONNECTING = 3; private native static void classInitNative(); - private native void initializeNativeDataNative(); + private native void initNative(); + private native void cleanupNative(); private native boolean connectA2dpNative(byte[] address); private native boolean disconnectA2dpNative(byte[] address); } diff --git a/src/com/android/bluetooth/btservice/AdapterApp.java b/src/com/android/bluetooth/btservice/AdapterApp.java new file mode 100644 index 000000000..9116072dd --- /dev/null +++ b/src/com/android/bluetooth/btservice/AdapterApp.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2012 Google Inc. + */ + +/** + * @hide + */ + +package com.android.bluetooth.btservice; + +import android.app.Application; +import android.bluetooth.BluetoothAdapter; +import android.content.IntentFilter; +import android.util.Log; + +public class AdapterApp extends Application { + private static final String TAG = "BluetoothAdapterApp"; + 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; + + + @Override + public void onCreate() { + super.onCreate(); + if (DBG) Log.d(TAG, "onCreate"); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + if (DBG) Log.d(TAG, "finalize"); + } +} diff --git a/src/com/android/bluetooth/btservice/AdapterProperties.java b/src/com/android/bluetooth/btservice/AdapterProperties.java index 47f8c63be..4d40a72c1 100755 --- a/src/com/android/bluetooth/btservice/AdapterProperties.java +++ b/src/com/android/bluetooth/btservice/AdapterProperties.java @@ -59,10 +59,20 @@ class AdapterProperties { } static synchronized AdapterProperties getInstance(AdapterService service, Context context) { - if (sInstance == null) sInstance = new AdapterProperties(service, context); + if (sInstance == null) { + sInstance = new AdapterProperties(service, context); + } else { + sInstance.mService = service; + sInstance.mContext = context; + //Cleanup needed? + } return sInstance; } + public void init() { + mProfileConnectionState.clear(); + } + public Object Clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java index 741e44e6a..c5338c5b3 100755 --- a/src/com/android/bluetooth/btservice/AdapterService.java +++ b/src/com/android/bluetooth/btservice/AdapterService.java @@ -20,12 +20,12 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.ParcelUuid; import android.os.RemoteException; -import android.os.ServiceManager; import android.provider.Settings; import android.util.Log; import android.util.Pair; @@ -34,6 +34,7 @@ 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.pan.PanService; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties; @@ -41,11 +42,33 @@ import java.io.FileDescriptor; import java.io.IOException; import java.util.HashMap; import java.util.Set; +import java.util.Map; +import java.util.Iterator; +import java.util.Map.Entry; +import android.content.pm.PackageManager; -public class AdapterService extends Application { + +public class AdapterService extends Service { private static final String TAG = "BluetoothAdapterService"; private static final boolean DBG = true; + /** + * List of profile services to support.Comment out to disable a profile + * Profiles started in order of appearance + */ + @SuppressWarnings("rawtypes") + private static final Class[] SUPPORTED_PROFILE_SERVICES = { + HeadsetService.class, + A2dpService.class, + HidService.class, + HealthService.class, + PanService.class + }; + + public static final String ACTION_LOAD_ADAPTER_PROPERTIES="com.android.bluetooth.btservice.action.LOAD_ADAPTER_PROPERTIES"; + public static final String ACTION_SERVICE_STATE_CHANGED="com.android.bluetooth.btservice.action.STATE_CHANGED"; + public static final String EXTRA_ACTION="action"; + static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; @@ -57,61 +80,254 @@ public class AdapterService extends Application { private boolean mIsAirplaneToggleable; private static AdapterService sAdapterService; - private BluetoothAdapter mAdapter; private AdapterState mAdapterStateMachine; private BondStateMachine mBondStateMachine; private JniCallbacks mJniCallbacks; + private RemoteDevices mRemoteDevices; + private boolean mStopPending; + private boolean mStartPending; + private boolean mProfilesStarted; + private boolean mNativeAvailable; + private HashMap mProfileServicesState = new HashMap(); + + //In case Bluetooth app crashes, we need to reinit JNI + private static boolean mIsJniInited; + private static synchronized void initJni() { + if (!mIsJniInited) { + if (DBG) Log.d(TAG,"Initializing JNI Library"); + System.loadLibrary("bluetooth_jni"); + classInitNative(); + mIsJniInited=true; + } + } + public static AdapterService getAdapterService(){ + return sAdapterService; + } - private RemoteDevices mRemoteDevices; - static { - System.loadLibrary("bluetooth_jni"); - classInitNative(); + public void onProfileServiceStateChanged(String serviceName, int state) { + if (DBG) Log.d(TAG,"onProfileServiceStateChange: serviceName=" + serviceName + ", state = " + state); + boolean doUpdate=false; + synchronized (mProfileServicesState) { + Integer prevState = mProfileServicesState.get(serviceName); + if (prevState != null && prevState != state) { + mProfileServicesState.put(serviceName,state); + doUpdate=true; + } + } + if (!doUpdate) { + return; + } + if (mStopPending) { + //Process stop or disable pending + //Check if all services are stopped if so, do cleanup + if (DBG) Log.d(TAG,"Checking if all profiles are stopped..."); + synchronized (mProfileServicesState) { + Iterator> i = mProfileServicesState.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry entry = i.next(); + if (BluetoothAdapter.STATE_OFF != entry.getValue()) { + //Log.d(TAG, "Profile still running: " entry.getKey()); + return; + } + } + } + if (DBG) Log.d(TAG, "All profile services stopped..."); + Message m = mHandler.obtainMessage(MESSAGE_ONSTOPPED_STOPPENDING); + mHandler.sendMessage(m); + } else if (mStartPending) { + //Process start pending + //Check if all services are started if so, update state + if (DBG) Log.d(TAG,"Checking if all profiles are running..."); + synchronized (mProfileServicesState) { + Iterator> i = mProfileServicesState.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry entry = i.next(); + if (BluetoothAdapter.STATE_ON != entry.getValue()) { + //Log.d(TAG, "Profile still not running:" + entry.getKey()); + return; + } + } + } + if (DBG) Log.d(TAG, "All profile services started."); + Message m = mHandler.obtainMessage(MESSAGE_ONSTARTED); + mHandler.sendMessage(m); + } } @Override public void onCreate() { super.onCreate(); - ServiceManager.addService(Context.BLUETOOTH_SERVICE, mBinder); - - mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (DBG) Log.d(TAG, "onCreate"); mContext = this; sAdapterService = this; - - IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); - registerForAirplaneMode(filter); - registerReceiver(mReceiver, filter); - + initJni(); //We always check and init JNI in case we crashed and restarted mRemoteDevices = RemoteDevices.getInstance(this, mContext); + mRemoteDevices.init(); mAdapterProperties = AdapterProperties.getInstance(this, mContext); - mAdapterStateMachine = new AdapterState(this, mContext, mAdapterProperties); + mAdapterProperties.init(); + mAdapterStateMachine = new AdapterState(this, mContext, mAdapterProperties); mBondStateMachine = new BondStateMachine(this, mContext, mAdapterProperties); mJniCallbacks = JniCallbacks.getInstance(mRemoteDevices, mAdapterProperties, mAdapterStateMachine, mBondStateMachine); + initNative(); + mNativeAvailable=true; + //Load the name and address + getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDADDR); + getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDNAME); + } - initNative(); + @Override + public IBinder onBind(Intent intent) { + if (DBG) Log.d(TAG, "onBind"); + return mBinder; + } + public boolean onUnbind(Intent intent) { + if (DBG) Log.d(TAG,"onUnbind"); + return super.onUnbind(intent); + } + + public void onDestroy() { + if (DBG) Log.d(TAG, "onDestroy()"); + super.onDestroy(); + if (mBondStateMachine != null) { + mBondStateMachine.cleanup(); + mBondStateMachine = null; + } + if (mAdapterStateMachine != null) { + mAdapterStateMachine.cleanup(); + mAdapterStateMachine = null; + } + if (mNativeAvailable) { + Log.d(TAG, "Cleaning up adapter native...."); + cleanupNative(); + Log.d(TAG, "Done cleaning up adapter native...."); + mNativeAvailable=false; + } + } + + public int onStartCommand(Intent intent ,int flags, int startId) { + if (DBG) Log.d(TAG, "onStartCommand"); + if (checkCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM)!=PackageManager.PERMISSION_GRANTED) { + Log.e(TAG, "Permission denied!"); + return START_STICKY; + } + + String action = intent.getStringExtra(EXTRA_ACTION); + if (DBG) Log.d(TAG,"onStartCommand(): action = " + action); + if (ACTION_SERVICE_STATE_CHANGED.equals(action)) { + int state= intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,BluetoothAdapter.ERROR); + if (DBG) Log.d(TAG,"onStartCommand(): state = " + Utils.debugGetAdapterStateString(state)); + if (state == BluetoothAdapter.STATE_OFF) { + stop(); + } else if (state == BluetoothAdapter.STATE_ON) { + start(); + } + } + return START_STICKY; + } + + private void start() { + if (DBG) Log.d(TAG,"start() called"); + if (mProfilesStarted || mStartPending) return; + if (DBG) Log.d(TAG,"starting bluetooth state machine and profiles.."); + mStartPending=true; mAdapterStateMachine.start(); mBondStateMachine.start(); - //TODO(BT): Remove this when BT is no longer a persitent process. - int bluetoothOn = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.BLUETOOTH_ON, 0); - if (!isAirplaneModeOn() && bluetoothOn != 0) mAdapter.enable(); - startService(new Intent(this, HeadsetService.class)); - startService(new Intent(this, A2dpService.class)); - startService(new Intent(this, HidService.class)); - startService(new Intent(this, HealthService.class)); + //Start profile services + for (int i=0; i =0;i--) { + stopProfileService(SUPPORTED_PROFILE_SERVICES[i]); + } + } else { + mStopPending=false; + finish(); + } + } + + private static final int MESSAGE_ONSTARTED=1; + private static final int MESSAGE_ONSTOPPED_STOPPENDING =2; + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (DBG) Log.d (TAG, "Message: " + msg.what); + + switch (msg.what) { + case MESSAGE_ONSTARTED: { + Log.d(TAG, "onStarted()"); + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); + registerForAirplaneMode(filter); + registerReceiver(mReceiver, filter); + mProfilesStarted = true; + mStartPending = false; + } + break; + + case MESSAGE_ONSTOPPED_STOPPENDING: { + Log.d(TAG, "onStopped_StopPending() called"); + mStopPending=false; + mBondStateMachine.quit(); + mAdapterStateMachine.quit(); + finish(); + } + break; + } } + }; + + /** + * Last step in the disable->stop->cleanup sequence + */ + private void finish() { + stopSelf(); + } + + @SuppressWarnings("rawtypes") + private void startProfileService(Class service) { + String serviceName = service.getName(); + Integer serviceState = mProfileServicesState.get(serviceName); + if(serviceState != null && serviceState != BluetoothAdapter.STATE_OFF) { + Log.w(TAG, "Unable to start service "+serviceName+". Invalid state: " + serviceState); + return; + } + + mProfileServicesState.put(serviceName,BluetoothAdapter.STATE_TURNING_ON); + Intent i = new Intent(this,service); + i.putExtra(EXTRA_ACTION,ACTION_SERVICE_STATE_CHANGED); + i.putExtra(BluetoothAdapter.EXTRA_STATE,BluetoothAdapter.STATE_ON); + if (DBG) Log.d(TAG, "Starting profile service "+serviceName); + startService(i); + } + + @SuppressWarnings("rawtypes") + private void stopProfileService(Class service) { + String serviceName = service.getName(); + Integer serviceState = mProfileServicesState.get(service.getName()); + if(serviceState == null || serviceState != BluetoothAdapter.STATE_ON) { + Log.w(TAG, "Unable to stop service " + serviceName + ". Invalid state: " + serviceState); + return; + } + mProfileServicesState.put(serviceName, BluetoothAdapter.STATE_TURNING_OFF); + Intent i = new Intent(this, service); + i.putExtra(EXTRA_ACTION,ACTION_SERVICE_STATE_CHANGED); + i.putExtra(BluetoothAdapter.EXTRA_STATE,BluetoothAdapter.STATE_OFF); + if (DBG) Log.d(TAG, "Stopping profile service "+serviceName); + startService(i); } private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -133,8 +349,6 @@ public class AdapterService extends Application { } }; - - private void registerForAirplaneMode(IntentFilter filter) { final ContentResolver resolver = mContext.getContentResolver(); final String airplaneModeRadios = Settings.System.getString(resolver, @@ -157,6 +371,7 @@ public class AdapterService extends Application { return Settings.System.getInt(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 1; } + private boolean mPersistDisable; /** * Handlers for incoming service calls @@ -175,10 +390,10 @@ public class AdapterService extends Application { public boolean enable() { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); - // Persist the setting + Log.d(TAG,"enable() called..."); Message m = mAdapterStateMachine.obtainMessage(AdapterState.USER_TURN_ON); - m.arg1 = 1; + m.arg1 = 1; //persist state mAdapterStateMachine.sendMessage(m); return true; } @@ -186,6 +401,7 @@ public class AdapterService extends Application { public boolean disable(boolean persist) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); + unregisterReceiver(mReceiver); int val = (persist ? 1 : 0); Message m = mAdapterStateMachine.obtainMessage(AdapterState.USER_TURN_OFF); diff --git a/src/com/android/bluetooth/btservice/AdapterState.java b/src/com/android/bluetooth/btservice/AdapterState.java index 1086c2205..fd475bf6f 100644 --- a/src/com/android/bluetooth/btservice/AdapterState.java +++ b/src/com/android/bluetooth/btservice/AdapterState.java @@ -39,10 +39,10 @@ final class AdapterState extends StateMachine { private static final int DISCONNECT_TIMEOUT = 3000; private static final int ENABLE_TIMEOUT_DELAY = 6000; // 6 secs - private final AdapterService mAdapterService; - private final Context mContext; - private final AdapterProperties mAdapterProperties; - + private AdapterService mAdapterService; + private Context mContext; + private AdapterProperties mAdapterProperties; + private boolean mPendingPersistEnable; private PendingCommandState mPendingCommandState = new PendingCommandState(); private OnState mOnState = new OnState(); private OffState mOffState = new OffState(); @@ -53,10 +53,13 @@ final class AdapterState extends StateMachine { addState(mOnState); addState(mOffState); addState(mPendingCommandState); - setInitialState(mOffState); mAdapterService = service; mContext = context; mAdapterProperties = adapterProperties; + setInitialState(mOffState); + } + + public void cleanup() { } private class OffState extends State { @@ -70,7 +73,9 @@ final class AdapterState extends StateMachine { switch(msg.what) { case USER_TURN_ON: int persist = msg.arg1; - if (persist == 1) mAdapterService.persistBluetoothSetting(true); + //if (persist == 1) mAdapterService.persistBluetoothSetting(true); + //Persist enable state only once enable completes + mPendingPersistEnable = (persist ==1); sendIntent(BluetoothAdapter.STATE_TURNING_ON); boolean ret = mAdapterService.enableNative(); if (!ret) { @@ -123,7 +128,8 @@ final class AdapterState extends StateMachine { case USER_TURN_OFF: int persist = msg.arg1; if (persist == 1) { - mAdapterService.persistBluetoothSetting(false); + //Persist disable immediately even before disable completes + mAdapterService.persistBluetoothSetting(false); } //Fall Through case AIRPLANE_MODE_ON: @@ -176,6 +182,11 @@ final class AdapterState extends StateMachine { break; case ENABLED_READY: removeMessages(ENABLE_TIMEOUT); + //Persist enable state only once enable completes + if (mPendingPersistEnable) { + mAdapterService.persistBluetoothSetting(true); + mPendingPersistEnable=false; + } mAdapterProperties.onBluetoothReady(); sendIntent(BluetoothAdapter.STATE_ON); transitionTo(mOnState); diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java index 56b2a0550..a9f0f1159 100755 --- a/src/com/android/bluetooth/btservice/BondStateMachine.java +++ b/src/com/android/bluetooth/btservice/BondStateMachine.java @@ -39,10 +39,10 @@ final class BondStateMachine extends StateMachine { static final int BOND_STATE_BONDING = 1; static final int BOND_STATE_BONDED = 2; - private final AdapterService mAdapterService; - private final Context mContext; - private final AdapterProperties mAdapterProperties; - private final RemoteDevices mRemoteDevices; + private AdapterService mAdapterService; + private Context mContext; + private AdapterProperties mAdapterProperties; + private RemoteDevices mRemoteDevices; private PendingCommandState mPendingCommandState = new PendingCommandState(); private StableState mStableState = new StableState(); @@ -52,11 +52,14 @@ final class BondStateMachine extends StateMachine { super("BondStateMachine:"); addState(mStableState); addState(mPendingCommandState); - setInitialState(mStableState); + mRemoteDevices = RemoteDevices.getInstance(service, context); mAdapterService = service; mAdapterProperties = prop; mContext = context; - mRemoteDevices = RemoteDevices.getInstance(service, context); + setInitialState(mStableState); + } + + public void cleanup() { } private class StableState extends State { @@ -67,8 +70,14 @@ final class BondStateMachine extends StateMachine { @Override public boolean processMessage(Message msg) { + if (msg.what == SM_QUIT_CMD) { + return false; + } + BluetoothDevice dev = (BluetoothDevice)msg.obj; + switch(msg.what) { + case CREATE_BOND: createBond(dev, true); break; diff --git a/src/com/android/bluetooth/btservice/JniCallbacks.java b/src/com/android/bluetooth/btservice/JniCallbacks.java index d9feb6fd4..109db55fd 100644 --- a/src/com/android/bluetooth/btservice/JniCallbacks.java +++ b/src/com/android/bluetooth/btservice/JniCallbacks.java @@ -12,7 +12,7 @@ final class JniCallbacks { private AdapterState mAdapterStateMachine; private BondStateMachine mBondStateMachine; - JniCallbacks(RemoteDevices remoteDevices, AdapterProperties adapterProperties, + private JniCallbacks(RemoteDevices remoteDevices, AdapterProperties adapterProperties, AdapterState adapterStateMachine, BondStateMachine bondStateMachine) { mRemoteDevices = remoteDevices; mAdapterProperties = adapterProperties; @@ -23,12 +23,18 @@ final class JniCallbacks { static synchronized JniCallbacks getInstance(RemoteDevices remoteDevices, AdapterProperties adapterProperties, AdapterState adapterStateMachine, BondStateMachine bondStateMachine) { - if (sInstance == null) sInstance = + if (sInstance == null) { + sInstance = new JniCallbacks(remoteDevices, adapterProperties, adapterStateMachine, bondStateMachine); + } else { + sInstance.mRemoteDevices = remoteDevices; + sInstance.mAdapterProperties = adapterProperties; + sInstance.mAdapterStateMachine = adapterStateMachine; + sInstance.mBondStateMachine = bondStateMachine; + } return sInstance; } - public Object Clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java index 8233ad5de..a5d7bcf89 100755 --- a/src/com/android/bluetooth/btservice/RemoteDevices.java +++ b/src/com/android/bluetooth/btservice/RemoteDevices.java @@ -41,17 +41,27 @@ final class RemoteDevices { private RemoteDevices(AdapterService service, Context context) { mAdapter = BluetoothAdapter.getDefaultAdapter(); + mContext = context; mAdapterService = service; mSdpTracker = new ArrayList(); mDevices = new HashMap(); - mContext = context; } static synchronized RemoteDevices getInstance(AdapterService service, Context context) { - if (sInstance == null) sInstance = new RemoteDevices(service, context); + if (sInstance == null) { + sInstance = new RemoteDevices(service, context); + } else { + mContext = context; + mAdapterService = service; + } return sInstance; } + public void init() { + mSdpTracker.clear(); + mDevices.clear(); + } + public Object Clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } diff --git a/src/com/android/bluetooth/hdp/HealthService.java b/src/com/android/bluetooth/hdp/HealthService.java index 2ae3f65ed..ee6f39051 100644 --- a/src/com/android/bluetooth/hdp/HealthService.java +++ b/src/com/android/bluetooth/hdp/HealthService.java @@ -32,6 +32,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.android.bluetooth.Utils; +import android.content.pm.PackageManager; +import com.android.bluetooth.btservice.AdapterService; /** * Provides Bluetooth Health Device profile, as a service in @@ -67,18 +69,6 @@ public class HealthService extends Service { @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 @@ -92,15 +82,74 @@ public class HealthService extends Service { log("onStart"); if (mAdapter == null) { Log.w(TAG, "Stopping Bluetooth HealthService: device does not have BT"); - stopSelf(); + stop(); + } + + if (checkCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM)!=PackageManager.PERMISSION_GRANTED) { + Log.e(TAG, "Permission denied!"); + return; + } + + String action = intent.getStringExtra(AdapterService.EXTRA_ACTION); + if (!AdapterService.ACTION_SERVICE_STATE_CHANGED.equals(action)) { + Log.e(TAG, "Invalid action " + action); + return; + } + + int state= intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); + if(state==BluetoothAdapter.STATE_OFF) { + stop(); + } else if (state== BluetoothAdapter.STATE_ON){ + start(); } } @Override public void onDestroy() { super.onDestroy(); - if (DBG) log("Stopping Bluetooth HealthService"); - // TBD + if (DBG) log("Destroying service."); + } + + private void start() { + if (DBG) log("startService"); + 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")); + initializeNative(); + + //Notify adapter service + AdapterService sAdapter = AdapterService.getAdapterService(); + if (sAdapter!= null) { + sAdapter.onProfileServiceStateChanged(getClass().getName(), BluetoothAdapter.STATE_ON); + } + } + + private void stop() { + if (DBG) log("stop()"); + + //Cleanup looper + Looper looper = mHandler.getLooper(); + if (looper != null) { + looper.quit(); + } + + //Cleanup native + cleanupNative(); + + //Notify adapter service + AdapterService sAdapter = AdapterService.getAdapterService(); + if (sAdapter!= null) { + sAdapter.onProfileServiceStateChanged(getClass().getName(), BluetoothAdapter.STATE_OFF); + } + if (DBG) log("stop() done."); + stopSelf(); } private final class HealthServiceMessageHandler extends Handler { @@ -716,7 +765,8 @@ public class HealthService extends Service { private static final int CHANNEL_TYPE_ANY =2; private native static void classInitNative(); - private native void initializeNativeDataNative(); + private native void initializeNative(); + private native void cleanupNative(); 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); diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java index 602af8962..c07016ca3 100755 --- a/src/com/android/bluetooth/hfp/HeadsetService.java +++ b/src/com/android/bluetooth/hfp/HeadsetService.java @@ -21,6 +21,10 @@ import android.os.Message; import android.provider.Settings; import android.util.Log; import java.util.List; +import java.util.Iterator; +import java.util.Map; +import android.content.pm.PackageManager; +import com.android.bluetooth.btservice.AdapterService; /** * Provides Bluetooth Headset and Handsfree profile, as a service in @@ -43,15 +47,6 @@ public class HeadsetService extends Service { log("onCreate"); super.onCreate(); mAdapter = BluetoothAdapter.getDefaultAdapter(); - if (mAdapter == null) { - // no bluetooth on this device - return; - } - mStateMachine = new HeadsetStateMachine(this); - mStateMachine.start(); - IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); - registerReceiver(mHeadsetReceiver, filter); } @Override @@ -62,17 +57,67 @@ public class HeadsetService extends Service { public void onStart(Intent intent, int startId) { log("onStart"); + if (mAdapter == null) { - Log.w(TAG, "Stopping Bluetooth HeadsetService: device does not have BT"); - stopSelf(); + Log.w(TAG, "Stopping profile service: device does not have BT"); + stop(); + } + + if (checkCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM)!=PackageManager.PERMISSION_GRANTED) { + Log.e(TAG, "Permission denied!"); + return; + } + + String action = intent.getStringExtra(AdapterService.EXTRA_ACTION); + if (!AdapterService.ACTION_SERVICE_STATE_CHANGED.equals(action)) { + Log.e(TAG, "Invalid action " + action); + return; + } + + int state= intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); + if(state==BluetoothAdapter.STATE_OFF) { + stop(); + } else if (state== BluetoothAdapter.STATE_ON){ + start(); } } @Override public void onDestroy() { super.onDestroy(); - if (DBG) log("Stopping Bluetooth HeadsetService"); + if (DBG) log("Destroying service."); + } + + private void start() { + mStateMachine = new HeadsetStateMachine(this); + mStateMachine.start(); + IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); + registerReceiver(mHeadsetReceiver, filter); + + //Notify adapter service + AdapterService sAdapter = AdapterService.getAdapterService(); + if (sAdapter!= null) { + sAdapter.onProfileServiceStateChanged(getClass().getName(), BluetoothAdapter.STATE_ON); + } + } + + private void stop() { + if (DBG) log("stopService()"); unregisterReceiver(mHeadsetReceiver); + + if (mStateMachine!= null) { + mStateMachine.quit(); + mStateMachine.cleanup(); + mStateMachine=null; + } + + //Notify adapter service + AdapterService sAdapter = AdapterService.getAdapterService(); + if (sAdapter!= null) { + sAdapter.onProfileServiceStateChanged(getClass().getName(), BluetoothAdapter.STATE_OFF); + } + stopSelf(); } private final BroadcastReceiver mHeadsetReceiver = new BroadcastReceiver() { diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java index 1b5dabeb9..f496ae2f0 100755 --- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java +++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java @@ -165,7 +165,7 @@ final class HeadsetStateMachine extends StateMachine { Log.e(TAG, "Could not bind to Bluetooth Headset Phone Service"); } - initializeNativeDataNative(); + initializeNative(); mDisconnected = new Disconnected(); mPending = new Pending(); @@ -185,6 +185,21 @@ final class HeadsetStateMachine extends StateMachine { setInitialState(mDisconnected); } + public void cleanup() { + cleanupNative(); + if (mPhoneProxy != null) { + if (DBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + try { + mPhoneProxy = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + private class Disconnected extends State { @Override public void enter() { @@ -1535,7 +1550,8 @@ final class HeadsetStateMachine extends StateMachine { } private native static void classInitNative(); - private native void initializeNativeDataNative(); + private native void initializeNative(); + private native void cleanupNative(); private native boolean connectHfpNative(byte[] address); private native boolean disconnectHfpNative(byte[] address); private native boolean connectAudioNative(byte[] address); diff --git a/src/com/android/bluetooth/hid/HidService.java b/src/com/android/bluetooth/hid/HidService.java index 2bf580f2a..624362764 100755 --- a/src/com/android/bluetooth/hid/HidService.java +++ b/src/com/android/bluetooth/hid/HidService.java @@ -26,6 +26,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import com.android.bluetooth.Utils; +import android.content.pm.PackageManager; +import com.android.bluetooth.btservice.AdapterService; /** * Provides Bluetooth Hid Device profile, as a service in @@ -64,9 +66,6 @@ public class HidService extends Service { @Override public void onCreate() { mAdapter = BluetoothAdapter.getDefaultAdapter(); - mAdapterService = IBluetooth.Stub.asInterface(ServiceManager.getService("bluetooth")); - mInputDevices = Collections.synchronizedMap(new HashMap()); - initializeNativeDataNative(); } @Override @@ -75,22 +74,65 @@ public class HidService extends Service { return mBinder; } - @Override public void onStart(Intent intent, int startId) { log("onStart"); + if (mAdapter == null) { - Log.w(TAG, "Stopping Bluetooth HidService: device does not have BT"); - stopSelf(); + Log.w(TAG, "Stopping profile service: device does not have BT"); + stop(); + } + + if (checkCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM)!=PackageManager.PERMISSION_GRANTED) { + Log.e(TAG, "Permission denied!"); + return; + } + + String action = intent.getStringExtra(AdapterService.EXTRA_ACTION); + if (!AdapterService.ACTION_SERVICE_STATE_CHANGED.equals(action)) { + Log.e(TAG, "Invalid action " + action); + return; + } + + int state= intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); + if(state==BluetoothAdapter.STATE_OFF) { + stop(); + } else if (state== BluetoothAdapter.STATE_ON){ + start(); } } @Override public void onDestroy() { super.onDestroy(); + if (DBG) log("Destroying service."); + } + + private void start() { + mAdapterService = IBluetooth.Stub.asInterface(ServiceManager.getService("bluetooth")); + mInputDevices = Collections.synchronizedMap(new HashMap()); + initializeNative(); + + //Notify adapter service + AdapterService sAdapter = AdapterService.getAdapterService(); + if (sAdapter!= null) { + sAdapter.onProfileServiceStateChanged(getClass().getName(), BluetoothAdapter.STATE_ON); + } + } + + private void stop() { if (DBG) log("Stopping Bluetooth HidService"); - // TBD native cleanup + cleanupNative(); + + //Notify adapter service + AdapterService sAdapter = AdapterService.getAdapterService(); + if (sAdapter!= null) { + sAdapter.onProfileServiceStateChanged(getClass().getName(), BluetoothAdapter.STATE_OFF); + } + stopSelf(); } + + private final Handler mHandler = new Handler() { @Override @@ -477,7 +519,8 @@ public class HidService extends Service { private final static int CONN_STATE_DISCONNECTING = 3; private native static void classInitNative(); - private native void initializeNativeDataNative(); + private native void initializeNative(); + private native void cleanupNative(); private native boolean connectHidNative(byte[] btAddress); private native boolean disconnectHidNative(byte[] btAddress); private native boolean getProtocolModeNative(byte[] btAddress); diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java new file mode 100644 index 000000000..3f79df452 --- /dev/null +++ b/src/com/android/bluetooth/pan/PanService.java @@ -0,0 +1,544 @@ +/* + * Copyright (C) 2012 Google Inc. + */ + +package com.android.bluetooth.pan; + +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothTetheringDataTracker; +import android.bluetooth.IBluetooth; +import android.bluetooth.IBluetoothPan; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.Resources.NotFoundException; +import android.net.ConnectivityManager; +import android.net.InterfaceConfiguration; +import android.net.LinkAddress; +import android.net.NetworkUtils; +import android.os.IBinder; +import android.os.INetworkManagementService; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.util.Log; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; + +/** + * Provides Bluetooth Pan Device profile, as a service in + * the Bluetooth application. + * @hide + */ +public class PanService extends Service { + private static final String TAG = "BluetoothPanService"; + 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 static final String BLUETOOTH_IFACE_ADDR_START= "192.168.44.1"; + private static final int BLUETOOTH_MAX_PAN_CONNECTIONS = 5; + private static final int BLUETOOTH_PREFIX_LENGTH = 24; + + private BluetoothAdapter mAdapter; + private IBluetooth mAdapterService; + private HashMap mPanDevices; + private ArrayList mBluetoothIfaceAddresses; + private int mMaxPanDevices; + private String mPanIfName; + + private static final int MESSAGE_CONNECT = 1; + private static final int MESSAGE_DISCONNECT = 2; + private static final int MESSAGE_SET_TETHERING = 3; + private static final int MESSAGE_CONNECT_STATE_CHANGED = 11; + + + static { + classInitNative(); + } + + @Override + public void onCreate() { + Log.d(TAG, "onCreate"); + mAdapter = BluetoothAdapter.getDefaultAdapter(); + } + + @Override + public void onStart(Intent intent, int startId) { + log("onStart"); + if (mAdapter == null) { + Log.w(TAG, "Stopping Bluetooth Pan Service: device does not have BT"); + stop(); + } + + if (checkCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM)!=PackageManager.PERMISSION_GRANTED) { + Log.e(TAG, "Permission denied!"); + return; + } + + String action = intent.getStringExtra(AdapterService.EXTRA_ACTION); + if (!AdapterService.ACTION_SERVICE_STATE_CHANGED.equals(action)) { + Log.e(TAG, "Invalid action " + action); + return; + } + + int state= intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); + if(state==BluetoothAdapter.STATE_OFF) { + stop(); + } else if (state== BluetoothAdapter.STATE_ON){ + start(); + } + } + + @Override + public IBinder onBind(Intent intent) { + log("onBind"); + return mBinder; + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (DBG) log("Stopping Bluetooth PanService"); + } + + private void start() { + if (DBG) log("start"); + + mPanDevices = new HashMap(); + mAdapterService = IBluetooth.Stub.asInterface(ServiceManager.getService("bluetooth")); + mBluetoothIfaceAddresses = new ArrayList(); + try { + mMaxPanDevices = getResources().getInteger( + com.android.internal.R.integer.config_max_pan_devices); + } catch (NotFoundException e) { + mMaxPanDevices = BLUETOOTH_MAX_PAN_CONNECTIONS; + } + + initializeNative(); + + //Notify adapter service + AdapterService sAdapter = AdapterService.getAdapterService(); + if (sAdapter!= null) { + sAdapter.onProfileServiceStateChanged(getClass().getName(), BluetoothAdapter.STATE_ON); + } + + } + + private void stop() { + if (DBG) log("stop"); + + //Cleanup native + cleanupNative(); + + //Notify adapter service + AdapterService sAdapter = AdapterService.getAdapterService(); + if (sAdapter!= null) { + sAdapter.onProfileServiceStateChanged(getClass().getName(), BluetoothAdapter.STATE_OFF); + } + if (DBG) log("stop() done."); + stopSelf(); + } + + private final Handler mHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_CONNECT: + { + BluetoothDevice device = (BluetoothDevice) msg.obj; + if (!connectPanNative(getByteAddress(device), BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE)) { + handlePanDeviceStateChange(device, null, BluetoothProfile.STATE_CONNECTING, + BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE); + handlePanDeviceStateChange(device, null, BluetoothProfile.STATE_DISCONNECTED, + BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE); + break; + } + } + break; + case MESSAGE_DISCONNECT: + { + BluetoothDevice device = (BluetoothDevice) msg.obj; + if (!disconnectPanNative(getByteAddress(device)) ) { + handlePanDeviceStateChange(device, mPanIfName, BluetoothProfile.STATE_DISCONNECTING, + BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE); + handlePanDeviceStateChange(device, mPanIfName, BluetoothProfile.STATE_DISCONNECTED, + BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE); + break; + } + } + break; + case MESSAGE_SET_TETHERING: + boolean tetherOn = (Boolean) msg.obj; + if(tetherOn) + enablePanNative(BluetoothPan.LOCAL_NAP_ROLE | BluetoothPan.LOCAL_PANU_ROLE); + else enablePanNative(BluetoothPan.LOCAL_PANU_ROLE); + break; + case MESSAGE_CONNECT_STATE_CHANGED: + { + ConnectState cs = (ConnectState)msg.obj; + BluetoothDevice device = getDevice(cs.addr); + // TBD get iface from the msg + if (DBG) log("MESSAGE_CONNECT_STATE_CHANGED: " + device + " state: " + cs.state); + handlePanDeviceStateChange(device, mPanIfName /* iface */, convertHalState(cs.state), + cs.local_role, cs.remote_role); + } + break; + } + } + }; + + /** + * Handlers for incoming service calls + */ + private final IBluetoothPan.Stub mBinder = new IBluetoothPan.Stub() { + public boolean connect(BluetoothDevice device) { + enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + if (getConnectionState(device) != BluetoothProfile.STATE_DISCONNECTED) { + Log.e(TAG, "Pan Device not disconnected: " + device); + return false; + } + Message msg = mHandler.obtainMessage(MESSAGE_CONNECT); + msg.obj = device; + mHandler.sendMessage(msg); + return true; + } + + public boolean disconnect(BluetoothDevice device) { + enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT); + msg.obj = device; + mHandler.sendMessage(msg); + return true; + } + + public int getConnectionState(BluetoothDevice device) { + BluetoothPanDevice panDevice = mPanDevices.get(device); + if (panDevice == null) { + return BluetoothPan.STATE_DISCONNECTED; + } + return panDevice.mState; + } + private boolean isPanNapOn() { + if(DBG) Log.d(TAG, "isTetheringOn call getPanLocalRoleNative"); + return (getPanLocalRoleNative() & BluetoothPan.LOCAL_NAP_ROLE) != 0; + } + private boolean isPanUOn() { + if(DBG) Log.d(TAG, "isTetheringOn call getPanLocalRoleNative"); + return (getPanLocalRoleNative() & BluetoothPan.LOCAL_PANU_ROLE) != 0; + } + public boolean isTetheringOn() { + // TODO(BT) have a variable marking the on/off state + return isPanNapOn(); + } + + public void setBluetoothTethering(boolean value) { + if(DBG) Log.d(TAG, "setBluetoothTethering: " + value); + enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + Message msg = mHandler.obtainMessage(MESSAGE_SET_TETHERING); + msg.obj = new Boolean(value); + mHandler.sendMessage(msg); + } + + public List getConnectedDevices() { + enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + List devices = getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED}); + return devices; + } + + public List getDevicesMatchingConnectionStates(int[] states) { + enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + List panDevices = new ArrayList(); + + for (BluetoothDevice device: mPanDevices.keySet()) { + int panDeviceState = getConnectionState(device); + for (int state : states) { + if (state == panDeviceState) { + panDevices.add(device); + break; + } + } + } + return panDevices; + } + }; + static protected class ConnectState { + public ConnectState(byte[] address, int state, int error, int local_role, int remote_role) { + this.addr = address; + this.state = state; + this.error = error; + this.local_role = local_role; + this.remote_role = remote_role; + } + byte[] addr; + int state; + int error; + int local_role; + int remote_role; + }; + private void onConnectStateChanged(byte[] address, int state, int error, int local_role, int remote_role) { + if (DBG) log("onConnectStateChanged: " + state + ", local role:" + local_role + ", remote_role: " + remote_role); + Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_STATE_CHANGED); + msg.obj = new ConnectState(address, state, error, local_role, remote_role); + mHandler.sendMessage(msg); + } + private void onControlStateChanged(int local_role, int state, int error, String ifname) { + if (DBG) + log("onControlStateChanged: " + state + ", error: " + error + ", ifname: " + ifname); + if(error == 0) + mPanIfName = ifname; + } + private BluetoothDevice getDevice(byte[] address) { + return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); + } + + private byte[] getByteAddress(BluetoothDevice device) { + return Utils.getBytesFromAddress(device.getAddress()); + } + + private int convertHalState(int halState) { + switch (halState) { + case CONN_STATE_CONNECTED: + return BluetoothProfile.STATE_CONNECTED; + case CONN_STATE_CONNECTING: + return BluetoothProfile.STATE_CONNECTING; + case CONN_STATE_DISCONNECTED: + return BluetoothProfile.STATE_DISCONNECTED; + case CONN_STATE_DISCONNECTING: + return BluetoothProfile.STATE_DISCONNECTING; + default: + Log.e(TAG, "bad pan connection state: " + halState); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + + void handlePanDeviceStateChange(BluetoothDevice device, + String iface, int state, int local_role, int remote_role) { + if(DBG) Log.d(TAG, "handlePanDeviceStateChange: device: " + device + ", iface: " + iface + + ", state: " + state + ", local_role:" + local_role + ", remote_role:" + remote_role); + int prevState; + String ifaceAddr = null; + BluetoothPanDevice panDevice = mPanDevices.get(device); + if (panDevice == null) { + prevState = BluetoothProfile.STATE_DISCONNECTED; + } else { + prevState = panDevice.mState; + ifaceAddr = panDevice.mIfaceAddr; + } + Log.d(TAG, "handlePanDeviceStateChange preState: " + prevState + " state: " + state); + if (prevState == state) return; + if (remote_role == BluetoothPan.LOCAL_PANU_ROLE) { + Log.d(TAG, "handlePanDeviceStateChange LOCAL_NAP_ROLE:REMOTE_PANU_ROLE"); + if (state == BluetoothProfile.STATE_CONNECTED) { + if(DBG) Log.d(TAG, "handlePanDeviceStateChange: nap STATE_CONNECTED, enableTethering iface: " + iface); + ifaceAddr = enableTethering(iface); + if (ifaceAddr == null) Log.e(TAG, "Error seting up tether interface"); + } else if (state == BluetoothProfile.STATE_DISCONNECTED) { + if (ifaceAddr != null) { + mBluetoothIfaceAddresses.remove(ifaceAddr); + ifaceAddr = null; + } + } + } else { + // PANU Role = reverse Tether + Log.d(TAG, "handlePanDeviceStateChange LOCAL_PANU_ROLE:REMOTE_NAP_ROLE"); + if (state == BluetoothProfile.STATE_CONNECTED) { + if(DBG) Log.d(TAG, "handlePanDeviceStateChange: panu STATE_CONNECTED, startReverseTether"); + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + Log.d(TAG, "call INetworkManagementService.startReverseTethering()"); + try { + service.startReverseTethering(iface); + } catch (Exception e) { + Log.e(TAG, "Cannot start reverse tethering: " + e); + return; + } + } else if (state == BluetoothProfile.STATE_DISCONNECTED && + (prevState == BluetoothProfile.STATE_CONNECTED || + prevState == BluetoothProfile.STATE_DISCONNECTING)) { + if(DBG) Log.d(TAG, "handlePanDeviceStateChange: stopReverseTether, panDevice.mIface: " + + panDevice.mIface); + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + try { + service.stopReverseTethering(); + } catch(Exception e) { + Log.e(TAG, "Cannot stop reverse tethering: " + e); + return; + } + } + } + + if (panDevice == null) { + panDevice = new BluetoothPanDevice(state, ifaceAddr, iface, local_role); + mPanDevices.put(device, panDevice); + } else { + panDevice.mState = state; + panDevice.mIfaceAddr = ifaceAddr; + panDevice.mLocalRole = local_role; + panDevice.mIface = iface; + } + + Intent intent = new Intent(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.putExtra(BluetoothPan.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothPan.EXTRA_STATE, state); + intent.putExtra(BluetoothPan.EXTRA_LOCAL_ROLE, local_role); + sendBroadcast(intent, BLUETOOTH_PERM); + + if (DBG) Log.d(TAG, "Pan Device state : device: " + device + " State:" + + prevState + "->" + state); + try { + mAdapterService.sendConnectionStateChange(device, BluetoothProfile.PAN, state, + prevState); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + + // configured when we start tethering + private String enableTethering(String iface) { + if (DBG) Log.d(TAG, "updateTetherState:" + iface); + + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + ConnectivityManager cm = + (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs(); + + // bring toggle the interfaces + String[] currentIfaces = new String[0]; + try { + currentIfaces = service.listInterfaces(); + } catch (Exception e) { + Log.e(TAG, "Error listing Interfaces :" + e); + return null; + } + + boolean found = false; + for (String currIface: currentIfaces) { + if (currIface.equals(iface)) { + found = true; + break; + } + } + + if (!found) return null; + + String address = createNewTetheringAddressLocked(); + if (address == null) return null; + + InterfaceConfiguration ifcg = null; + try { + ifcg = service.getInterfaceConfig(iface); + if (ifcg != null) { + InetAddress addr = null; + if (ifcg.addr == null || (addr = ifcg.addr.getAddress()) == null || + addr.equals(NetworkUtils.numericToInetAddress("0.0.0.0")) || + addr.equals(NetworkUtils.numericToInetAddress("::0"))) { + addr = NetworkUtils.numericToInetAddress(address); + } + ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up"); + ifcg.addr = new LinkAddress(addr, BLUETOOTH_PREFIX_LENGTH); + ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", ""); + ifcg.interfaceFlags = ifcg.interfaceFlags.replace(" "," "); + service.setInterfaceConfig(iface, ifcg); + if (cm.tether(iface) != ConnectivityManager.TETHER_ERROR_NO_ERROR) { + Log.e(TAG, "Error tethering "+iface); + } + } + } catch (Exception e) { + Log.e(TAG, "Error configuring interface " + iface + ", :" + e); + return null; + } + return address; + } + + private String createNewTetheringAddressLocked() { + if (getConnectedPanDevices().size() == mMaxPanDevices) { + if (DBG) Log.d(TAG, "Max PAN device connections reached"); + return null; + } + String address = BLUETOOTH_IFACE_ADDR_START; + while (true) { + if (mBluetoothIfaceAddresses.contains(address)) { + String[] addr = address.split("\\."); + Integer newIp = Integer.parseInt(addr[2]) + 1; + address = address.replace(addr[2], newIp.toString()); + } else { + break; + } + } + mBluetoothIfaceAddresses.add(address); + return address; + } + + private List getConnectedPanDevices() { + List devices = new ArrayList(); + + for (BluetoothDevice device: mPanDevices.keySet()) { + if (getPanDeviceConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { + devices.add(device); + } + } + return devices; + } + + private int getPanDeviceConnectionState(BluetoothDevice device) { + BluetoothPanDevice panDevice = mPanDevices.get(device); + if (panDevice == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return panDevice.mState; + } + + private class BluetoothPanDevice { + private int mState; + private String mIfaceAddr; + private String mIface; + private int mLocalRole; // Which local role is this PAN device bound to + + BluetoothPanDevice(int state, String ifaceAddr, String iface, int localRole) { + mState = state; + mIfaceAddr = ifaceAddr; + mIface = iface; + mLocalRole = localRole; + } + } + + private void log(String msg) { + Log.d(TAG, msg); + } + + // Constants matching Hal header file bt_hh.h + // bthh_connection_state_t + private final static int CONN_STATE_CONNECTED = 0; + private final static int CONN_STATE_CONNECTING = 1; + private final static int CONN_STATE_DISCONNECTED = 2; + private final static int CONN_STATE_DISCONNECTING = 3; + + private native static void classInitNative(); + private native void initializeNative(); + private native void cleanupNative(); + private native boolean connectPanNative(byte[] btAddress, int local_role, int remote_role); + private native boolean disconnectPanNative(byte[] btAddress); + private native boolean enablePanNative(int local_role); + private native int getPanLocalRoleNative(); +} diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapReceiver.java b/src/com/android/bluetooth/pbap/BluetoothPbapReceiver.java index 55cb66c8e..6aa09dce8 100644 --- a/src/com/android/bluetooth/pbap/BluetoothPbapReceiver.java +++ b/src/com/android/bluetooth/pbap/BluetoothPbapReceiver.java @@ -46,17 +46,20 @@ public class BluetoothPbapReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (V) Log.v(TAG, "PbapReceiver onReceive: " + intent.getAction()); + if (V) Log.v(TAG, "PbapReceiver onReceive "); Intent in = new Intent(); in.putExtras(intent); in.setClass(context, BluetoothPbapService.class); String action = intent.getAction(); in.putExtra("action", action); + if (V) Log.v(TAG,"***********action = " + action); + boolean startService = true; if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); in.putExtra(BluetoothAdapter.EXTRA_STATE, state); + if (V) Log.v(TAG,"***********state = " + state); if ((state == BluetoothAdapter.STATE_TURNING_ON) || (state == BluetoothAdapter.STATE_TURNING_OFF)) { startService = false; @@ -69,6 +72,7 @@ public class BluetoothPbapReceiver extends BroadcastReceiver { } } if (startService) { + if (V) Log.v(TAG,"***********Calling start service!!!! with action = " + in.getAction()); context.startService(in); } } diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java index a5c5f20c4..b2b1d4842 100644 --- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java +++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java @@ -75,9 +75,9 @@ public class BluetoothPbapService extends Service { * DEBUG log: "setprop log.tag.BluetoothPbapService VERBOSE" */ - public static final boolean DEBUG = false; + public static final boolean DEBUG = true; - public static final boolean VERBOSE = false; + public static final boolean VERBOSE = true; /** * Intent indicating incoming obex authentication request which is from @@ -182,9 +182,12 @@ public class BluetoothPbapService extends Service { public BluetoothPbapService() { mState = BluetoothPbap.STATE_DISCONNECTED; - IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE); + // IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE); + IBinder b = null; if (b == null) { - throw new RuntimeException("Bluetooth service not available"); + Log.e(TAG, "fail to get bluetooth service"); + // throw new RuntimeException("Bluetooth service not available"); + return; } mBluetoothService = IBluetooth.Stub.asInterface(b); } @@ -211,7 +214,6 @@ public class BluetoothPbapService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { - if (VERBOSE) Log.v(TAG, "Pbap Service onStartCommand"); int retCode = super.onStartCommand(intent, flags, startId); if (retCode == START_STICKY) { mStartId = startId; @@ -237,6 +239,8 @@ public class BluetoothPbapService extends Service { if (VERBOSE) Log.v(TAG, "action: " + action); int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); + if (VERBOSE) Log.v(TAG, "state: " + state); + boolean removeTimeoutMsg = true; if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { if (state == BluetoothAdapter.STATE_OFF) { @@ -532,7 +536,7 @@ public class BluetoothPbapService extends Service { if (trust) { try { - if (VERBOSE) Log.v(TAG, "incomming connection accepted from: " + if (VERBOSE) Log.v(TAG, "incoming connection accepted from: " + sRemoteDeviceName + " automatically as trusted device"); startObexServerSession(); } catch (IOException ex) { @@ -564,9 +568,12 @@ public class BluetoothPbapService extends Service { } stopped = true; // job done ,close this thread; } catch (IOException ex) { + stopped=true; + /* if (stopped) { break; } + */ if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString()); } } @@ -638,11 +645,15 @@ public class BluetoothPbapService extends Service { intent.putExtra(BluetoothPbap.PBAP_STATE, mState); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); sendBroadcast(intent, BLUETOOTH_PERM); - try { - mBluetoothService.sendConnectionStateChange(mRemoteDevice, BluetoothProfile.PBAP, - mState, prevState); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in sendConnectionStateChange"); + if (mBluetoothService != null) { + try { + mBluetoothService.sendConnectionStateChange(mRemoteDevice, BluetoothProfile.PBAP, + mState, prevState); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in sendConnectionStateChange"); + } + } else { + Log.e(TAG, "null mBluetoothService"); } } } -- cgit v1.2.3