am 19ca82e6: (-s ours) am 1453841a: (-s ours) Import translations. DO NOT MERGE
* commit '19ca82e647e4b2c232d81b0e7b89e9c969c35831':
Import translations. DO NOT MERGE
diff --git a/Android.mk b/Android.mk
index c6cfd20..0ef513e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -14,6 +14,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard
LOCAL_REQUIRED_MODULES := bluetooth.default
+LOCAL_MULTILIB := 32
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0d20a5a..770556b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -37,6 +37,7 @@
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="com.android.permission.HANDOVER_STATUS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
<uses-permission android:name="android.permission.NET_ADMIN" />
<uses-permission android:name="android.permission.CALL_PRIVILEGED" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
@@ -264,6 +265,22 @@
</service>
<service
android:process="@string/process"
+ android:name = ".a2dp.A2dpSinkService"
+ android:enabled="@bool/profile_supported_a2dp_sink">
+ <intent-filter>
+ <action android:name="android.bluetooth.IBluetoothA2dpSink" />
+ </intent-filter>
+ </service>
+ <service
+ android:process="@string/process"
+ android:name = ".avrcp.AvrcpControllerService"
+ android:enabled="@bool/profile_supported_avrcp_controller">
+ <intent-filter>
+ <action android:name="android.bluetooth.IBluetoothAvrcpController" />
+ </intent-filter>
+ </service>
+ <service
+ android:process="@string/process"
android:name = ".hid.HidService"
android:enabled="@bool/profile_supported_hid">
<intent-filter>
@@ -286,5 +303,13 @@
<action android:name="android.bluetooth.IBluetoothPan" />
</intent-filter>
</service>
+ <service
+ android:process="@string/process"
+ android:name = ".hfpclient.HeadsetClientService"
+ android:enabled="@bool/profile_supported_hfpclient">
+ <intent-filter>
+ <action android:name="android.bluetooth.IBluetoothHeadsetClient" />
+ </intent-filter>
+ </service>
</application>
</manifest>
diff --git a/jni/Android.mk b/jni/Android.mk
index 2eb21f6..e1c63b2 100644
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -5,8 +5,11 @@
LOCAL_SRC_FILES:= \
com_android_bluetooth_btservice_AdapterService.cpp \
com_android_bluetooth_hfp.cpp \
+ com_android_bluetooth_hfpclient.cpp \
com_android_bluetooth_a2dp.cpp \
+ com_android_bluetooth_a2dp_sink.cpp \
com_android_bluetooth_avrcp.cpp \
+ com_android_bluetooth_avrcp_controller.cpp \
com_android_bluetooth_hid.cpp \
com_android_bluetooth_hdp.cpp \
com_android_bluetooth_pan.cpp \
@@ -21,9 +24,9 @@
libcutils \
libutils \
liblog \
- libhardware \
- libpowermanager \
- libbinder
+ libhardware
+
+LOCAL_MULTILIB := 32
#LOCAL_CFLAGS += -O0 -g
diff --git a/jni/com_android_bluetooth.h b/jni/com_android_bluetooth.h
index 506cddd..74dcddb 100644
--- a/jni/com_android_bluetooth.h
+++ b/jni/com_android_bluetooth.h
@@ -1,4 +1,5 @@
/*
+ * Copyright (c) 2014 The Android Open Source Project
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -33,10 +34,16 @@
int register_com_android_bluetooth_hfp(JNIEnv* env);
+int register_com_android_bluetooth_hfpclient(JNIEnv* env);
+
int register_com_android_bluetooth_a2dp(JNIEnv* env);
+int register_com_android_bluetooth_a2dp_sink(JNIEnv* env);
+
int register_com_android_bluetooth_avrcp(JNIEnv* env);
+int register_com_android_bluetooth_avrcp_controller(JNIEnv* env);
+
int register_com_android_bluetooth_hid(JNIEnv* env);
int register_com_android_bluetooth_hdp(JNIEnv* env);
diff --git a/jni/com_android_bluetooth_a2dp_sink.cpp b/jni/com_android_bluetooth_a2dp_sink.cpp
new file mode 100644
index 0000000..f2bbb1b
--- /dev/null
+++ b/jni/com_android_bluetooth_a2dp_sink.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BluetoothA2dpSinkServiceJni"
+
+#define LOG_NDEBUG 0
+
+#include "com_android_bluetooth.h"
+#include "hardware/bt_av.h"
+#include "utils/Log.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <string.h>
+
+namespace android {
+static jmethodID method_onConnectionStateChanged;
+static jmethodID method_onAudioStateChanged;
+static jmethodID method_onAudioConfigChanged;
+
+static const btav_interface_t *sBluetoothA2dpInterface = NULL;
+static jobject mCallbacksObj = NULL;
+static JNIEnv *sCallbackEnv = NULL;
+
+static bool checkCallbackThread() {
+ // Always fetch the latest callbackEnv from AdapterService.
+ // Caching this could cause this sCallbackEnv to go out-of-sync
+ // with the AdapterService's ENV if an ASSOCIATE/DISASSOCIATE event
+ // is received
+ //if (sCallbackEnv == NULL) {
+ sCallbackEnv = getCallbackEnv();
+ //}
+
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ if (sCallbackEnv != env || sCallbackEnv == NULL) return false;
+ return true;
+}
+
+static void bta2dp_connection_state_callback(btav_connection_state_t state, bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for connection state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, (jint) state,
+ addr);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void bta2dp_audio_state_callback(btav_audio_state_t state, bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for connection state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged, (jint) state,
+ addr);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void bta2dp_audio_config_callback(bt_bdaddr_t *bd_addr, uint32_t sample_rate, uint8_t channel_count) {
+ jbyteArray addr;
+
+ ALOGI("%s", __FUNCTION__);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for connection state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioConfigChanged, addr, (jint)sample_rate, (jint)channel_count);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static btav_callbacks_t sBluetoothA2dpCallbacks = {
+ sizeof(sBluetoothA2dpCallbacks),
+ bta2dp_connection_state_callback,
+ bta2dp_audio_state_callback,
+ bta2dp_audio_config_callback
+};
+
+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");
+
+ method_onAudioStateChanged =
+ env->GetMethodID(clazz, "onAudioStateChanged", "(I[B)V");
+
+ method_onAudioConfigChanged =
+ env->GetMethodID(clazz, "onAudioConfigChanged", "([BII)V");
+
+ ALOGI("%s: succeeds", __FUNCTION__);
+}
+
+static void initNative(JNIEnv *env, jobject object) {
+ const bt_interface_t* btInf;
+ bt_status_t status;
+
+ if ( (btInf = getBluetoothInterface()) == NULL) {
+ ALOGE("Bluetooth module is not loaded");
+ return;
+ }
+
+ if (sBluetoothA2dpInterface !=NULL) {
+ ALOGW("Cleaning up A2DP Interface before initializing...");
+ sBluetoothA2dpInterface->cleanup();
+ sBluetoothA2dpInterface = NULL;
+ }
+
+ if (mCallbacksObj != NULL) {
+ ALOGW("Cleaning up A2DP callback object");
+ env->DeleteGlobalRef(mCallbacksObj);
+ mCallbacksObj = NULL;
+ }
+
+ if ( (sBluetoothA2dpInterface = (btav_interface_t *)
+ btInf->get_profile_interface(BT_PROFILE_ADVANCED_AUDIO_SINK_ID)) == NULL) {
+ ALOGE("Failed to get Bluetooth A2DP Sink Interface");
+ return;
+ }
+
+ if ( (status = sBluetoothA2dpInterface->init(&sBluetoothA2dpCallbacks)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed to initialize Bluetooth A2DP Sink, 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) {
+ ALOGE("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;
+ bt_status_t status;
+
+ ALOGI("%s: sBluetoothA2dpInterface: %p", __FUNCTION__, sBluetoothA2dpInterface);
+ if (!sBluetoothA2dpInterface) return JNI_FALSE;
+
+ addr = env->GetByteArrayElements(address, NULL);
+ btAddr = (bt_bdaddr_t *) addr;
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ((status = sBluetoothA2dpInterface->connect((bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed HF connection, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean disconnectA2dpNative(JNIEnv *env, jobject object, jbyteArray address) {
+ jbyte *addr;
+ bt_status_t status;
+
+ if (!sBluetoothA2dpInterface) return JNI_FALSE;
+
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ( (status = sBluetoothA2dpInterface->disconnect((bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed HF disconnection, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static JNINativeMethod sMethods[] = {
+ {"classInitNative", "()V", (void *) classInitNative},
+ {"initNative", "()V", (void *) initNative},
+ {"cleanupNative", "()V", (void *) cleanupNative},
+ {"connectA2dpNative", "([B)Z", (void *) connectA2dpNative},
+ {"disconnectA2dpNative", "([B)Z", (void *) disconnectA2dpNative},
+};
+
+int register_com_android_bluetooth_a2dp_sink(JNIEnv* env)
+{
+ return jniRegisterNativeMethods(env, "com/android/bluetooth/a2dp/A2dpSinkStateMachine",
+ sMethods, NELEM(sMethods));
+}
+
+}
diff --git a/jni/com_android_bluetooth_avrcp.cpp b/jni/com_android_bluetooth_avrcp.cpp
index c077bc6..fb6f2fd 100644
--- a/jni/com_android_bluetooth_avrcp.cpp
+++ b/jni/com_android_bluetooth_avrcp.cpp
@@ -67,6 +67,7 @@
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getRcFeatures, addr, (jint)features);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
static void btavrcp_get_play_status_callback() {
@@ -154,7 +155,7 @@
btavrcp_get_element_attr_callback,
btavrcp_register_notification_callback,
btavrcp_volume_change_callback,
- btavrcp_passthrough_command_callback
+ btavrcp_passthrough_command_callback,
};
static void classInitNative(JNIEnv* env, jclass clazz) {
@@ -407,12 +408,12 @@
{"registerNotificationRspPlayPosNative", "(II)Z",
(void *) registerNotificationRspPlayPosNative},
{"setVolumeNative", "(I)Z",
- (void *) setVolumeNative}
+ (void *) setVolumeNative},
};
int register_com_android_bluetooth_avrcp(JNIEnv* env)
{
- return jniRegisterNativeMethods(env, "com/android/bluetooth/a2dp/Avrcp",
+ return jniRegisterNativeMethods(env, "com/android/bluetooth/avrcp/Avrcp",
sMethods, NELEM(sMethods));
}
diff --git a/jni/com_android_bluetooth_avrcp_controller.cpp b/jni/com_android_bluetooth_avrcp_controller.cpp
new file mode 100644
index 0000000..9e32721
--- /dev/null
+++ b/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BluetoothAvrcpControllerJni"
+
+#define LOG_NDEBUG 0
+
+#include "com_android_bluetooth.h"
+#include "hardware/bt_rc.h"
+#include "utils/Log.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <string.h>
+
+namespace android {
+static jmethodID method_handlePassthroughRsp;
+static jmethodID method_onConnectionStateChanged;
+
+static const btrc_ctrl_interface_t *sBluetoothAvrcpInterface = NULL;
+static jobject mCallbacksObj = NULL;
+static JNIEnv *sCallbackEnv = NULL;
+
+static bool checkCallbackThread() {
+ // Always fetch the latest callbackEnv from AdapterService.
+ // Caching this could cause this sCallbackEnv to go out-of-sync
+ // with the AdapterService's ENV if an ASSOCIATE/DISASSOCIATE event
+ // is received
+ sCallbackEnv = getCallbackEnv();
+
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ if (sCallbackEnv != env || sCallbackEnv == NULL) return false;
+ return true;
+}
+
+static void btavrcp_passthrough_response_callback(int id, int pressed) {
+ ALOGI("%s", __FUNCTION__);
+ ALOGI("id: %d, pressed: %d", id, pressed);
+
+ if (!checkCallbackThread()) {
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handlePassthroughRsp, (jint)id,
+ (jint)pressed);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void btavrcp_connection_state_callback(bool state, bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
+ ALOGI("%s", __FUNCTION__);
+ ALOGI("conn state: %d", state);
+
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for connection state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, (jboolean) state,
+ addr);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+
+static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
+ sizeof(sBluetoothAvrcpCallbacks),
+ btavrcp_passthrough_response_callback,
+ btavrcp_connection_state_callback
+};
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+ method_handlePassthroughRsp =
+ env->GetMethodID(clazz, "handlePassthroughRsp", "(II)V");
+
+ method_onConnectionStateChanged =
+ env->GetMethodID(clazz, "onConnectionStateChanged", "(Z[B)V");
+
+ ALOGI("%s: succeeds", __FUNCTION__);
+}
+
+static void initNative(JNIEnv *env, jobject object) {
+ const bt_interface_t* btInf;
+ bt_status_t status;
+
+ if ( (btInf = getBluetoothInterface()) == NULL) {
+ ALOGE("Bluetooth module is not loaded");
+ return;
+ }
+
+ if (sBluetoothAvrcpInterface !=NULL) {
+ ALOGW("Cleaning up Avrcp Interface before initializing...");
+ sBluetoothAvrcpInterface->cleanup();
+ sBluetoothAvrcpInterface = NULL;
+ }
+
+ if (mCallbacksObj != NULL) {
+ ALOGW("Cleaning up Avrcp callback object");
+ env->DeleteGlobalRef(mCallbacksObj);
+ mCallbacksObj = NULL;
+ }
+
+ if ( (sBluetoothAvrcpInterface = (btrc_ctrl_interface_t *)
+ btInf->get_profile_interface(BT_PROFILE_AV_RC_CTRL_ID)) == NULL) {
+ ALOGE("Failed to get Bluetooth Avrcp Controller Interface");
+ return;
+ }
+
+ if ( (status = sBluetoothAvrcpInterface->init(&sBluetoothAvrcpCallbacks)) !=
+ BT_STATUS_SUCCESS) {
+ ALOGE("Failed to initialize Bluetooth Avrcp Controller, status: %d", status);
+ sBluetoothAvrcpInterface = NULL;
+ return;
+ }
+
+ mCallbacksObj = env->NewGlobalRef(object);
+}
+
+static void cleanupNative(JNIEnv *env, jobject object) {
+ const bt_interface_t* btInf;
+
+ if ( (btInf = getBluetoothInterface()) == NULL) {
+ ALOGE("Bluetooth module is not loaded");
+ return;
+ }
+
+ if (sBluetoothAvrcpInterface !=NULL) {
+ sBluetoothAvrcpInterface->cleanup();
+ sBluetoothAvrcpInterface = NULL;
+ }
+
+ if (mCallbacksObj != NULL) {
+ env->DeleteGlobalRef(mCallbacksObj);
+ mCallbacksObj = NULL;
+ }
+}
+
+static jboolean sendPassThroughCommandNative(JNIEnv *env, jobject object, jbyteArray address,
+ jint key_code, jint key_state) {
+ jbyte *addr;
+ bt_status_t status;
+
+ if (!sBluetoothAvrcpInterface) return JNI_FALSE;
+
+ ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+
+ ALOGI("key_code: %d, key_state: %d", key_code, key_state);
+
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ((status = sBluetoothAvrcpInterface->send_pass_through_cmd((bt_bdaddr_t *)addr,
+ (uint8_t)key_code, (uint8_t)key_state))!= BT_STATUS_SUCCESS) {
+ ALOGE("Failed sending passthru command, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static JNINativeMethod sMethods[] = {
+ {"classInitNative", "()V", (void *) classInitNative},
+ {"initNative", "()V", (void *) initNative},
+ {"cleanupNative", "()V", (void *) cleanupNative},
+ {"sendPassThroughCommandNative", "([BII)Z",
+ (void *) sendPassThroughCommandNative},
+};
+
+int register_com_android_bluetooth_avrcp_controller(JNIEnv* env)
+{
+ return jniRegisterNativeMethods(env, "com/android/bluetooth/avrcp/AvrcpControllerService",
+ sMethods, NELEM(sMethods));
+}
+
+}
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index 91c59e7..89ddb89 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -22,10 +22,6 @@
#include "cutils/properties.h"
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/Log.h"
-#include <powermanager/PowerManager.h>
-#include <powermanager/IPowerManager.h>
-#include <binder/IServiceManager.h>
-#include <utils/RefBase.h>
#include <string.h>
#include <pthread.h>
@@ -45,11 +41,15 @@
static jmethodID method_bondStateChangeCallback;
static jmethodID method_aclStateChangeCallback;
static jmethodID method_discoveryStateChangeCallback;
+static jmethodID method_setWakeAlarm;
+static jmethodID method_acquireWakeLock;
+static jmethodID method_releaseWakeLock;
static const bt_interface_t *sBluetoothInterface = NULL;
static const btsock_interface_t *sBluetoothSocketInterface = NULL;
static JNIEnv *callbackEnv = NULL;
+static jobject sJniAdapterServiceObj;
static jobject sJniCallbacksObj;
static jfieldID sJniCallbacksField;
@@ -443,40 +443,7 @@
ALOGV("%s: status:%d packet_count:%d ", __FUNCTION__, status, packet_count);
}
-
-static void bt_wakelock_control_callback(int acquire) {
- static sp<IPowerManager> sPm = NULL;
- static sp<IBinder> sWakelock = NULL;
-
- if (sPm == NULL) {
- sp<IBinder> binder = defaultServiceManager()->checkService(String16("power"));
- sPm = interface_cast<IPowerManager>(binder);
-
- if (sPm == NULL) {
- ALOGE("Unable to get a hold of PowerManager. Bluetooth wakelocks will not function");
- return;
- }
- }
- if (acquire) {
- if (sWakelock != NULL)
- ALOGE("Double wakelock-acquire in BT");
- else {
- sp<IBinder> binder = new BBinder();
- if (NO_ERROR == sPm->acquireWakeLock(POWERMANAGER_PARTIAL_WAKE_LOCK, binder,
- String16("TimerWakelock"), String16("com.android.bluetooth")))
- sWakelock = binder;
- }
- } else {
- if (sWakelock == NULL)
- ALOGE("Double wakelock-release in BT");
- else {
- sPm->releaseWakeLock(sWakelock, 0);
- sWakelock.clear();
- }
- }
-}
-
-bt_callbacks_t sBluetoothCallbacks = {
+static bt_callbacks_t sBluetoothCallbacks = {
sizeof(sBluetoothCallbacks),
adapter_state_change_callback,
adapter_properties_callback,
@@ -490,8 +457,117 @@
callback_thread_event,
dut_mode_recv_callback,
- le_test_mode_recv_callback,
- bt_wakelock_control_callback
+ le_test_mode_recv_callback
+};
+
+// The callback to call when the wake alarm fires.
+static alarm_cb sAlarmCallback;
+
+// The data to pass to the wake alarm callback.
+static void *sAlarmCallbackData;
+
+static bool set_wake_alarm_callout(uint64_t delay_millis, bool should_wake, alarm_cb cb, void *data) {
+ JNIEnv *env;
+ JavaVM *vm = AndroidRuntime::getJavaVM();
+ jint status = vm->GetEnv((void **)&env, JNI_VERSION_1_6);
+
+ if (status != JNI_OK && status != JNI_EDETACHED) {
+ ALOGE("%s unable to get environment for JNI call", __func__);
+ return false;
+ }
+
+ if (status == JNI_EDETACHED && vm->AttachCurrentThread(&env, NULL) != 0) {
+ ALOGE("%s unable to attach thread to VM", __func__);
+ return false;
+ }
+
+ jboolean jshould_wake = should_wake ? JNI_TRUE : JNI_FALSE;
+ jboolean ret = env->CallBooleanMethod(sJniAdapterServiceObj, method_setWakeAlarm, (jlong)delay_millis, jshould_wake);
+ if (ret) {
+ sAlarmCallback = cb;
+ sAlarmCallbackData = data;
+ }
+
+ if (status == JNI_EDETACHED) {
+ vm->DetachCurrentThread();
+ }
+
+ return !!ret;
+}
+
+static int acquire_wake_lock_callout(const char *lock_name) {
+ JNIEnv *env;
+ JavaVM *vm = AndroidRuntime::getJavaVM();
+ jint status = vm->GetEnv((void **)&env, JNI_VERSION_1_6);
+
+ if (status != JNI_OK && status != JNI_EDETACHED) {
+ ALOGE("%s unable to get environment for JNI call", __func__);
+ return BT_STATUS_FAIL;
+ }
+
+ if (status == JNI_EDETACHED && vm->AttachCurrentThread(&env, NULL) != 0) {
+ ALOGE("%s unable to attach thread to VM", __func__);
+ return BT_STATUS_FAIL;
+ }
+
+ jboolean ret = JNI_FALSE;
+ jstring lock_name_jni = env->NewStringUTF(lock_name);
+ if (lock_name_jni) {
+ ret = env->CallBooleanMethod(sJniAdapterServiceObj, method_acquireWakeLock, lock_name_jni);
+ env->DeleteLocalRef(lock_name_jni);
+ } else {
+ ALOGE("%s unable to allocate string: %s", __func__, lock_name);
+ }
+
+ if (status == JNI_EDETACHED) {
+ vm->DetachCurrentThread();
+ }
+
+ return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL;
+}
+
+static int release_wake_lock_callout(const char *lock_name) {
+ JNIEnv *env;
+ JavaVM *vm = AndroidRuntime::getJavaVM();
+ jint status = vm->GetEnv((void **)&env, JNI_VERSION_1_6);
+
+ if (status != JNI_OK && status != JNI_EDETACHED) {
+ ALOGE("%s unable to get environment for JNI call", __func__);
+ return BT_STATUS_FAIL;
+ }
+
+ if (status == JNI_EDETACHED && vm->AttachCurrentThread(&env, NULL) != 0) {
+ ALOGE("%s unable to attach thread to VM", __func__);
+ return BT_STATUS_FAIL;
+ }
+
+ jboolean ret = JNI_FALSE;
+ jstring lock_name_jni = env->NewStringUTF(lock_name);
+ if (lock_name_jni) {
+ ret = env->CallBooleanMethod(sJniAdapterServiceObj, method_releaseWakeLock, lock_name_jni);
+ env->DeleteLocalRef(lock_name_jni);
+ } else {
+ ALOGE("%s unable to allocate string: %s", __func__, lock_name);
+ }
+
+ if (status == JNI_EDETACHED) {
+ vm->DetachCurrentThread();
+ }
+
+ return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL;
+}
+
+// Called by Java code when alarm is fired. A wake lock is held by the caller
+// over the duration of this callback.
+static void alarmFiredNative(JNIEnv *env, jobject obj) {
+ sAlarmCallback(sAlarmCallbackData);
+}
+
+static bt_os_callouts_t sBluetoothOsCallouts = {
+ sizeof(sBluetoothOsCallouts),
+ set_wake_alarm_callout,
+ acquire_wake_lock_callout,
+ release_wake_lock_callout,
};
static void classInitNative(JNIEnv* env, jclass clazz) {
@@ -525,6 +601,11 @@
method_aclStateChangeCallback = env->GetMethodID(jniCallbackClass,
"aclStateChangeCallback", "(I[BI)V");
+
+ method_setWakeAlarm = env->GetMethodID(clazz, "setWakeAlarm", "(JZ)Z");
+ method_acquireWakeLock = env->GetMethodID(clazz, "acquireWakeLock", "(Ljava/lang/String;)Z");
+ method_releaseWakeLock = env->GetMethodID(clazz, "releaseWakeLock", "(Ljava/lang/String;)Z");
+
char value[PROPERTY_VALUE_MAX];
property_get("bluetooth.mock_stack", value, "");
@@ -549,15 +630,24 @@
static bool initNative(JNIEnv* env, jobject obj) {
ALOGV("%s:",__FUNCTION__);
+ sJniAdapterServiceObj = env->NewGlobalRef(obj);
sJniCallbacksObj = env->NewGlobalRef(env->GetObjectField(obj, sJniCallbacksField));
if (sBluetoothInterface) {
int ret = sBluetoothInterface->init(&sBluetoothCallbacks);
if (ret != BT_STATUS_SUCCESS) {
- ALOGE("Error while setting the callbacks \n");
+ ALOGE("Error while setting the callbacks: %d\n", ret);
sBluetoothInterface = NULL;
return JNI_FALSE;
}
+ ret = sBluetoothInterface->set_os_callouts(&sBluetoothOsCallouts);
+ if (ret != BT_STATUS_SUCCESS) {
+ ALOGE("Error while setting Bluetooth callouts: %d\n", ret);
+ sBluetoothInterface->cleanup();
+ sBluetoothInterface = NULL;
+ return JNI_FALSE;
+ }
+
if ( (sBluetoothSocketInterface = (btsock_interface_t *)
sBluetoothInterface->get_profile_interface(BT_PROFILE_SOCKETS_ID)) == NULL) {
ALOGE("Error getting socket interface");
@@ -577,6 +667,7 @@
ALOGI("%s: return from cleanup",__FUNCTION__);
env->DeleteGlobalRef(sJniCallbacksObj);
+ env->DeleteGlobalRef(sJniAdapterServiceObj);
return JNI_TRUE;
}
@@ -914,6 +1005,8 @@
if (!sBluetoothSocketInterface) return -1;
+ ALOGV("%s: SOCK FLAG = %x", __FUNCTION__, flag);
+
service_name = env->GetStringUTFChars(name_str, NULL);
uuid = env->GetByteArrayElements(uuidObj, NULL);
@@ -921,7 +1014,6 @@
ALOGE("failed to get uuid");
goto Fail;
}
- ALOGE("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) {
ALOGE("Socket listen failed: %d", status);
@@ -980,7 +1072,8 @@
{"connectSocketNative", "([BI[BII)I", (void*) connectSocketNative},
{"createSocketChannelNative", "(ILjava/lang/String;[BII)I",
(void*) createSocketChannelNative},
- {"configHciSnoopLogNative", "(Z)Z", (void*) configHciSnoopLogNative}
+ {"configHciSnoopLogNative", "(Z)Z", (void*) configHciSnoopLogNative},
+ {"alarmFiredNative", "()V", (void *) alarmFiredNative},
};
int register_com_android_bluetooth_btservice_AdapterService(JNIEnv* env)
@@ -1018,13 +1111,28 @@
return JNI_ERR;
}
+ if ((status = android::register_com_android_bluetooth_hfpclient(e)) < 0) {
+ ALOGE("jni hfp client registration failure, status: %d", status);
+ return JNI_ERR;
+ }
+
if ((status = android::register_com_android_bluetooth_a2dp(e)) < 0) {
- ALOGE("jni a2dp registration failure: %d", status);
+ ALOGE("jni a2dp source registration failure: %d", status);
+ return JNI_ERR;
+ }
+
+ if ((status = android::register_com_android_bluetooth_a2dp_sink(e)) < 0) {
+ ALOGE("jni a2dp sink registration failure: %d", status);
return JNI_ERR;
}
if ((status = android::register_com_android_bluetooth_avrcp(e)) < 0) {
- ALOGE("jni avrcp registration failure: %d", status);
+ ALOGE("jni avrcp target registration failure: %d", status);
+ return JNI_ERR;
+ }
+
+ if ((status = android::register_com_android_bluetooth_avrcp_controller(e)) < 0) {
+ ALOGE("jni avrcp controller registration failure: %d", status);
return JNI_ERR;
}
diff --git a/jni/com_android_bluetooth_gatt.cpp b/jni/com_android_bluetooth_gatt.cpp
index 5941ad4..1898357 100644
--- a/jni/com_android_bluetooth_gatt.cpp
+++ b/jni/com_android_bluetooth_gatt.cpp
@@ -160,6 +160,11 @@
static jmethodID method_onReadRemoteRssi;
static jmethodID method_onAdvertiseCallback;
static jmethodID method_onConfigureMTU;
+static jmethodID method_onScanFilterConfig;
+static jmethodID method_onMultiAdvEnable;
+static jmethodID method_onMultiAdvUpdate;
+static jmethodID method_onMultiAdvSetAdvData;
+static jmethodID method_onMultiAdvDisable;
/**
* Server callback methods
@@ -439,6 +444,42 @@
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
}
+void btgattc_scan_filter_cb(int action, int status)
+{
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanFilterConfig,
+ action, status);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_multiadv_enable_cb(int client_if, int status)
+{
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onMultiAdvEnable, status,client_if);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_multiadv_update_cb(int client_if, int status)
+{
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onMultiAdvUpdate, status, client_if);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_multiadv_setadv_data_cb(int client_if, int status)
+{
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onMultiAdvSetAdvData, status, client_if);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_multiadv_disable_cb(int client_if, int status)
+{
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onMultiAdvDisable, status, client_if);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
static const btgatt_client_callbacks_t sGattClientCallbacks = {
btgattc_register_app_cb,
btgattc_scan_result_cb,
@@ -459,6 +500,11 @@
btgattc_remote_rssi_cb,
btgattc_advertise_cb,
btgattc_configure_mtu_cb,
+ btgattc_scan_filter_cb,
+ btgattc_multiadv_enable_cb,
+ btgattc_multiadv_update_cb,
+ btgattc_multiadv_setadv_data_cb,
+ btgattc_multiadv_disable_cb
};
@@ -674,6 +720,12 @@
method_onRegisterForNotifications = env->GetMethodID(clazz, "onRegisterForNotifications", "(IIIIIJJIJJ)V");
method_onReadRemoteRssi = env->GetMethodID(clazz, "onReadRemoteRssi", "(ILjava/lang/String;II)V");
method_onConfigureMTU = env->GetMethodID(clazz, "onConfigureMTU", "(III)V");
+ method_onAdvertiseCallback = env->GetMethodID(clazz, "onAdvertiseCallback", "(II)V");
+ method_onScanFilterConfig = env->GetMethodID(clazz, "onScanFilterConfig", "(II)V");
+ method_onMultiAdvEnable = env->GetMethodID(clazz, "onClientEnable", "(II)V");
+ method_onMultiAdvUpdate = env->GetMethodID(clazz, "onClientUpdate", "(II)V");
+ method_onMultiAdvSetAdvData = env->GetMethodID(clazz, "onClientData", "(II)V");
+ method_onMultiAdvDisable = env->GetMethodID(clazz, "onClientDisable", "(II)V");
// Server callbacks
@@ -690,7 +742,6 @@
method_onAttributeRead= env->GetMethodID(clazz, "onAttributeRead", "(Ljava/lang/String;IIIIZ)V");
method_onAttributeWrite= env->GetMethodID(clazz, "onAttributeWrite", "(Ljava/lang/String;IIIIIZZ[B)V");
method_onExecuteWrite= env->GetMethodID(clazz, "onExecuteWrite", "(Ljava/lang/String;III)V");
- method_onAdvertiseCallback = env->GetMethodID(clazz, "onAdvertiseCallback", "(II)V");
info("classInitNative: Success!");
}
@@ -778,20 +829,20 @@
sGattIf->client->unregister_client(clientIf);
}
-static void gattClientScanNative(JNIEnv* env, jobject object, jint clientIf, jboolean start)
+static void gattClientScanNative(JNIEnv* env, jobject object, jboolean start)
{
if (!sGattIf) return;
- sGattIf->client->scan(clientIf, start);
+ sGattIf->client->scan(start);
}
static void gattClientConnectNative(JNIEnv* env, jobject object, jint clientif,
- jstring address, jboolean isDirect)
+ jstring address, jboolean isDirect, jint transport)
{
if (!sGattIf) return;
bt_bdaddr_t bda;
jstr2bdaddr(env, &bda, address);
- sGattIf->client->connect(clientif, &bda, isDirect);
+ sGattIf->client->connect(clientif, &bda, isDirect, transport);
}
static void gattClientDisconnectNative(JNIEnv* env, jobject object, jint clientIf,
@@ -1100,6 +1151,84 @@
env->ReleaseByteArrayElements(serviceUuid, service_uuid, JNI_ABORT);
}
+static void gattSetScanParametersNative(JNIEnv* env, jobject object,
+ jint scan_interval, jint scan_window)
+{
+ if (!sGattIf) return;
+ sGattIf->client->set_scan_parameters(scan_interval, scan_window);
+}
+
+
+static void gattClientScanFilterEnableNative(JNIEnv* env, jobject object, jboolean enable)
+{
+ if (!sGattIf) return;
+ sGattIf->client->scan_filter_enable(enable ? 1 : 0);
+}
+
+static void gattClientScanFilterAddNative(JNIEnv* env, jobject object, jint type,
+ jint company_id, jint company_mask, jlong uuid_lsb, jlong uuid_msb,
+ jlong uuid_mask_lsb, jlong uuid_mask_msb,
+ jstring name, jstring address, jbyte addr_type, jbyteArray data)
+{
+ if (!sGattIf) return;
+
+ switch(type)
+ {
+ case 0: // BTM_BLE_PF_ADDR_FILTER
+ bt_bdaddr_t bda;
+ jstr2bdaddr(env, &bda, address);
+ sGattIf->client->scan_filter_add(type, 0, 0, 0, 0, 0, &bda, addr_type, 0);
+ break;
+
+ case 1: // BTM_BLE_PF_SRVC_DATA
+ sGattIf->client->scan_filter_add(type, 0, 0, 0, 0, 0, 0, 0, 0);
+ break;
+
+ case 2: // BTM_BLE_PF_SRVC_UUID
+ case 3: // BTM_BLE_PF_SRVC_SOL_UUID
+ {
+ bt_uuid_t uuid, uuid_mask;
+ set_uuid(uuid.uu, uuid_msb, uuid_lsb);
+ set_uuid(uuid_mask.uu, uuid_mask_msb, uuid_mask_lsb);
+ if (uuid_mask_lsb != 0 && uuid_mask_msb != 0)
+ sGattIf->client->scan_filter_add(type, 0, 0, 0, &uuid, &uuid_mask, 0, 0, 0);
+ else
+ sGattIf->client->scan_filter_add(type, 0, 0, 0, &uuid, 0, 0, 0, 0);
+ break;
+ }
+
+ case 4: // BTM_BLE_PF_LOCAL_NAME
+ {
+ const char* c_name = env->GetStringUTFChars(name, NULL);
+ if (c_name != NULL && strlen(c_name) != 0)
+ {
+ sGattIf->client->scan_filter_add(type, 0, 0, strlen(c_name), 0, 0, 0, 0, c_name);
+ env->ReleaseStringUTFChars(name, c_name);
+ }
+ break;
+ }
+
+ case 5: // BTM_BLE_PF_MANU_DATA
+ {
+ jbyte* data_array = env->GetByteArrayElements(data, 0);
+ int data_len = env->GetArrayLength(data) / 2; // Array contains mask
+
+ sGattIf->client->scan_filter_add(type, company_id,company_mask, data_len,
+ 0, 0, 0, 0, (char*)data_array);
+ env->ReleaseByteArrayElements(data, data_array, JNI_ABORT);
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+static void gattClientScanFilterClearNative(JNIEnv* env, jobject object)
+{
+ if (!sGattIf) return;
+ sGattIf->client->scan_filter_clear();
+}
static void gattClientConfigureMTUNative(JNIEnv *env, jobject object,
jint conn_id, jint mtu)
@@ -1108,6 +1237,54 @@
sGattIf->client->configure_mtu(conn_id, mtu);
}
+static void gattClientEnableAdvNative(JNIEnv* env, jobject object, jint client_if,
+ jint min_interval, jint max_interval, jint adv_type, jint chnl_map, jint tx_power)
+{
+ if (!sGattIf) return;
+
+ sGattIf->client->multi_adv_enable(client_if, min_interval, max_interval, adv_type, chnl_map,
+ tx_power);
+}
+
+static void gattClientUpdateAdvNative(JNIEnv* env, jobject object, jint client_if,
+ jint min_interval, jint max_interval, jint adv_type, jint chnl_map, jint tx_power)
+{
+ if (!sGattIf) return;
+
+ sGattIf->client->multi_adv_update(client_if, min_interval, max_interval, adv_type, chnl_map,
+ tx_power);
+}
+
+static void gattClientSetAdvDataNative(JNIEnv* env, jobject object , jint client_if,
+ jboolean set_scan_rsp, jboolean incl_name, jboolean incl_txpower, jint appearance,
+ jbyteArray manufacturer_data,jbyteArray service_data, jbyteArray service_uuid)
+{
+ if (!sGattIf) return;
+ jbyte* manu_data = env->GetByteArrayElements(manufacturer_data, NULL);
+ uint16_t manu_len = (uint16_t) env->GetArrayLength(manufacturer_data);
+
+ jbyte* serv_data = env->GetByteArrayElements(service_data, NULL);
+ uint16_t serv_data_len = (uint16_t) env->GetArrayLength(service_data);
+
+ jbyte* serv_uuid = env->GetByteArrayElements(service_uuid, NULL);
+ uint16_t serv_uuid_len = (uint16_t) env->GetArrayLength(service_uuid);
+
+ sGattIf->client->multi_adv_set_inst_data(client_if, set_scan_rsp, incl_name,incl_txpower,
+ appearance, manu_len, (char*)manu_data,
+ serv_data_len, (char*)serv_data, serv_uuid_len,
+ (char*)serv_uuid);
+
+ env->ReleaseByteArrayElements(manufacturer_data, manu_data, JNI_ABORT);
+ env->ReleaseByteArrayElements(service_data, serv_data, JNI_ABORT);
+ env->ReleaseByteArrayElements(service_uuid, serv_uuid, JNI_ABORT);
+}
+
+static void gattClientDisableAdvNative(JNIEnv* env, jobject object, jint client_if)
+{
+ if (!sGattIf) return;
+ sGattIf->client->multi_adv_disable(client_if);
+}
+
/**
* Native server functions
*/
@@ -1127,7 +1304,7 @@
}
static void gattServerConnectNative(JNIEnv *env, jobject object,
- jint server_if, jstring address, jboolean is_direct)
+ jint server_if, jstring address, jboolean is_direct, jint transport)
{
if (!sGattIf) return;
@@ -1135,7 +1312,7 @@
const char *c_address = env->GetStringUTFChars(address, NULL);
bd_addr_str_to_addr(c_address, bd_addr.address);
- sGattIf->server->connect(server_if, &bd_addr, is_direct);
+ sGattIf->server->connect(server_if, &bd_addr, is_direct, transport);
}
static void gattServerDisconnectNative(JNIEnv* env, jobject object, jint serverIf,
@@ -1297,6 +1474,19 @@
* JNI function definitinos
*/
+// JNI functions defined in GattStateMachine class.
+static JNINativeMethod sStateMachineMethods[] = {
+ {"gattClientScanNative", "(Z)V", (void *) gattClientScanNative},
+ {"gattClientScanFilterEnableNative", "(Z)V", (void *) gattClientScanFilterEnableNative},
+ {"gattClientScanFilterAddNative", "(IIIJJJJLjava/lang/String;Ljava/lang/String;B[B)V", (void *) gattClientScanFilterAddNative},
+ {"gattClientScanFilterClearNative", "()V", (void *) gattClientScanFilterClearNative},
+ {"gattClientEnableAdvNative", "(IIIIII)V", (void *) gattClientEnableAdvNative},
+ {"gattClientUpdateAdvNative", "(IIIIII)V", (void *) gattClientUpdateAdvNative},
+ {"gattClientSetAdvDataNative", "(IZZZI[B[B[B)V", (void *) gattClientSetAdvDataNative},
+ {"gattClientDisableAdvNative", "(I)V", (void *) gattClientDisableAdvNative},
+};
+
+// JNI functions defined in GattService class.
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void *) classInitNative},
{"initializeNative", "()V", (void *) initializeNative},
@@ -1305,8 +1495,7 @@
{"gattClientGetDeviceTypeNative", "(Ljava/lang/String;)I", (void *) gattClientGetDeviceTypeNative},
{"gattClientRegisterAppNative", "(JJ)V", (void *) gattClientRegisterAppNative},
{"gattClientUnregisterAppNative", "(I)V", (void *) gattClientUnregisterAppNative},
- {"gattClientScanNative", "(IZ)V", (void *) gattClientScanNative},
- {"gattClientConnectNative", "(ILjava/lang/String;Z)V", (void *) gattClientConnectNative},
+ {"gattClientConnectNative", "(ILjava/lang/String;ZI)V", (void *) gattClientConnectNative},
{"gattClientDisconnectNative", "(ILjava/lang/String;I)V", (void *) gattClientDisconnectNative},
{"gattClientRefreshNative", "(ILjava/lang/String;)V", (void *) gattClientRefreshNative},
{"gattClientSearchServiceNative", "(IZJJ)V", (void *) gattClientSearchServiceNative},
@@ -1325,7 +1514,7 @@
{"gattServerRegisterAppNative", "(JJ)V", (void *) gattServerRegisterAppNative},
{"gattServerUnregisterAppNative", "(I)V", (void *) gattServerUnregisterAppNative},
- {"gattServerConnectNative", "(ILjava/lang/String;Z)V", (void *) gattServerConnectNative},
+ {"gattServerConnectNative", "(ILjava/lang/String;ZI)V", (void *) gattServerConnectNative},
{"gattServerDisconnectNative", "(ILjava/lang/String;I)V", (void *) gattServerDisconnectNative},
{"gattServerAddServiceNative", "(IIIJJI)V", (void *) gattServerAddServiceNative},
{"gattServerAddIncludedServiceNative", "(III)V", (void *) gattServerAddIncludedServiceNative},
@@ -1339,13 +1528,18 @@
{"gattServerSendResponseNative", "(IIIIII[BI)V", (void *) gattServerSendResponseNative},
{"gattSetAdvDataNative", "(IZZZIII[B[B[B)V", (void *) gattSetAdvDataNative},
+ {"gattSetScanParametersNative", "(II)V", (void *) gattSetScanParametersNative},
{"gattTestNative", "(IJJLjava/lang/String;IIIII)V", (void *) gattTestNative},
};
int register_com_android_bluetooth_gatt(JNIEnv* env)
{
- return jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/GattService",
- sMethods, NELEM(sMethods));
+ int register_success =
+ jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/GattServiceStateMachine",
+ sStateMachineMethods, NELEM(sStateMachineMethods));
+ return register_success &
+ jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/GattService",
+ sMethods, NELEM(sMethods));
}
}
diff --git a/jni/com_android_bluetooth_hfp.cpp b/jni/com_android_bluetooth_hfp.cpp
index a99e9a8..f0306c6 100644
--- a/jni/com_android_bluetooth_hfp.cpp
+++ b/jni/com_android_bluetooth_hfp.cpp
@@ -104,96 +104,250 @@
sCallbackEnv->DeleteLocalRef(addr);
}
-static void voice_recognition_callback(bthf_vr_state_t state) {
+static void voice_recognition_callback(bthf_vr_state_t state, bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVrStateChanged, (jint) state);
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVrStateChanged, (jint) state, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void answer_call_callback() {
+static void answer_call_callback(bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAnswerCall);
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAnswerCall, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void hangup_call_callback() {
+static void hangup_call_callback(bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onHangupCall);
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onHangupCall, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void volume_control_callback(bthf_volume_type_t type, int volume) {
+static void volume_control_callback(bthf_volume_type_t type, int volume, bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVolumeChanged, (jint) type, (jint) volume);
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVolumeChanged, (jint) type,
+ (jint) volume, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void dial_call_callback(char *number) {
+static void dial_call_callback(char *number, bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
CHECK_CALLBACK_ENV
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
jstring js_number = sCallbackEnv->NewStringUTF(number);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onDialCall,
- js_number);
+ js_number, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(js_number);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void dtmf_cmd_callback(char dtmf) {
+static void dtmf_cmd_callback(char dtmf, bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
CHECK_CALLBACK_ENV
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
// TBD dtmf has changed from int to char
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSendDtmf, dtmf);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSendDtmf, dtmf, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void noice_reduction_callback(bthf_nrec_t nrec) {
+static void noice_reduction_callback(bthf_nrec_t nrec, bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
CHECK_CALLBACK_ENV
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNoiceReductionEnable,
- nrec == BTHF_NREC_START);
+ nrec == BTHF_NREC_START, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void at_chld_callback(bthf_chld_type_t chld) {
+static void at_chld_callback(bthf_chld_type_t chld, bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtChld, chld);
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtChld, chld, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void at_cnum_callback() {
+static void at_cnum_callback(bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtCnum);
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtCnum, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void at_cind_callback() {
+static void at_cind_callback(bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtCind);
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtCind, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void at_cops_callback() {
+static void at_cops_callback(bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtCops);
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtCops, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void at_clcc_callback() {
+static void at_clcc_callback(bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtClcc);
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtClcc, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void unknown_at_callback(char *at_string) {
+static void unknown_at_callback(char *at_string, bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
CHECK_CALLBACK_ENV
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
jstring js_at_string = sCallbackEnv->NewStringUTF(at_string);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onUnknownAt,
- js_at_string);
+ js_at_string, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(js_at_string);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void key_pressed_callback() {
+static void key_pressed_callback(bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onKeyPressed);
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onKeyPressed, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
static bthf_callbacks_t sBluetoothHfpCallbacks = {
@@ -226,20 +380,20 @@
method_onConnectionStateChanged =
env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V");
method_onAudioStateChanged = env->GetMethodID(clazz, "onAudioStateChanged", "(I[B)V");
- method_onVrStateChanged = env->GetMethodID(clazz, "onVrStateChanged", "(I)V");
- method_onAnswerCall = env->GetMethodID(clazz, "onAnswerCall", "()V");
- method_onHangupCall = env->GetMethodID(clazz, "onHangupCall", "()V");
- method_onVolumeChanged = env->GetMethodID(clazz, "onVolumeChanged", "(II)V");
- method_onDialCall = env->GetMethodID(clazz, "onDialCall", "(Ljava/lang/String;)V");
- method_onSendDtmf = env->GetMethodID(clazz, "onSendDtmf", "(I)V");
- method_onNoiceReductionEnable = env->GetMethodID(clazz, "onNoiceReductionEnable", "(Z)V");
- method_onAtChld = env->GetMethodID(clazz, "onAtChld", "(I)V");
- method_onAtCnum = env->GetMethodID(clazz, "onAtCnum", "()V");
- method_onAtCind = env->GetMethodID(clazz, "onAtCind", "()V");
- method_onAtCops = env->GetMethodID(clazz, "onAtCops", "()V");
- method_onAtClcc = env->GetMethodID(clazz, "onAtClcc", "()V");
- method_onUnknownAt = env->GetMethodID(clazz, "onUnknownAt", "(Ljava/lang/String;)V");
- method_onKeyPressed = env->GetMethodID(clazz, "onKeyPressed", "()V");
+ method_onVrStateChanged = env->GetMethodID(clazz, "onVrStateChanged", "(I[B)V");
+ method_onAnswerCall = env->GetMethodID(clazz, "onAnswerCall", "([B)V");
+ method_onHangupCall = env->GetMethodID(clazz, "onHangupCall", "([B)V");
+ method_onVolumeChanged = env->GetMethodID(clazz, "onVolumeChanged", "(II[B)V");
+ method_onDialCall = env->GetMethodID(clazz, "onDialCall", "(Ljava/lang/String;[B)V");
+ method_onSendDtmf = env->GetMethodID(clazz, "onSendDtmf", "(I[B)V");
+ method_onNoiceReductionEnable = env->GetMethodID(clazz, "onNoiceReductionEnable", "(Z[B)V");
+ method_onAtChld = env->GetMethodID(clazz, "onAtChld", "(I[B)V");
+ method_onAtCnum = env->GetMethodID(clazz, "onAtCnum", "([B)V");
+ method_onAtCind = env->GetMethodID(clazz, "onAtCind", "([B)V");
+ method_onAtCops = env->GetMethodID(clazz, "onAtCops", "([B)V");
+ method_onAtClcc = env->GetMethodID(clazz, "onAtClcc", "([B)V");
+ method_onUnknownAt = env->GetMethodID(clazz, "onUnknownAt", "(Ljava/lang/String;[B)V");
+ method_onKeyPressed = env->GetMethodID(clazz, "onKeyPressed", "([B)V");
/*
if ( (btInf = getBluetoothInterface()) == NULL) {
@@ -265,7 +419,7 @@
ALOGI("%s: succeeds", __FUNCTION__);
}
-static void initializeNative(JNIEnv *env, jobject object) {
+static void initializeNative(JNIEnv *env, jobject object, jint max_hf_clients) {
const bt_interface_t* btInf;
bt_status_t status;
@@ -292,7 +446,8 @@
return;
}
- if ( (status = sBluetoothHfpInterface->init(&sBluetoothHfpCallbacks)) != BT_STATUS_SUCCESS) {
+ if ( (status = sBluetoothHfpInterface->init(&sBluetoothHfpCallbacks,
+ max_hf_clients)) != BT_STATUS_SUCCESS) {
ALOGE("Failed to initialize Bluetooth HFP, status: %d", status);
sBluetoothHfpInterface = NULL;
return;
@@ -402,34 +557,61 @@
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
-static jboolean startVoiceRecognitionNative(JNIEnv *env, jobject object) {
+static jboolean startVoiceRecognitionNative(JNIEnv *env, jobject object, jbyteArray address) {
+ jbyte *addr;
bt_status_t status;
if (!sBluetoothHfpInterface) return JNI_FALSE;
- if ( (status = sBluetoothHfpInterface->start_voice_recognition()) != BT_STATUS_SUCCESS) {
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ( (status = sBluetoothHfpInterface->start_voice_recognition((bt_bdaddr_t *) addr))
+ != BT_STATUS_SUCCESS) {
ALOGE("Failed to start voice recognition, status: %d", status);
}
+ env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
-static jboolean stopVoiceRecognitionNative(JNIEnv *env, jobject object) {
+static jboolean stopVoiceRecognitionNative(JNIEnv *env, jobject object, jbyteArray address) {
+ jbyte *addr;
bt_status_t status;
if (!sBluetoothHfpInterface) return JNI_FALSE;
- if ( (status = sBluetoothHfpInterface->stop_voice_recognition()) != BT_STATUS_SUCCESS) {
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ( (status = sBluetoothHfpInterface->stop_voice_recognition((bt_bdaddr_t *) addr))
+ != BT_STATUS_SUCCESS) {
ALOGE("Failed to stop voice recognition, status: %d", status);
}
+ env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
-static jboolean setVolumeNative(JNIEnv *env, jobject object, jint volume_type, jint volume) {
+static jboolean setVolumeNative(JNIEnv *env, jobject object, jint volume_type,
+ jint volume, jbyteArray address) {
+ jbyte *addr;
bt_status_t status;
if (!sBluetoothHfpInterface) return JNI_FALSE;
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
if ( (status = sBluetoothHfpInterface->volume_control((bthf_volume_type_t) volume_type,
- volume)) != BT_STATUS_SUCCESS) {
+ volume, (bt_bdaddr_t *) addr)) != BT_STATUS_SUCCESS) {
ALOGE("FAILED to control volume, status: %d", status);
}
+ env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
@@ -447,76 +629,123 @@
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
-static jboolean copsResponseNative(JNIEnv *env, jobject object, jstring operator_str) {
+static jboolean copsResponseNative(JNIEnv *env, jobject object, jstring operator_str,
+ jbyteArray address) {
+ jbyte *addr;
bt_status_t status;
const char *operator_name;
if (!sBluetoothHfpInterface) return JNI_FALSE;
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
operator_name = env->GetStringUTFChars(operator_str, NULL);
- if ( (status = sBluetoothHfpInterface->cops_response(operator_name)) != BT_STATUS_SUCCESS) {
+ if ( (status = sBluetoothHfpInterface->cops_response(operator_name,(bt_bdaddr_t *) addr))
+ != BT_STATUS_SUCCESS) {
ALOGE("Failed sending cops response, status: %d", status);
}
+ env->ReleaseByteArrayElements(address, addr, 0);
env->ReleaseStringUTFChars(operator_str, operator_name);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
static jboolean cindResponseNative(JNIEnv *env, jobject object,
jint service, jint num_active, jint num_held, jint call_state,
- jint signal, jint roam, jint battery_charge) {
+ jint signal, jint roam, jint battery_charge, jbyteArray address) {
+ jbyte *addr;
bt_status_t status;
+
+ ALOGI("%s: sBluetoothHfpInterface: %p", __FUNCTION__, sBluetoothHfpInterface);
if (!sBluetoothHfpInterface) return JNI_FALSE;
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
if ( (status = sBluetoothHfpInterface->cind_response(service, num_active, num_held,
(bthf_call_state_t) call_state,
- signal, roam, battery_charge)) != BT_STATUS_SUCCESS) {
+ signal, roam, battery_charge, (bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) {
ALOGE("Failed cind_response, status: %d", status);
}
+ env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
-static jboolean atResponseStringNative(JNIEnv *env, jobject object, jstring response_str) {
+static jboolean atResponseStringNative(JNIEnv *env, jobject object, jstring response_str,
+ jbyteArray address) {
+ jbyte *addr;
bt_status_t status;
const char *response;
if (!sBluetoothHfpInterface) return JNI_FALSE;
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
response = env->GetStringUTFChars(response_str, NULL);
- if ( (status = sBluetoothHfpInterface->formatted_at_response(response)) != BT_STATUS_SUCCESS) {
+ if ( (status = sBluetoothHfpInterface->formatted_at_response(response,
+ (bt_bdaddr_t *)addr))!= BT_STATUS_SUCCESS) {
ALOGE("Failed formatted AT response, status: %d", status);
}
+ env->ReleaseByteArrayElements(address, addr, 0);
env->ReleaseStringUTFChars(response_str, response);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
-static jboolean atResponseCodeNative(JNIEnv *env, jobject object, jint response_code, jint cmee_code) {
+static jboolean atResponseCodeNative(JNIEnv *env, jobject object, jint response_code,
+ jint cmee_code, jbyteArray address) {
+ jbyte *addr;
bt_status_t status;
if (!sBluetoothHfpInterface) return JNI_FALSE;
- if ( (status = sBluetoothHfpInterface->at_response((bthf_at_response_t) response_code, cmee_code)) !=
- BT_STATUS_SUCCESS) {
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ( (status = sBluetoothHfpInterface->at_response((bthf_at_response_t) response_code,
+ cmee_code, (bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) {
ALOGE("Failed AT response, status: %d", status);
}
+ env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
static jboolean clccResponseNative(JNIEnv *env, jobject object, jint index, jint dir,
jint callStatus, jint mode, jboolean mpty, jstring number_str,
- jint type) {
+ jint type, jbyteArray address) {
+ jbyte *addr;
bt_status_t status;
const char *number = NULL;
if (!sBluetoothHfpInterface) return JNI_FALSE;
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
if (number_str)
number = env->GetStringUTFChars(number_str, NULL);
if ( (status = sBluetoothHfpInterface->clcc_response(index, (bthf_call_direction_t) dir,
- (bthf_call_state_t) callStatus, (bthf_call_mode_t) mode,
- mpty ? BTHF_CALL_MPTY_TYPE_MULTI : BTHF_CALL_MPTY_TYPE_SINGLE,
- number, (bthf_call_addrtype_t) type)) != BT_STATUS_SUCCESS) {
+ (bthf_call_state_t) callStatus, (bthf_call_mode_t) mode,
+ mpty ? BTHF_CALL_MPTY_TYPE_MULTI : BTHF_CALL_MPTY_TYPE_SINGLE,
+ number, (bthf_call_addrtype_t) type, (bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) {
ALOGE("Failed sending CLCC response, status: %d", status);
}
+ env->ReleaseByteArrayElements(address, addr, 0);
if (number)
env->ReleaseStringUTFChars(number_str, number);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
@@ -541,21 +770,21 @@
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void *) classInitNative},
- {"initializeNative", "()V", (void *) initializeNative},
+ {"initializeNative", "(I)V", (void *) initializeNative},
{"cleanupNative", "()V", (void *) cleanupNative},
{"connectHfpNative", "([B)Z", (void *) connectHfpNative},
{"disconnectHfpNative", "([B)Z", (void *) disconnectHfpNative},
{"connectAudioNative", "([B)Z", (void *) connectAudioNative},
{"disconnectAudioNative", "([B)Z", (void *) disconnectAudioNative},
- {"startVoiceRecognitionNative", "()Z", (void *) startVoiceRecognitionNative},
- {"stopVoiceRecognitionNative", "()Z", (void *) stopVoiceRecognitionNative},
- {"setVolumeNative", "(II)Z", (void *) setVolumeNative},
+ {"startVoiceRecognitionNative", "([B)Z", (void *) startVoiceRecognitionNative},
+ {"stopVoiceRecognitionNative", "([B)Z", (void *) stopVoiceRecognitionNative},
+ {"setVolumeNative", "(II[B)Z", (void *) setVolumeNative},
{"notifyDeviceStatusNative", "(IIII)Z", (void *) notifyDeviceStatusNative},
- {"copsResponseNative", "(Ljava/lang/String;)Z", (void *) copsResponseNative},
- {"cindResponseNative", "(IIIIIII)Z", (void *) cindResponseNative},
- {"atResponseStringNative", "(Ljava/lang/String;)Z", (void *) atResponseStringNative},
- {"atResponseCodeNative", "(II)Z", (void *)atResponseCodeNative},
- {"clccResponseNative", "(IIIIZLjava/lang/String;I)Z", (void *) clccResponseNative},
+ {"copsResponseNative", "(Ljava/lang/String;[B)Z", (void *) copsResponseNative},
+ {"cindResponseNative", "(IIIIIII[B)Z", (void *) cindResponseNative},
+ {"atResponseStringNative", "(Ljava/lang/String;[B)Z", (void *) atResponseStringNative},
+ {"atResponseCodeNative", "(II[B)Z", (void *)atResponseCodeNative},
+ {"clccResponseNative", "(IIIIZLjava/lang/String;I[B)Z", (void *) clccResponseNative},
{"phoneStateChangeNative", "(IIILjava/lang/String;I)Z", (void *) phoneStateChangeNative},
};
diff --git a/jni/com_android_bluetooth_hfpclient.cpp b/jni/com_android_bluetooth_hfpclient.cpp
new file mode 100644
index 0000000..030681f
--- /dev/null
+++ b/jni/com_android_bluetooth_hfpclient.cpp
@@ -0,0 +1,614 @@
+/*
+ * Copyright (c) 2014 The Android Open Source Project
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BluetoothHeadsetClientServiceJni"
+#define LOG_NDEBUG 0
+
+#include "com_android_bluetooth.h"
+#include "hardware/bt_hf_client.h"
+#include "utils/Log.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#define CHECK_CALLBACK_ENV \
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);\
+ return; \
+ }
+
+namespace android {
+
+static bthf_client_interface_t *sBluetoothHfpClientInterface = NULL;
+static jobject mCallbacksObj = NULL;
+static JNIEnv *sCallbackEnv = NULL;
+
+static jmethodID method_onConnectionStateChanged;
+static jmethodID method_onAudioStateChanged;
+static jmethodID method_onVrStateChanged;
+static jmethodID method_onNetworkState;
+static jmethodID method_onNetworkRoaming;
+static jmethodID method_onNetworkSignal;
+static jmethodID method_onBatteryLevel;
+static jmethodID method_onCurrentOperator;
+static jmethodID method_onCall;
+static jmethodID method_onCallSetup;
+static jmethodID method_onCallHeld;
+static jmethodID method_onRespAndHold;
+static jmethodID method_onClip;
+static jmethodID method_onCallWaiting;
+static jmethodID method_onCurrentCalls;
+static jmethodID method_onVolumeChange;
+static jmethodID method_onCmdResult;
+static jmethodID method_onSubscriberInfo;
+static jmethodID method_onInBandRing;
+static jmethodID method_onLastVoiceTagNumber;
+static jmethodID method_onRingIndication;
+
+static bool checkCallbackThread() {
+ // Always fetch the latest callbackEnv from AdapterService.
+ // Caching this could cause this sCallbackEnv to go out-of-sync
+ // with the AdapterService's ENV if an ASSOCIATE/DISASSOCIATE event
+ // is received
+ sCallbackEnv = getCallbackEnv();
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ if (sCallbackEnv != env || sCallbackEnv == NULL) return false;
+ return true;
+}
+
+static void connection_state_cb(bthf_client_connection_state_t state, unsigned int peer_feat, unsigned int chld_feat, bt_bdaddr_t *bd_addr) {
+ jbyteArray addr;
+
+ CHECK_CALLBACK_ENV
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for connection state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, (jint) state, (jint) peer_feat, (jint) chld_feat, addr);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void audio_state_cb(bthf_client_audio_state_t state, bt_bdaddr_t *bd_addr) {
+ jbyteArray addr;
+
+ CHECK_CALLBACK_ENV
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for audio state");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged, (jint) state, addr);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void vr_cmd_cb(bthf_client_vr_state_t state) {
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVrStateChanged, (jint) state);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void network_state_cb (bthf_client_network_state_t state) {
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNetworkState, (jint) state);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void network_roaming_cb (bthf_client_service_type_t type) {
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNetworkRoaming, (jint) type);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void network_signal_cb (int signal) {
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNetworkSignal, (jint) signal);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void battery_level_cb (int level) {
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onBatteryLevel, (jint) level);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void current_operator_cb (const char *name) {
+ jstring js_name;
+
+ CHECK_CALLBACK_ENV
+
+ js_name = sCallbackEnv->NewStringUTF(name);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCurrentOperator, js_name);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(js_name);
+}
+
+static void call_cb (bthf_client_call_t call) {
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCall, (jint) call);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void callsetup_cb (bthf_client_callsetup_t callsetup) {
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCallSetup, (jint) callsetup);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void callheld_cb (bthf_client_callheld_t callheld) {
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCallHeld, (jint) callheld);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void resp_and_hold_cb (bthf_client_resp_and_hold_t resp_and_hold) {
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onRespAndHold, (jint) resp_and_hold);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void clip_cb (const char *number) {
+ jstring js_number;
+
+ CHECK_CALLBACK_ENV
+
+ js_number = sCallbackEnv->NewStringUTF(number);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClip, js_number);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(js_number);
+}
+
+static void call_waiting_cb (const char *number) {
+ jstring js_number;
+
+ CHECK_CALLBACK_ENV
+
+ js_number = sCallbackEnv->NewStringUTF(number);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCallWaiting, js_number);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(js_number);
+}
+
+static void current_calls_cb (int index, bthf_client_call_direction_t dir,
+ bthf_client_call_state_t state,
+ bthf_client_call_mpty_type_t mpty,
+ const char *number) {
+ jstring js_number;
+
+ CHECK_CALLBACK_ENV
+
+ js_number = sCallbackEnv->NewStringUTF(number);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCurrentCalls, index, dir, state, mpty, js_number);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(js_number);
+}
+
+static void volume_change_cb (bthf_client_volume_type_t type, int volume) {
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVolumeChange, (jint) type, (jint) volume);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void cmd_complete_cb (bthf_client_cmd_complete_t type, int cme) {
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCmdResult, (jint) type, (jint) cme);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void subscriber_info_cb (const char *name, bthf_client_subscriber_service_type_t type) {
+ jstring js_name;
+
+ CHECK_CALLBACK_ENV
+
+ js_name = sCallbackEnv->NewStringUTF(name);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSubscriberInfo, js_name, (jint) type);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(js_name);
+}
+
+static void in_band_ring_cb (bthf_client_in_band_ring_state_t in_band) {
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onInBandRing, (jint) in_band);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void last_voice_tag_number_cb (const char *number) {
+ jstring js_number;
+
+ CHECK_CALLBACK_ENV
+
+ js_number = sCallbackEnv->NewStringUTF(number);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onLastVoiceTagNumber, js_number);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ sCallbackEnv->DeleteLocalRef(js_number);
+}
+
+static void ring_indication_cb () {
+ CHECK_CALLBACK_ENV
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onRingIndication);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static bthf_client_callbacks_t sBluetoothHfpClientCallbacks = {
+ sizeof(sBluetoothHfpClientCallbacks),
+ connection_state_cb,
+ audio_state_cb,
+ vr_cmd_cb,
+ network_state_cb,
+ network_roaming_cb,
+ network_signal_cb,
+ battery_level_cb,
+ current_operator_cb,
+ call_cb,
+ callsetup_cb,
+ callheld_cb,
+ resp_and_hold_cb,
+ clip_cb,
+ call_waiting_cb,
+ current_calls_cb,
+ volume_change_cb,
+ cmd_complete_cb,
+ subscriber_info_cb,
+ in_band_ring_cb,
+ last_voice_tag_number_cb,
+ ring_indication_cb,
+};
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+ method_onConnectionStateChanged = env->GetMethodID(clazz, "onConnectionStateChanged", "(III[B)V");
+ method_onAudioStateChanged = env->GetMethodID(clazz, "onAudioStateChanged", "(I[B)V");
+ method_onVrStateChanged = env->GetMethodID(clazz, "onVrStateChanged", "(I)V");
+ method_onNetworkState = env->GetMethodID(clazz, "onNetworkState", "(I)V");
+ method_onNetworkRoaming = env->GetMethodID(clazz, "onNetworkRoaming", "(I)V");
+ method_onNetworkSignal = env->GetMethodID(clazz, "onNetworkSignal", "(I)V");
+ method_onBatteryLevel = env->GetMethodID(clazz, "onBatteryLevel", "(I)V");
+ method_onCurrentOperator = env->GetMethodID(clazz, "onCurrentOperator", "(Ljava/lang/String;)V");
+ method_onCall = env->GetMethodID(clazz, "onCall", "(I)V");
+ method_onCallSetup = env->GetMethodID(clazz, "onCallSetup", "(I)V");
+ method_onCallHeld = env->GetMethodID(clazz, "onCallHeld", "(I)V");
+ method_onRespAndHold = env->GetMethodID(clazz, "onRespAndHold", "(I)V");
+ method_onClip = env->GetMethodID(clazz, "onClip", "(Ljava/lang/String;)V");
+ method_onCallWaiting = env->GetMethodID(clazz, "onCallWaiting", "(Ljava/lang/String;)V");
+ method_onCurrentCalls = env->GetMethodID(clazz, "onCurrentCalls", "(IIIILjava/lang/String;)V");
+ method_onVolumeChange = env->GetMethodID(clazz, "onVolumeChange", "(II)V");
+ method_onCmdResult = env->GetMethodID(clazz, "onCmdResult", "(II)V");
+ method_onSubscriberInfo = env->GetMethodID(clazz, "onSubscriberInfo", "(Ljava/lang/String;I)V");
+ method_onInBandRing = env->GetMethodID(clazz, "onInBandRing", "(I)V");
+ method_onLastVoiceTagNumber = env->GetMethodID(clazz, "onLastVoiceTagNumber",
+ "(Ljava/lang/String;)V");
+ method_onRingIndication = env->GetMethodID(clazz, "onRingIndication","()V");
+
+ ALOGI("%s succeeds", __FUNCTION__);
+}
+
+static void initializeNative(JNIEnv *env, jobject object) {
+ const bt_interface_t* btInf;
+ bt_status_t status;
+
+ btInf = getBluetoothInterface();
+ if (btInf == NULL) {
+ ALOGE("Bluetooth module is not loaded");
+ return;
+ }
+
+ if (sBluetoothHfpClientInterface != NULL) {
+ ALOGW("Cleaning up Bluetooth HFP Client Interface before initializing");
+ sBluetoothHfpClientInterface->cleanup();
+ sBluetoothHfpClientInterface = NULL;
+ }
+
+ if (mCallbacksObj != NULL) {
+ ALOGW("Cleaning up Bluetooth HFP Client callback object");
+ env->DeleteGlobalRef(mCallbacksObj);
+ mCallbacksObj = NULL;
+ }
+
+ sBluetoothHfpClientInterface = (bthf_client_interface_t *)
+ btInf->get_profile_interface(BT_PROFILE_HANDSFREE_CLIENT_ID);
+ if (sBluetoothHfpClientInterface == NULL) {
+ ALOGE("Failed to get Bluetooth HFP Client Interface");
+ return;
+ }
+
+ status = sBluetoothHfpClientInterface->init(&sBluetoothHfpClientCallbacks);
+ if (status != BT_STATUS_SUCCESS) {
+ ALOGE("Failed to initialize Bluetooth HFP Client, status: %d", status);
+ sBluetoothHfpClientInterface = 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) {
+ ALOGE("Bluetooth module is not loaded");
+ return;
+ }
+
+ if (sBluetoothHfpClientInterface != NULL) {
+ ALOGW("Cleaning up Bluetooth HFP Client Interface...");
+ sBluetoothHfpClientInterface->cleanup();
+ sBluetoothHfpClientInterface = NULL;
+ }
+
+ if (mCallbacksObj != NULL) {
+ ALOGW("Cleaning up Bluetooth HFP Client callback object");
+ env->DeleteGlobalRef(mCallbacksObj);
+ mCallbacksObj = NULL;
+ }
+}
+
+static jboolean connectNative(JNIEnv *env, jobject object, jbyteArray address) {
+ jbyte *addr;
+ bt_status_t status;
+
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ((status = sBluetoothHfpClientInterface->connect((bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed AG connection, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean disconnectNative(JNIEnv *env, jobject object, jbyteArray address) {
+ jbyte *addr;
+ bt_status_t status;
+
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ( (status = sBluetoothHfpClientInterface->disconnect((bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed AG disconnection, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean connectAudioNative(JNIEnv *env, jobject object, jbyteArray address) {
+ jbyte *addr;
+ bt_status_t status;
+
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ( (status = sBluetoothHfpClientInterface->connect_audio((bt_bdaddr_t *)addr)) !=
+ BT_STATUS_SUCCESS) {
+ ALOGE("Failed AG audio connection, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean disconnectAudioNative(JNIEnv *env, jobject object, jbyteArray address) {
+ jbyte *addr;
+ bt_status_t status;
+
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ( (status = sBluetoothHfpClientInterface->disconnect_audio((bt_bdaddr_t *) addr)) !=
+ BT_STATUS_SUCCESS) {
+ ALOGE("Failed AG audio disconnection, status: %d", status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean startVoiceRecognitionNative(JNIEnv *env, jobject object) {
+ bt_status_t status;
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ if ( (status = sBluetoothHfpClientInterface->start_voice_recognition()) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed to start voice recognition, status: %d", status);
+ }
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean stopVoiceRecognitionNative(JNIEnv *env, jobject object) {
+ bt_status_t status;
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ if ( (status = sBluetoothHfpClientInterface->stop_voice_recognition()) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed to stop voice recognition, status: %d", status);
+ }
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean setVolumeNative(JNIEnv *env, jobject object, jint volume_type, jint volume) {
+ bt_status_t status;
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ if ( (status = sBluetoothHfpClientInterface->volume_control((bthf_client_volume_type_t) volume_type,
+ volume)) != BT_STATUS_SUCCESS) {
+ ALOGE("FAILED to control volume, status: %d", status);
+ }
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean dialNative(JNIEnv *env, jobject object, jstring number_str) {
+ bt_status_t status;
+ const char *number;
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ number = env->GetStringUTFChars(number_str, NULL);
+
+ if ( (status = sBluetoothHfpClientInterface->dial(number)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed to dial, status: %d", status);
+ }
+ env->ReleaseStringUTFChars(number_str, number);
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean dialMemoryNative(JNIEnv *env, jobject object, jint location) {
+ bt_status_t status;
+
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ if ( (status = sBluetoothHfpClientInterface->dial_memory((int)location)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed to dial from memory, status: %d", status);
+ }
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean handleCallActionNative(JNIEnv *env, jobject object, jint action, jint index) {
+ bt_status_t status;
+
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ if ( (status = sBluetoothHfpClientInterface->handle_call_action((bthf_client_call_action_t)action, (int)index)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed to enter private mode, status: %d", status);
+ }
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean queryCurrentCallsNative(JNIEnv *env, jobject object) {
+ bt_status_t status;
+
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ if ( (status = sBluetoothHfpClientInterface->query_current_calls()) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed to query current calls, status: %d", status);
+ }
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean queryCurrentOperatorNameNative(JNIEnv *env, jobject object) {
+ bt_status_t status;
+
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ if ( (status = sBluetoothHfpClientInterface->query_current_operator_name()) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed to query current operator name, status: %d", status);
+ }
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean retrieveSubscriberInfoNative(JNIEnv *env, jobject object) {
+ bt_status_t status;
+
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ if ( (status = sBluetoothHfpClientInterface->retrieve_subscriber_info()) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed to retrieve subscriber info, status: %d", status);
+ }
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean sendDtmfNative(JNIEnv *env, jobject object, jbyte code) {
+ bt_status_t status;
+
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ if ( (status = sBluetoothHfpClientInterface->send_dtmf((char)code)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed to send DTMF, status: %d", status);
+ }
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean requestLastVoiceTagNumberNative(JNIEnv *env, jobject object) {
+ bt_status_t status;
+
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ if ( (status = sBluetoothHfpClientInterface->request_last_voice_tag_number()) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed to request last Voice Tag number, status: %d", status);
+ }
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean sendATCmdNative(JNIEnv *env, jobject object, jint cmd,
+ jint val1, jint val2, jstring arg_str) {
+ bt_status_t status;
+ const char *arg;
+
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ arg = env->GetStringUTFChars(arg_str, NULL);
+
+ if ((status = sBluetoothHfpClientInterface->send_at_cmd(cmd,val1,val2,arg)) !=
+ BT_STATUS_SUCCESS) {
+ ALOGE("Failed to send cmd, status: %d", status);
+ }
+
+ env->ReleaseStringUTFChars(arg_str, arg);
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static JNINativeMethod sMethods[] = {
+ {"classInitNative", "()V", (void *) classInitNative},
+ {"initializeNative", "()V", (void *) initializeNative},
+ {"cleanupNative", "()V", (void *) cleanupNative},
+ {"connectNative", "([B)Z", (void *) connectNative},
+ {"disconnectNative", "([B)Z", (void *) disconnectNative},
+ {"connectAudioNative", "([B)Z", (void *) connectAudioNative},
+ {"disconnectAudioNative", "([B)Z", (void *) disconnectAudioNative},
+ {"startVoiceRecognitionNative", "()Z", (void *) startVoiceRecognitionNative},
+ {"stopVoiceRecognitionNative", "()Z", (void *) stopVoiceRecognitionNative},
+ {"setVolumeNative", "(II)Z", (void *) setVolumeNative},
+ {"dialNative", "(Ljava/lang/String;)Z", (void *) dialNative},
+ {"dialMemoryNative", "(I)Z", (void *) dialMemoryNative},
+ {"handleCallActionNative", "(II)Z", (void *) handleCallActionNative},
+ {"queryCurrentCallsNative", "()Z", (void *) queryCurrentCallsNative},
+ {"queryCurrentOperatorNameNative", "()Z", (void *) queryCurrentOperatorNameNative},
+ {"retrieveSubscriberInfoNative", "()Z", (void *) retrieveSubscriberInfoNative},
+ {"sendDtmfNative", "(B)Z", (void *) sendDtmfNative},
+ {"requestLastVoiceTagNumberNative", "()Z",
+ (void *) requestLastVoiceTagNumberNative},
+ {"sendATCmdNative", "(IIILjava/lang/String;)Z", (void *) sendATCmdNative},
+};
+
+int register_com_android_bluetooth_hfpclient(JNIEnv* env)
+{
+ return jniRegisterNativeMethods(env, "com/android/bluetooth/hfpclient/HeadsetClientStateMachine",
+ sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 82222ba..ed3314a 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -30,7 +30,7 @@
<string name="bt_enable_title" msgid="8657832550503456572"></string>
<string name="bt_enable_line1" msgid="7203551583048149">"የብሉቱዝ አገልግሎት ለመጠቀም፣ መጀመሪያ ብሉቱዝን ያብሩ።"</string>
<string name="bt_enable_line2" msgid="4341936569415937994">"ብሉቱዝ አሁን ይብራ?"</string>
- <string name="bt_enable_cancel" msgid="1988832367505151727">"ሰርዝ"</string>
+ <string name="bt_enable_cancel" msgid="1988832367505151727">"ይቅር"</string>
<string name="bt_enable_ok" msgid="3432462749994538265">"አብራ"</string>
<string name="incoming_file_confirm_title" msgid="8139874248612182627">"ፋይልሰደዳ"</string>
<string name="incoming_file_confirm_content" msgid="6673812334377911289">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" <xliff:g id="FILE">%2$s</xliff:g>(<xliff:g id="SIZE">%3$s</xliff:g>) ሊልኩልዎ ይፈልጋሉ፡ \n\n ፋይል ይቀበሉ?"</string>
@@ -110,8 +110,8 @@
<string name="outbound_noti_title" msgid="8051906709452260849">"ብሉቱዝ ማጋሪያ፡ የተላኩ ፋይሎች"</string>
<string name="inbound_noti_title" msgid="4143352641953027595">"ብሉቱዝ ማጋሪያ፡ የደረሱ ፋይሎች"</string>
<string name="noti_caption" msgid="7508708288885707365">"<xliff:g id="SUCCESSFUL_NUMBER_0">%1$s</xliff:g> የተሳካ፣ <xliff:g id="UNSUCCESSFUL_NUMBER">%2$s</xliff:g> ያልተሳካ።"</string>
- <string name="transfer_menu_clear_all" msgid="790017462957873132">"ዝርዝር አጥራ"</string>
+ <string name="transfer_menu_clear_all" msgid="790017462957873132">"ዝርዝር አጽዳ"</string>
<string name="transfer_menu_open" msgid="3368984869083107200">"ክፈት"</string>
- <string name="transfer_menu_clear" msgid="5854038118831427492">"ከዝርዝር አጥራ"</string>
- <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"አጥራ"</string>
+ <string name="transfer_menu_clear" msgid="5854038118831427492">"ከዝርዝር አጽዳ"</string>
+ <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"አጽዳ"</string>
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index fe1194f..932f3f1 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -43,11 +43,11 @@
<string name="incoming_file_toast_msg" msgid="1733710749992901811">"Ein anderes Gerät versucht, eine Datei zu übertragen. Bestätigen Sie, dass Sie diese Datei empfangen möchten."</string>
<string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-Freigabe: <xliff:g id="FILE">%1$s</xliff:g> wird empfangen"</string>
<string name="notification_received" msgid="3324588019186687985">"Bluetooth-Freigabe: <xliff:g id="FILE">%1$s</xliff:g> wurde empfangen"</string>
- <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-Freigabe: Datei <xliff:g id="FILE">%1$s</xliff:g> wurde nicht empfangen."</string>
+ <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-Freigabe: <xliff:g id="FILE">%1$s</xliff:g> wurde nicht empfangen"</string>
<string name="notification_sending" msgid="3035748958534983833">"Bluetooth-Freigabe: <xliff:g id="FILE">%1$s</xliff:g> wird gesendet"</string>
<string name="notification_sent" msgid="9218710861333027778">"Bluetooth-Freigabe: <xliff:g id="FILE">%1$s</xliff:g> gesendet"</string>
<string name="notification_sent_complete" msgid="302943281067557969">"Zu 100 % abgeschlossen"</string>
- <string name="notification_sent_fail" msgid="6696082233774569445">"Bluetooth-Freigabe: Datei <xliff:g id="FILE">%1$s</xliff:g> wurde nicht gesendet"</string>
+ <string name="notification_sent_fail" msgid="6696082233774569445">"Bluetooth-Freigabe: <xliff:g id="FILE">%1$s</xliff:g> wurde nicht gesendet"</string>
<string name="download_title" msgid="3353228219772092586">"Dateiübertragung"</string>
<string name="download_line1" msgid="4926604799202134144">"Von: \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="download_line2" msgid="5876973543019417712">"Datei: <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -81,7 +81,7 @@
<string name="bt_toast_1" msgid="972182708034353383">"Die Datei wird empfangen. Überprüfen Sie den Fortschritt in der Benachrichtigungskonsole."</string>
<string name="bt_toast_2" msgid="8602553334099066582">"Die Datei kann nicht empfangen werden."</string>
<string name="bt_toast_3" msgid="6707884165086862518">"Der Empfang der Datei von \"<xliff:g id="SENDER">%1$s</xliff:g>\" wurde angehalten."</string>
- <string name="bt_toast_4" msgid="4678812947604395649">"Die Datei wird an \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" gesendet."</string>
+ <string name="bt_toast_4" msgid="4678812947604395649">"Datei wird an \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" gesendet..."</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> Dateien werden an \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" gesendet."</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Die Übertragung der Datei an \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" wurde abgebrochen"</string>
<string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Der USB-Speicher verfügt nicht über genügend Speicherplatz, um die Datei von \"<xliff:g id="SENDER">%1$s</xliff:g>\" zu speichern."</string>
@@ -107,7 +107,7 @@
<string name="outbound_history_title" msgid="4279418703178140526">"Ausgehende Übertragungen"</string>
<string name="no_transfers" msgid="3482965619151865672">"Übertragungsverlauf ist leer."</string>
<string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"Alle Elemente werden aus der Liste gelöscht."</string>
- <string name="outbound_noti_title" msgid="8051906709452260849">"Bluetooth-Freigabe: gesendete Dateien"</string>
+ <string name="outbound_noti_title" msgid="8051906709452260849">"Bluetooth-Freigabe: Gesendete Dateien"</string>
<string name="inbound_noti_title" msgid="4143352641953027595">"Bluetooth-Freigabe: Empfangene Dateien"</string>
<string name="noti_caption" msgid="7508708288885707365">"<xliff:g id="SUCCESSFUL_NUMBER_0">%1$s</xliff:g> erfolgreich, <xliff:g id="UNSUCCESSFUL_NUMBER">%2$s</xliff:g> fehlgeschlagen"</string>
<string name="transfer_menu_clear_all" msgid="790017462957873132">"Liste löschen"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index eec8148..e5d50a3 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -51,7 +51,7 @@
<string name="download_title" msgid="3353228219772092586">"Transferencia de archivos"</string>
<string name="download_line1" msgid="4926604799202134144">"De: \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="download_line2" msgid="5876973543019417712">"Archivo: <xliff:g id="FILE">%1$s</xliff:g>"</string>
- <string name="download_line3" msgid="4384821622908676061">"Tamaño de archivo: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
+ <string name="download_line3" msgid="4384821622908676061">"Tamaño del archivo: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="download_line4" msgid="8535996869722666525"></string>
<string name="download_line5" msgid="3069560415845295386">"Recibiendo archivo…"</string>
<string name="download_cancel" msgid="9177305996747500768">"Detener"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 92e898a..6271c40 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -21,7 +21,7 @@
<string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"הכנס מכשיר Bluetooth לרשימה הלבנה."</string>
<string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"מאפשר לאפליקציה להכניס מכשיר Bluetooth באופן זמני לרשימה הלבנה, ובכך לאפשר למכשיר לשלוח קבצים למכשיר זה ללא אישור משתמש."</string>
<string name="permlab_handoverStatus" msgid="7316032998801933554">"קבל שידורי העברת מסירה של Bluetooth."</string>
- <string name="permdesc_handoverStatus" msgid="4752738070064786310">"מאפשר קבלת מידע סטטוס של העברת מסירה מ-Bluetooth."</string>
+ <string name="permdesc_handoverStatus" msgid="4752738070064786310">"מאפשר קבלת נתוני סטטוס של העברת מסירה מ-Bluetooth."</string>
<string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
<string name="unknown_device" msgid="9221903979877041009">"מכשיר לא ידוע"</string>
<string name="unknownNumber" msgid="4994750948072751566">"לא ידוע"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 617aae5..70bfb59 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -38,16 +38,16 @@
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"ยอมรับ"</string>
<string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ตกลง"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"มีการหมดเวลาเกิดขึ้นขณะยอมรับไฟล์ขาเข้าจาก \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"การแบ่งปันทางบลูทูธ: ไฟล์ขาเข้า"</string>
+ <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"การแชร์ทางบลูทูธ: ไฟล์ขาเข้า"</string>
<string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"คุณต้องการรับไฟล์นี้หรือไม่"</string>
<string name="incoming_file_toast_msg" msgid="1733710749992901811">"ไฟล์ขาเข้าจากอุปกรณ์อื่น ยืนยันว่าคุณต้องการรับไฟล์นี้"</string>
- <string name="notification_receiving" msgid="4674648179652543984">"การแบ่งปันทางบลูทูธ: กำลังรับ <xliff:g id="FILE">%1$s</xliff:g>"</string>
- <string name="notification_received" msgid="3324588019186687985">"การแบ่งปันทางบลูทูธ: รับ <xliff:g id="FILE">%1$s</xliff:g> แล้ว"</string>
- <string name="notification_received_fail" msgid="3619350997285714746">"การแบ่งปันทางบลูทูธ: ไม่ได้รับไฟล์ <xliff:g id="FILE">%1$s</xliff:g>"</string>
- <string name="notification_sending" msgid="3035748958534983833">"การแบ่งปันทางบลูทูธ: กำลังส่ง <xliff:g id="FILE">%1$s</xliff:g>"</string>
- <string name="notification_sent" msgid="9218710861333027778">"การแบ่งปันทางบลูทูธ: ส่ง <xliff:g id="FILE">%1$s</xliff:g> แล้ว"</string>
+ <string name="notification_receiving" msgid="4674648179652543984">"การแชร์ทางบลูทูธ: กำลังรับ <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_received" msgid="3324588019186687985">"การแชร์ทางบลูทูธ: รับ <xliff:g id="FILE">%1$s</xliff:g> แล้ว"</string>
+ <string name="notification_received_fail" msgid="3619350997285714746">"การแชร์ทางบลูทูธ: ไม่ได้รับไฟล์ <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_sending" msgid="3035748958534983833">"การแชร์ทางบลูทูธ: กำลังส่ง <xliff:g id="FILE">%1$s</xliff:g>"</string>
+ <string name="notification_sent" msgid="9218710861333027778">"การแชร์ทางบลูทูธ: ส่ง <xliff:g id="FILE">%1$s</xliff:g> แล้ว"</string>
<string name="notification_sent_complete" msgid="302943281067557969">"เสร็จสมบูรณ์ 100%"</string>
- <string name="notification_sent_fail" msgid="6696082233774569445">"การแบ่งปันทางบลูทูธ: ไฟล์ <xliff:g id="FILE">%1$s</xliff:g> ไม่ถูกส่ง"</string>
+ <string name="notification_sent_fail" msgid="6696082233774569445">"การแชร์ทางบลูทูธ: ไฟล์ <xliff:g id="FILE">%1$s</xliff:g> ไม่ถูกส่ง"</string>
<string name="download_title" msgid="3353228219772092586">"การถ่ายโอนไฟล์"</string>
<string name="download_line1" msgid="4926604799202134144">"จาก: \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="download_line2" msgid="5876973543019417712">"ไฟล์: <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -107,8 +107,8 @@
<string name="outbound_history_title" msgid="4279418703178140526">"การถ่ายโอนขาออก"</string>
<string name="no_transfers" msgid="3482965619151865672">"ประวัติการถ่ายโอนว่างเปล่า"</string>
<string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"รายการทั้งหมดจะถูกล้างจากรายการ"</string>
- <string name="outbound_noti_title" msgid="8051906709452260849">"การแบ่งปันทางบลูทูธ: ส่งไฟล์แล้ว"</string>
- <string name="inbound_noti_title" msgid="4143352641953027595">"การแบ่งปันทางบลูทูธ: รับไฟล์แล้ว"</string>
+ <string name="outbound_noti_title" msgid="8051906709452260849">"การแชร์ทางบลูทูธ: ส่งไฟล์แล้ว"</string>
+ <string name="inbound_noti_title" msgid="4143352641953027595">"การแชร์ทางบลูทูธ: รับไฟล์แล้ว"</string>
<string name="noti_caption" msgid="7508708288885707365">"สำเร็จ <xliff:g id="SUCCESSFUL_NUMBER_0">%1$s</xliff:g> รายการ, ไม่สำเร็จ <xliff:g id="UNSUCCESSFUL_NUMBER">%2$s</xliff:g> รายการ"</string>
<string name="transfer_menu_clear_all" msgid="790017462957873132">"ล้างรายการ"</string>
<string name="transfer_menu_open" msgid="3368984869083107200">"เปิด"</string>
diff --git a/res/values-th/test_strings.xml b/res/values-th/test_strings.xml
index 05b9be3..a2cb81e 100644
--- a/res/values-th/test_strings.xml
+++ b/res/values-th/test_strings.xml
@@ -2,7 +2,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="hello" msgid="1740533743008967039">"สวัสดีชาวโลก ทดสอบ"</string>
- <string name="app_name" msgid="1203877025577761792">"การแบ่งปันทางบลูทูธ"</string>
+ <string name="app_name" msgid="1203877025577761792">"การแชร์ทางบลูทูธ"</string>
<string name="insert_record" msgid="1450997173838378132">"แทรกบันทึก"</string>
<string name="update_record" msgid="2480425402384910635">"ยืนยันบันทึก"</string>
<string name="ack_record" msgid="6716152390978472184">"บันทึก Ack"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 24eeb2c..f2b4ebb 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -14,8 +14,10 @@
-->
<resources>
<bool name="profile_supported_a2dp">true</bool>
+ <bool name="profile_supported_a2dp_sink">false</bool>
<bool name="profile_supported_hdp">true</bool>
<bool name="profile_supported_hs_hfp">true</bool>
+ <bool name="profile_supported_hfpclient">false</bool>
<bool name="profile_supported_hid">true</bool>
<bool name="profile_supported_opp">true</bool>
<bool name="profile_supported_pan">true</bool>
@@ -24,4 +26,5 @@
<bool name="pbap_include_photos_in_vcard">false</bool>
<bool name="pbap_use_profile_for_owner_vcard">true</bool>
<bool name="profile_supported_map">true</bool>
+ <bool name="profile_supported_avrcp_controller">false</bool>
</resources>
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index 743513e..c06d0cd 100755
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.provider.Settings;
import android.util.Log;
+import com.android.bluetooth.avrcp.Avrcp;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.Utils;
import java.util.ArrayList;
diff --git a/src/com/android/bluetooth/a2dp/A2dpSinkService.java b/src/com/android/bluetooth/a2dp/A2dpSinkService.java
new file mode 100644
index 0000000..bd27952
--- /dev/null
+++ b/src/com/android/bluetooth/a2dp/A2dpSinkService.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.a2dp;
+
+import android.bluetooth.BluetoothAudioConfig;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothA2dpSink;
+import android.util.Log;
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.Utils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provides Bluetooth A2DP Sink profile, as a service in the Bluetooth application.
+ * @hide
+ */
+public class A2dpSinkService extends ProfileService {
+ private static final boolean DBG = false;
+ private static final String TAG = "A2dpSinkService";
+
+ private A2dpSinkStateMachine mStateMachine;
+ private static A2dpSinkService sA2dpSinkService;
+
+ protected String getName() {
+ return TAG;
+ }
+
+ protected IProfileServiceBinder initBinder() {
+ return new BluetoothA2dpSinkBinder(this);
+ }
+
+ protected boolean start() {
+ mStateMachine = A2dpSinkStateMachine.make(this, this);
+ setA2dpSinkService(this);
+ return true;
+ }
+
+ protected boolean stop() {
+ mStateMachine.doQuit();
+ return true;
+ }
+
+ protected boolean cleanup() {
+ if (mStateMachine!= null) {
+ mStateMachine.cleanup();
+ }
+ clearA2dpSinkService();
+ return true;
+ }
+
+ //API Methods
+
+ public static synchronized A2dpSinkService getA2dpSinkService(){
+ if (sA2dpSinkService != null && sA2dpSinkService.isAvailable()) {
+ if (DBG) Log.d(TAG, "getA2dpSinkService(): returning " + sA2dpSinkService);
+ return sA2dpSinkService;
+ }
+ if (DBG) {
+ if (sA2dpSinkService == null) {
+ Log.d(TAG, "getA2dpSinkService(): service is NULL");
+ } else if (!(sA2dpSinkService.isAvailable())) {
+ Log.d(TAG,"getA2dpSinkService(): service is not available");
+ }
+ }
+ return null;
+ }
+
+ private static synchronized void setA2dpSinkService(A2dpSinkService instance) {
+ if (instance != null && instance.isAvailable()) {
+ if (DBG) Log.d(TAG, "setA2dpSinkService(): set to: " + sA2dpSinkService);
+ sA2dpSinkService = instance;
+ } else {
+ if (DBG) {
+ if (sA2dpSinkService == null) {
+ Log.d(TAG, "setA2dpSinkService(): service not available");
+ } else if (!sA2dpSinkService.isAvailable()) {
+ Log.d(TAG,"setA2dpSinkService(): service is cleaning up");
+ }
+ }
+ }
+ }
+
+ private static synchronized void clearA2dpSinkService() {
+ sA2dpSinkService = null;
+ }
+
+ public boolean connect(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH ADMIN permission");
+
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState == BluetoothProfile.STATE_CONNECTED ||
+ connectionState == BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+
+ mStateMachine.sendMessage(A2dpSinkStateMachine.CONNECT, device);
+ return true;
+ }
+
+ boolean disconnect(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH ADMIN permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+
+ mStateMachine.sendMessage(A2dpSinkStateMachine.DISCONNECT, device);
+ return true;
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mStateMachine.getConnectedDevices();
+ }
+
+ List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mStateMachine.getDevicesMatchingConnectionStates(states);
+ }
+
+ int getConnectionState(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mStateMachine.getConnectionState(device);
+ }
+
+ BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mStateMachine.getAudioConfig(device);
+ }
+
+ //Binder object: Must be static class or memory leak may occur
+ private static class BluetoothA2dpSinkBinder extends IBluetoothA2dpSink.Stub
+ implements IProfileServiceBinder {
+ private A2dpSinkService mService;
+
+ private A2dpSinkService getService() {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG,"A2dp call not allowed for non-active user");
+ return null;
+ }
+
+ if (mService != null && mService.isAvailable()) {
+ return mService;
+ }
+ return null;
+ }
+
+ BluetoothA2dpSinkBinder(A2dpSinkService svc) {
+ mService = svc;
+ }
+
+ public boolean cleanup() {
+ mService = null;
+ return true;
+ }
+
+ public boolean connect(BluetoothDevice device) {
+ A2dpSinkService service = getService();
+ if (service == null) return false;
+ return service.connect(device);
+ }
+
+ public boolean disconnect(BluetoothDevice device) {
+ A2dpSinkService service = getService();
+ if (service == null) return false;
+ return service.disconnect(device);
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ A2dpSinkService service = getService();
+ if (service == null) return new ArrayList<BluetoothDevice>(0);
+ return service.getConnectedDevices();
+ }
+
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ A2dpSinkService service = getService();
+ if (service == null) return new ArrayList<BluetoothDevice>(0);
+ return service.getDevicesMatchingConnectionStates(states);
+ }
+
+ public int getConnectionState(BluetoothDevice device) {
+ A2dpSinkService service = getService();
+ if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
+ return service.getConnectionState(device);
+ }
+
+ public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
+ A2dpSinkService service = getService();
+ if (service == null) return null;
+ return service.getAudioConfig(device);
+ }
+ };
+}
diff --git a/src/com/android/bluetooth/a2dp/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpSinkStateMachine.java
new file mode 100644
index 0000000..929676f
--- /dev/null
+++ b/src/com/android/bluetooth/a2dp/A2dpSinkStateMachine.java
@@ -0,0 +1,802 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Bluetooth A2dp StateMachine
+ * (Disconnected)
+ * | ^
+ * CONNECT | | DISCONNECTED
+ * V |
+ * (Pending)
+ * | ^
+ * CONNECTED | | CONNECT
+ * V |
+ * (Connected)
+ */
+package com.android.bluetooth.a2dp;
+
+import android.bluetooth.BluetoothA2dpSink;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAudioConfig;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.IBluetooth;
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.content.Intent;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ParcelUuid;
+import android.util.Log;
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.util.IState;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Set;
+
+final class A2dpSinkStateMachine extends StateMachine {
+ private static final boolean DBG = false;
+
+ static final int CONNECT = 1;
+ static final int DISCONNECT = 2;
+ private static final int STACK_EVENT = 101;
+ private static final int CONNECT_TIMEOUT = 201;
+
+ private Disconnected mDisconnected;
+ private Pending mPending;
+ private Connected mConnected;
+
+ private A2dpSinkService mService;
+ private Context mContext;
+ private BluetoothAdapter mAdapter;
+ private final AudioManager mAudioManager;
+ private IntentBroadcastHandler mIntentBroadcastHandler;
+ private final WakeLock mWakeLock;
+
+ private static final int MSG_CONNECTION_STATE_CHANGED = 0;
+
+ // mCurrentDevice is the device connected before the state changes
+ // mTargetDevice is the device to be connected
+ // mIncomingDevice is the device connecting to us, valid only in Pending state
+ // when mIncomingDevice is not null, both mCurrentDevice
+ // and mTargetDevice are null
+ // when either mCurrentDevice or mTargetDevice is not null,
+ // mIncomingDevice is null
+ // Stable states
+ // No connection, Disconnected state
+ // both mCurrentDevice and mTargetDevice are null
+ // Connected, Connected state
+ // mCurrentDevice is not null, mTargetDevice is null
+ // Interim states
+ // Connecting to a device, Pending
+ // mCurrentDevice is null, mTargetDevice is not null
+ // Disconnecting device, Connecting to new device
+ // Pending
+ // Both mCurrentDevice and mTargetDevice are not null
+ // Disconnecting device Pending
+ // mCurrentDevice is not null, mTargetDevice is null
+ // Incoming connections Pending
+ // Both mCurrentDevice and mTargetDevice are null
+ private BluetoothDevice mCurrentDevice = null;
+ private BluetoothDevice mTargetDevice = null;
+ private BluetoothDevice mIncomingDevice = null;
+
+ private final HashMap<BluetoothDevice,BluetoothAudioConfig> mAudioConfigs
+ = new HashMap<BluetoothDevice,BluetoothAudioConfig>();
+
+ static {
+ classInitNative();
+ }
+
+ private A2dpSinkStateMachine(A2dpSinkService svc, Context context) {
+ super("A2dpSinkStateMachine");
+ mService = svc;
+ mContext = context;
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ initNative();
+
+ mDisconnected = new Disconnected();
+ mPending = new Pending();
+ mConnected = new Connected();
+
+ addState(mDisconnected);
+ addState(mPending);
+ addState(mConnected);
+
+ setInitialState(mDisconnected);
+
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BluetoothA2dpSinkService");
+
+ mIntentBroadcastHandler = new IntentBroadcastHandler();
+
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ }
+
+ static A2dpSinkStateMachine make(A2dpSinkService svc, Context context) {
+ Log.d("A2dpSinkStateMachine", "make");
+ A2dpSinkStateMachine a2dpSm = new A2dpSinkStateMachine(svc, context);
+ a2dpSm.start();
+ return a2dpSm;
+ }
+
+ public void doQuit() {
+ quitNow();
+ }
+
+ public void cleanup() {
+ cleanupNative();
+ mAudioConfigs.clear();
+ }
+
+ private class Disconnected extends State {
+ @Override
+ public void enter() {
+ log("Enter Disconnected: " + getCurrentMessage().what);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ log("Disconnected process message: " + message.what);
+ if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) {
+ loge("ERROR: current, target, or mIncomingDevice not null in Disconnected");
+ return NOT_HANDLED;
+ }
+
+ boolean retValue = HANDLED;
+ switch(message.what) {
+ case CONNECT:
+ BluetoothDevice device = (BluetoothDevice) message.obj;
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTED);
+
+ if (!connectA2dpNative(getByteAddress(device)) ) {
+ broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ break;
+ }
+
+ synchronized (A2dpSinkStateMachine.this) {
+ mTargetDevice = device;
+ transitionTo(mPending);
+ }
+ // TODO(BT) remove CONNECT_TIMEOUT when the stack
+ // sends back events consistently
+ sendMessageDelayed(CONNECT_TIMEOUT, 30000);
+ break;
+ case DISCONNECT:
+ // ignore
+ break;
+ case STACK_EVENT:
+ StackEvent event = (StackEvent) message.obj;
+ switch (event.type) {
+ case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ processConnectionEvent(event.valueInt, event.device);
+ break;
+ case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
+ processAudioConfigEvent(event.audioConfig, event.device);
+ break;
+ default:
+ loge("Unexpected stack event: " + event.type);
+ break;
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return retValue;
+ }
+
+ @Override
+ public void exit() {
+ log("Exit Disconnected: " + getCurrentMessage().what);
+ }
+
+ // in Disconnected state
+ private void processConnectionEvent(int state, BluetoothDevice device) {
+ switch (state) {
+ case CONNECTION_STATE_DISCONNECTED:
+ logw("Ignore HF DISCONNECTED event, device: " + device);
+ break;
+ case CONNECTION_STATE_CONNECTING:
+ if (okToConnect(device)){
+ logi("Incoming A2DP accepted");
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTED);
+ synchronized (A2dpSinkStateMachine.this) {
+ mIncomingDevice = device;
+ transitionTo(mPending);
+ }
+ } else {
+ //reject the connection and stay in Disconnected state itself
+ logi("Incoming A2DP rejected");
+ disconnectA2dpNative(getByteAddress(device));
+ // the other profile connection should be initiated
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if (adapterService != null) {
+ adapterService.connectOtherProfile(device,
+ AdapterService.PROFILE_CONN_REJECTED);
+ }
+ }
+ break;
+ case CONNECTION_STATE_CONNECTED:
+ logw("A2DP Connected from Disconnected state");
+ if (okToConnect(device)){
+ logi("Incoming A2DP accepted");
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ synchronized (A2dpSinkStateMachine.this) {
+ mCurrentDevice = device;
+ transitionTo(mConnected);
+ }
+ } else {
+ //reject the connection and stay in Disconnected state itself
+ logi("Incoming A2DP rejected");
+ disconnectA2dpNative(getByteAddress(device));
+ // the other profile connection should be initiated
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if (adapterService != null) {
+ adapterService.connectOtherProfile(device,
+ AdapterService.PROFILE_CONN_REJECTED);
+ }
+ }
+ break;
+ case CONNECTION_STATE_DISCONNECTING:
+ logw("Ignore HF DISCONNECTING event, device: " + device);
+ break;
+ default:
+ loge("Incorrect state: " + state);
+ break;
+ }
+ }
+ }
+
+ private class Pending extends State {
+ @Override
+ public void enter() {
+ log("Enter Pending: " + getCurrentMessage().what);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ log("Pending process message: " + message.what);
+
+ boolean retValue = HANDLED;
+ switch(message.what) {
+ case CONNECT:
+ deferMessage(message);
+ break;
+ case CONNECT_TIMEOUT:
+ onConnectionStateChanged(CONNECTION_STATE_DISCONNECTED,
+ getByteAddress(mTargetDevice));
+ break;
+ case DISCONNECT:
+ BluetoothDevice device = (BluetoothDevice) message.obj;
+ if (mCurrentDevice != null && mTargetDevice != null &&
+ mTargetDevice.equals(device) ) {
+ // cancel connection to the mTargetDevice
+ broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ synchronized (A2dpSinkStateMachine.this) {
+ mTargetDevice = null;
+ }
+ } else {
+ deferMessage(message);
+ }
+ break;
+ case STACK_EVENT:
+ StackEvent event = (StackEvent) message.obj;
+ switch (event.type) {
+ case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ removeMessages(CONNECT_TIMEOUT);
+ processConnectionEvent(event.valueInt, event.device);
+ break;
+ case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
+ processAudioConfigEvent(event.audioConfig, event.device);
+ break;
+ default:
+ loge("Unexpected stack event: " + event.type);
+ break;
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return retValue;
+ }
+
+ // in Pending state
+ private void processConnectionEvent(int state, BluetoothDevice device) {
+ switch (state) {
+ case CONNECTION_STATE_DISCONNECTED:
+ mAudioConfigs.remove(device);
+ if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
+ broadcastConnectionState(mCurrentDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_DISCONNECTING);
+ synchronized (A2dpSinkStateMachine.this) {
+ mCurrentDevice = null;
+ }
+
+ if (mTargetDevice != null) {
+ if (!connectA2dpNative(getByteAddress(mTargetDevice))) {
+ broadcastConnectionState(mTargetDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ synchronized (A2dpSinkStateMachine.this) {
+ mTargetDevice = null;
+ transitionTo(mDisconnected);
+ }
+ }
+ } else {
+ synchronized (A2dpSinkStateMachine.this) {
+ mIncomingDevice = null;
+ transitionTo(mDisconnected);
+ }
+ }
+ } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
+ // outgoing connection failed
+ broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ synchronized (A2dpSinkStateMachine.this) {
+ mTargetDevice = null;
+ transitionTo(mDisconnected);
+ }
+ } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
+ broadcastConnectionState(mIncomingDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ synchronized (A2dpSinkStateMachine.this) {
+ mIncomingDevice = null;
+ transitionTo(mDisconnected);
+ }
+ } else {
+ loge("Unknown device Disconnected: " + device);
+ }
+ break;
+ case CONNECTION_STATE_CONNECTED:
+ if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
+ // disconnection failed
+ broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTING);
+ if (mTargetDevice != null) {
+ broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ }
+ synchronized (A2dpSinkStateMachine.this) {
+ mTargetDevice = null;
+ transitionTo(mConnected);
+ }
+ } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
+ broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ synchronized (A2dpSinkStateMachine.this) {
+ mCurrentDevice = mTargetDevice;
+ mTargetDevice = null;
+ transitionTo(mConnected);
+ }
+ } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
+ broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ synchronized (A2dpSinkStateMachine.this) {
+ mCurrentDevice = mIncomingDevice;
+ mIncomingDevice = null;
+ transitionTo(mConnected);
+ }
+ } else {
+ loge("Unknown device Connected: " + device);
+ // something is wrong here, but sync our state with stack
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ synchronized (A2dpSinkStateMachine.this) {
+ mCurrentDevice = device;
+ mTargetDevice = null;
+ mIncomingDevice = null;
+ transitionTo(mConnected);
+ }
+ }
+ break;
+ case CONNECTION_STATE_CONNECTING:
+ if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
+ log("current device tries to connect back");
+ // TODO(BT) ignore or reject
+ } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
+ // The stack is connecting to target device or
+ // there is an incoming connection from the target device at the same time
+ // we already broadcasted the intent, doing nothing here
+ log("Stack and target device are connecting");
+ }
+ else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
+ loge("Another connecting event on the incoming device");
+ } else {
+ // We get an incoming connecting request while Pending
+ // TODO(BT) is stack handing this case? let's ignore it for now
+ log("Incoming connection while pending, ignore");
+ }
+ break;
+ case CONNECTION_STATE_DISCONNECTING:
+ if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
+ // we already broadcasted the intent, doing nothing here
+ if (DBG) {
+ log("stack is disconnecting mCurrentDevice");
+ }
+ } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
+ loge("TargetDevice is getting disconnected");
+ } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
+ loge("IncomingDevice is getting disconnected");
+ } else {
+ loge("Disconnecting unknown device: " + device);
+ }
+ break;
+ default:
+ loge("Incorrect state: " + state);
+ break;
+ }
+ }
+
+ }
+
+ private class Connected extends State {
+ @Override
+ public void enter() {
+ log("Enter Connected: " + getCurrentMessage().what);
+ // Upon connected, the audio starts out as stopped
+ broadcastAudioState(mCurrentDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
+ BluetoothA2dpSink.STATE_PLAYING);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ log("Connected process message: " + message.what);
+ if (mCurrentDevice == null) {
+ loge("ERROR: mCurrentDevice is null in Connected");
+ return NOT_HANDLED;
+ }
+
+ boolean retValue = HANDLED;
+ switch(message.what) {
+ case CONNECT:
+ {
+ BluetoothDevice device = (BluetoothDevice) message.obj;
+ if (mCurrentDevice.equals(device)) {
+ break;
+ }
+
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTED);
+ if (!disconnectA2dpNative(getByteAddress(mCurrentDevice))) {
+ broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ break;
+ }
+
+ synchronized (A2dpSinkStateMachine.this) {
+ mTargetDevice = device;
+ transitionTo(mPending);
+ }
+ }
+ break;
+ case DISCONNECT:
+ {
+ BluetoothDevice device = (BluetoothDevice) message.obj;
+ if (!mCurrentDevice.equals(device)) {
+ break;
+ }
+ broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
+ BluetoothProfile.STATE_CONNECTED);
+ if (!disconnectA2dpNative(getByteAddress(device))) {
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ break;
+ }
+ transitionTo(mPending);
+ }
+ break;
+ case STACK_EVENT:
+ StackEvent event = (StackEvent) message.obj;
+ switch (event.type) {
+ case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ processConnectionEvent(event.valueInt, event.device);
+ break;
+ case EVENT_TYPE_AUDIO_STATE_CHANGED:
+ processAudioStateEvent(event.valueInt, event.device);
+ break;
+ case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
+ processAudioConfigEvent(event.audioConfig, event.device);
+ break;
+ default:
+ loge("Unexpected stack event: " + event.type);
+ break;
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return retValue;
+ }
+
+ // in Connected state
+ private void processConnectionEvent(int state, BluetoothDevice device) {
+ switch (state) {
+ case CONNECTION_STATE_DISCONNECTED:
+ mAudioConfigs.remove(device);
+ if (mCurrentDevice.equals(device)) {
+ broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED);
+ synchronized (A2dpSinkStateMachine.this) {
+ mCurrentDevice = null;
+ transitionTo(mDisconnected);
+ }
+ } else {
+ loge("Disconnected from unknown device: " + device);
+ }
+ break;
+ default:
+ loge("Connection State Device: " + device + " bad state: " + state);
+ break;
+ }
+ }
+ private void processAudioStateEvent(int state, BluetoothDevice device) {
+ if (!mCurrentDevice.equals(device)) {
+ loge("Audio State Device:" + device + "is different from ConnectedDevice:" +
+ mCurrentDevice);
+ return;
+ }
+ switch (state) {
+ case AUDIO_STATE_STARTED:
+ broadcastAudioState(device, BluetoothA2dpSink.STATE_PLAYING,
+ BluetoothA2dpSink.STATE_NOT_PLAYING);
+ break;
+ case AUDIO_STATE_REMOTE_SUSPEND:
+ case AUDIO_STATE_STOPPED:
+ broadcastAudioState(device, BluetoothA2dpSink.STATE_NOT_PLAYING,
+ BluetoothA2dpSink.STATE_PLAYING);
+ break;
+ default:
+ loge("Audio State Device: " + device + " bad state: " + state);
+ break;
+ }
+ }
+ }
+
+ private void processAudioConfigEvent(BluetoothAudioConfig audioConfig, BluetoothDevice device) {
+ mAudioConfigs.put(device, audioConfig);
+ broadcastAudioConfig(device, audioConfig);
+ }
+
+ int getConnectionState(BluetoothDevice device) {
+ if (getCurrentState() == mDisconnected) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ synchronized (this) {
+ IState currentState = getCurrentState();
+ if (currentState == mPending) {
+ if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
+ return BluetoothProfile.STATE_CONNECTING;
+ }
+ if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
+ return BluetoothProfile.STATE_DISCONNECTING;
+ }
+ if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
+ return BluetoothProfile.STATE_CONNECTING; // incoming connection
+ }
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ if (currentState == mConnected) {
+ if (mCurrentDevice.equals(device)) {
+ return BluetoothProfile.STATE_CONNECTED;
+ }
+ return BluetoothProfile.STATE_DISCONNECTED;
+ } else {
+ loge("Bad currentState: " + currentState);
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ }
+ }
+
+ BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
+ return mAudioConfigs.get(device);
+ }
+
+ List<BluetoothDevice> getConnectedDevices() {
+ List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+ synchronized(this) {
+ if (getCurrentState() == mConnected) {
+ devices.add(mCurrentDevice);
+ }
+ }
+ return devices;
+ }
+
+ boolean okToConnect(BluetoothDevice device) {
+ AdapterService adapterService = AdapterService.getAdapterService();
+ boolean ret = true;
+ //check if this is an incoming connection in Quiet mode.
+ if((adapterService == null) ||
+ ((adapterService.isQuietModeEnabled() == true) &&
+ (mTargetDevice == null))){
+ ret = false;
+ }
+ return ret;
+ }
+
+ synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
+ Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+ int connectionState;
+
+ for (BluetoothDevice device : bondedDevices) {
+ ParcelUuid[] featureUuids = device.getUuids();
+ if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSource)) {
+ continue;
+ }
+ connectionState = getConnectionState(device);
+ for(int i = 0; i < states.length; i++) {
+ if (connectionState == states[i]) {
+ deviceList.add(device);
+ }
+ }
+ }
+ return deviceList;
+ }
+
+
+ // This method does not check for error conditon (newState == prevState)
+ private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
+
+ int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, newState,
+ BluetoothProfile.A2DP_SINK);
+
+ mWakeLock.acquire();
+ mIntentBroadcastHandler.sendMessageDelayed(mIntentBroadcastHandler.obtainMessage(
+ MSG_CONNECTION_STATE_CHANGED,
+ prevState,
+ newState,
+ device),
+ delay);
+ }
+
+ private void broadcastAudioState(BluetoothDevice device, int state, int prevState) {
+ Intent intent = new Intent(BluetoothA2dpSink.ACTION_PLAYING_STATE_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
+//FIXME intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+
+ log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
+ }
+
+ private void broadcastAudioConfig(BluetoothDevice device, BluetoothAudioConfig audioConfig) {
+ Intent intent = new Intent(BluetoothA2dpSink.ACTION_AUDIO_CONFIG_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ intent.putExtra(BluetoothA2dpSink.EXTRA_AUDIO_CONFIG, audioConfig);
+//FIXME intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+
+ log("A2DP Audio Config : device: " + device + " config: " + audioConfig);
+ }
+
+ private byte[] getByteAddress(BluetoothDevice device) {
+ return Utils.getBytesFromAddress(device.getAddress());
+ }
+
+ private void onConnectionStateChanged(int state, byte[] address) {
+ StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt = state;
+ event.device = getDevice(address);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onAudioStateChanged(int state, byte[] address) {
+ StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
+ event.valueInt = state;
+ event.device = getDevice(address);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) {
+ StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_CONFIG_CHANGED);
+ int channelConfig = (channelCount == 1 ? AudioFormat.CHANNEL_IN_MONO
+ : AudioFormat.CHANNEL_IN_STEREO);
+ event.audioConfig = new BluetoothAudioConfig(sampleRate, channelConfig,
+ AudioFormat.ENCODING_PCM_16BIT);
+ event.device = getDevice(address);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private BluetoothDevice getDevice(byte[] address) {
+ return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
+ }
+
+ private class StackEvent {
+ int type = EVENT_TYPE_NONE;
+ int valueInt = 0;
+ BluetoothDevice device = null;
+ BluetoothAudioConfig audioConfig = null;
+
+ private StackEvent(int type) {
+ this.type = type;
+ }
+ }
+ /** Handles A2DP connection state change intent broadcasts. */
+ private class IntentBroadcastHandler extends Handler {
+
+ private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) {
+ Intent intent = new Intent(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+//FIXME intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ log("Connection state " + device + ": " + prevState + "->" + state);
+ mService.notifyProfileConnectionStateChanged(device, BluetoothProfile.A2DP_SINK,
+ state, prevState);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_CONNECTION_STATE_CHANGED:
+ onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
+ mWakeLock.release();
+ break;
+ }
+ }
+ }
+
+
+ // Event types for STACK_EVENT message
+ final private static int EVENT_TYPE_NONE = 0;
+ final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+ final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
+ final private static int EVENT_TYPE_AUDIO_CONFIG_CHANGED = 3;
+
+ // Do not modify without updating the HAL bt_av.h files.
+
+ // match up with btav_connection_state_t enum of bt_av.h
+ final static int CONNECTION_STATE_DISCONNECTED = 0;
+ final static int CONNECTION_STATE_CONNECTING = 1;
+ final static int CONNECTION_STATE_CONNECTED = 2;
+ final static int CONNECTION_STATE_DISCONNECTING = 3;
+
+ // match up with btav_audio_state_t enum of bt_av.h
+ final static int AUDIO_STATE_REMOTE_SUSPEND = 0;
+ final static int AUDIO_STATE_STOPPED = 1;
+ final static int AUDIO_STATE_STARTED = 2;
+
+ private native static void classInitNative();
+ 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/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index c9e75c5..94cc391 100755
--- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -78,10 +78,6 @@
private static final int MSG_CONNECTION_STATE_CHANGED = 0;
- private static final ParcelUuid[] A2DP_UUIDS = {
- BluetoothUuid.AudioSink
- };
-
// mCurrentDevice is the device connected before the state changes
// mTargetDevice is the device to be connected
// mIncomingDevice is the device connecting to us, valid only in Pending state
@@ -662,7 +658,7 @@
for (BluetoothDevice device : bondedDevices) {
ParcelUuid[] featureUuids = device.getUuids();
- if (!BluetoothUuid.containsAnyUuid(featureUuids, A2DP_UUIDS)) {
+ if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSink)) {
continue;
}
connectionState = getConnectionState(device);
@@ -679,7 +675,8 @@
// This method does not check for error conditon (newState == prevState)
private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
- int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, newState);
+ int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, newState,
+ BluetoothProfile.A2DP);
mWakeLock.acquire();
mIntentBroadcastHandler.sendMessageDelayed(mIntentBroadcastHandler.obtainMessage(
diff --git a/src/com/android/bluetooth/a2dp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java
similarity index 97%
rename from src/com/android/bluetooth/a2dp/Avrcp.java
rename to src/com/android/bluetooth/avrcp/Avrcp.java
index 2db403e..b1a94b8 100755
--- a/src/com/android/bluetooth/a2dp/Avrcp.java
+++ b/src/com/android/bluetooth/avrcp/Avrcp.java
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.android.bluetooth.a2dp;
+package com.android.bluetooth.avrcp;
import java.util.Timer;
import java.util.TimerTask;
import android.app.PendingIntent;
import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAvrcp;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -55,7 +56,7 @@
* support Bluetooth AVRCP profile.
* support metadata, play status and event notification
*/
-final class Avrcp {
+public final class Avrcp {
private static final boolean DEBUG = true;
private static final String TAG = "Avrcp";
@@ -88,10 +89,6 @@
private int mAbsVolRetryTimes;
private int mSkipAmount;
- /* AVRC IDs from avrc_defs.h */
- private static final int AVRC_ID_REWIND = 0x48;
- private static final int AVRC_ID_FAST_FOR = 0x49;
-
/* BTRC features */
public static final int BTRC_FEAT_METADATA = 0x01;
public static final int BTRC_FEAT_ABSOLUTE_VOLUME = 0x02;
@@ -179,7 +176,7 @@
mRemoteControlDisplay, true);
}
- static Avrcp make(Context context) {
+ public static Avrcp make(Context context) {
if (DEBUG) Log.v(TAG, "make");
Avrcp ar = new Avrcp(context);
ar.start();
@@ -337,8 +334,8 @@
break;
case MESSAGE_VOLUME_CHANGED:
- if (DEBUG) Log.v(TAG, "MESSAGE_VOLUME_CHANGED: volume=" + msg.arg1 +
- " ctype=" + msg.arg2);
+ if (DEBUG) Log.v(TAG, "MESSAGE_VOLUME_CHANGED: volume=" + ((byte)msg.arg1 & 0x7f)
+ + " ctype=" + msg.arg2);
if (msg.arg2 == AVRC_RSP_ACCEPT || msg.arg2 == AVRC_RSP_REJ) {
if (mVolCmdInProgress == false) {
@@ -352,8 +349,11 @@
if (mAbsoluteVolume != msg.arg1 && (msg.arg2 == AVRC_RSP_ACCEPT ||
msg.arg2 == AVRC_RSP_CHANGED ||
msg.arg2 == AVRC_RSP_INTERIM)) {
- notifyVolumeChanged(msg.arg1);
- mAbsoluteVolume = msg.arg1;
+ byte absVol = (byte)((byte)msg.arg1 & 0x7f); // discard MSB as it is RFD
+ notifyVolumeChanged((int)absVol);
+ mAbsoluteVolume = absVol;
+ long pecentVolChanged = ((long)absVol * 100) / 0x7f;
+ Log.e(TAG, "percent volume changed: " + pecentVolChanged + "%");
} else if (msg.arg2 == AVRC_RSP_REJ) {
Log.e(TAG, "setAbsoluteVolume call rejected");
}
@@ -633,10 +633,10 @@
private void handlePassthroughCmd(int id, int keyState) {
switch (id) {
- case AVRC_ID_REWIND:
+ case BluetoothAvrcp.PASSTHROUGH_ID_REWIND:
rewind(keyState);
break;
- case AVRC_ID_FAST_FOR:
+ case BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR:
fastForward(keyState);
break;
}
@@ -790,7 +790,6 @@
mHandler.removeMessages(MESSAGE_ADJUST_VOLUME);
Message msg = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, avrcpVolume, 0);
mHandler.sendMessage(msg);
-
}
/* Called in the native layer as a btrc_callback to return the volume set on the carkit in the
@@ -873,4 +872,6 @@
private native boolean registerNotificationRspTrackChangeNative(int type, byte[] track);
private native boolean registerNotificationRspPlayPosNative(int type, int playPos);
private native boolean setVolumeNative(int volume);
+ private native boolean sendPassThroughCommandNative(int keyCode, int keyState);
+
}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpControllerService.java b/src/com/android/bluetooth/avrcp/AvrcpControllerService.java
new file mode 100644
index 0000000..b7275d6
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/AvrcpControllerService.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAvrcpController;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothAvrcpController;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.Utils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+
+/**
+ * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
+ * @hide
+ */
+public class AvrcpControllerService extends ProfileService {
+ private static final boolean DBG = false;
+ private static final String TAG = "AvrcpControllerService";
+
+ private static final int MESSAGE_SEND_PASS_THROUGH_CMD = 1;
+
+ private AvrcpMessageHandler mHandler;
+ private static AvrcpControllerService sAvrcpControllerService;
+
+ private final ArrayList<BluetoothDevice> mConnectedDevices
+ = new ArrayList<BluetoothDevice>();
+
+ static {
+ classInitNative();
+ }
+
+ public AvrcpControllerService() {
+ initNative();
+ }
+
+ protected String getName() {
+ return TAG;
+ }
+
+ protected IProfileServiceBinder initBinder() {
+ return new BluetoothAvrcpControllerBinder(this);
+ }
+
+ protected boolean start() {
+ HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
+ thread.start();
+ Looper looper = thread.getLooper();
+ mHandler = new AvrcpMessageHandler(looper);
+
+ setAvrcpControllerService(this);
+ return true;
+ }
+
+ protected boolean stop() {
+ return true;
+ }
+
+ protected boolean cleanup() {
+ mHandler.removeCallbacksAndMessages(null);
+ Looper looper = mHandler.getLooper();
+ if (looper != null) {
+ looper.quit();
+ }
+
+ clearAvrcpControllerService();
+
+ cleanupNative();
+
+ return true;
+ }
+
+ //API Methods
+
+ public static synchronized AvrcpControllerService getAvrcpControllerService(){
+ if (sAvrcpControllerService != null && sAvrcpControllerService.isAvailable()) {
+ if (DBG) Log.d(TAG, "getAvrcpControllerService(): returning "
+ + sAvrcpControllerService);
+ return sAvrcpControllerService;
+ }
+ if (DBG) {
+ if (sAvrcpControllerService == null) {
+ Log.d(TAG, "getAvrcpControllerService(): service is NULL");
+ } else if (!(sAvrcpControllerService.isAvailable())) {
+ Log.d(TAG,"getAvrcpControllerService(): service is not available");
+ }
+ }
+ return null;
+ }
+
+ private static synchronized void setAvrcpControllerService(AvrcpControllerService instance) {
+ if (instance != null && instance.isAvailable()) {
+ if (DBG) Log.d(TAG, "setAvrcpControllerService(): set to: " + sAvrcpControllerService);
+ sAvrcpControllerService = instance;
+ } else {
+ if (DBG) {
+ if (sAvrcpControllerService == null) {
+ Log.d(TAG, "setAvrcpControllerService(): service not available");
+ } else if (!sAvrcpControllerService.isAvailable()) {
+ Log.d(TAG,"setAvrcpControllerService(): service is cleaning up");
+ }
+ }
+ }
+ }
+
+ private static synchronized void clearAvrcpControllerService() {
+ sAvrcpControllerService = null;
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mConnectedDevices;
+ }
+
+ List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ for (int i = 0; i < states.length; i++) {
+ if (states[i] == BluetoothProfile.STATE_CONNECTED) {
+ return mConnectedDevices;
+ }
+ }
+ return new ArrayList<BluetoothDevice>();
+ }
+
+ int getConnectionState(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return (mConnectedDevices.contains(device) ? BluetoothProfile.STATE_CONNECTED
+ : BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
+ if (DBG) Log.d(TAG, "sendPassThroughCmd");
+ Log.v(TAG, "keyCode: " + keyCode + " keyState: " + keyState);
+ if (device == null) {
+ throw new NullPointerException("device == null");
+ }
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ Message msg = mHandler.obtainMessage(MESSAGE_SEND_PASS_THROUGH_CMD,
+ keyCode, keyState, device);
+ mHandler.sendMessage(msg);
+ }
+
+ //Binder object: Must be static class or memory leak may occur
+ private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
+ implements IProfileServiceBinder {
+ private AvrcpControllerService mService;
+
+ private AvrcpControllerService getService() {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG,"AVRCP call not allowed for non-active user");
+ return null;
+ }
+
+ if (mService != null && mService.isAvailable()) {
+ return mService;
+ }
+ return null;
+ }
+
+ BluetoothAvrcpControllerBinder(AvrcpControllerService svc) {
+ mService = svc;
+ }
+
+ public boolean cleanup() {
+ mService = null;
+ return true;
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ AvrcpControllerService service = getService();
+ if (service == null) return new ArrayList<BluetoothDevice>(0);
+ return service.getConnectedDevices();
+ }
+
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ AvrcpControllerService service = getService();
+ if (service == null) return new ArrayList<BluetoothDevice>(0);
+ return service.getDevicesMatchingConnectionStates(states);
+ }
+
+ public int getConnectionState(BluetoothDevice device) {
+ AvrcpControllerService service = getService();
+ if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
+ return service.getConnectionState(device);
+ }
+
+ public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
+ Log.v(TAG,"Binder Call: sendPassThroughCmd");
+ AvrcpControllerService service = getService();
+ if (service == null) return;
+ service.sendPassThroughCmd(device, keyCode, keyState);
+ }
+ };
+
+ /** Handles Avrcp messages. */
+ private final class AvrcpMessageHandler extends Handler {
+ private AvrcpMessageHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_SEND_PASS_THROUGH_CMD:
+ if (DBG) Log.v(TAG, "MESSAGE_SEND_PASS_THROUGH_CMD");
+ BluetoothDevice device = (BluetoothDevice)msg.obj;
+ sendPassThroughCommandNative(getByteAddress(device), msg.arg1, msg.arg2);
+ break;
+ }
+ }
+ }
+
+ private void onConnectionStateChanged(boolean connected, byte[] address) {
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
+ (Utils.getAddressStringFromByte(address));
+ Log.d(TAG, "onConnectionStateChanged " + connected + " " + device);
+ Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
+ int oldState = (mConnectedDevices.contains(device) ? BluetoothProfile.STATE_CONNECTED
+ : BluetoothProfile.STATE_DISCONNECTED);
+ int newState = (connected ? BluetoothProfile.STATE_CONNECTED
+ : BluetoothProfile.STATE_DISCONNECTED);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, oldState);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+// intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ if (connected && oldState == BluetoothProfile.STATE_DISCONNECTED) {
+ mConnectedDevices.add(device);
+ } else if (!connected && oldState == BluetoothProfile.STATE_CONNECTED) {
+ mConnectedDevices.remove(device);
+ }
+ }
+
+ private void handlePassthroughRsp(int id, int keyState) {
+ Log.d(TAG, "passthrough response received as: key: "
+ + id + " state: " + keyState);
+ }
+
+ private byte[] getByteAddress(BluetoothDevice device) {
+ return Utils.getBytesFromAddress(device.getAddress());
+ }
+
+ private native static void classInitNative();
+ private native void initNative();
+ private native void cleanupNative();
+ private native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
+}
diff --git a/src/com/android/bluetooth/btservice/AbstractionLayer.java b/src/com/android/bluetooth/btservice/AbstractionLayer.java
index 4e0ebd0..413c077 100644
--- a/src/com/android/bluetooth/btservice/AbstractionLayer.java
+++ b/src/com/android/bluetooth/btservice/AbstractionLayer.java
@@ -45,6 +45,9 @@
static final int BT_PROPERTY_REMOTE_FRIENDLY_NAME = 0x0A;
static final int BT_PROPERTY_REMOTE_RSSI = 0x0B;
+ static final int BT_PROPERTY_REMOTE_VERSION_INFO = 0x0C;
+ static final int BT_PROPERTY_LOCAL_LE_FEATURES = 0x0D;
+
static final int BT_DEVICE_TYPE_BREDR = 0x01;
static final int BT_DEVICE_TYPE_BLE = 0x02;
static final int BT_DEVICE_TYPE_DUAL = 0x03;
diff --git a/src/com/android/bluetooth/btservice/AdapterProperties.java b/src/com/android/bluetooth/btservice/AdapterProperties.java
old mode 100755
new mode 100644
index d1b8e32..8d829de
--- a/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -58,6 +58,12 @@
private boolean mDiscovering;
private RemoteDevices mRemoteDevices;
private BluetoothAdapter mAdapter;
+ //TODO - all hw capabilities to be exposed as a class
+ private int mNumOfAdvertisementInstancesSupported;
+ private boolean mRpaOffloadSupported;
+ private int mNumOfOffloadedIrkSupported;
+ private int mNumOfOffloadedScanFilterSupported;
+ private int mOffloadedScanResultStorageBytes;
// Lock for all getters and setters.
// If finer grained locking is needer, more locks
@@ -210,6 +216,41 @@
}
/**
+ * @return the mNumOfAdvertisementInstancesSupported
+ */
+ int getNumOfAdvertisementInstancesSupported() {
+ return mNumOfAdvertisementInstancesSupported;
+ }
+
+ /**
+ * @return the mRpaOffloadSupported
+ */
+ boolean isRpaOffloadSupported() {
+ return mRpaOffloadSupported;
+ }
+
+ /**
+ * @return the mNumOfOffloadedIrkSupported
+ */
+ int getNumOfOffloadedIrkSupported() {
+ return mNumOfOffloadedIrkSupported;
+ }
+
+ /**
+ * @return the mNumOfOffloadedScanFilterSupported
+ */
+ int getNumOfOffloadedScanFilterSupported() {
+ return mNumOfOffloadedScanFilterSupported;
+ }
+
+ /**
+ * @return the mOffloadedScanResultStorageBytes
+ */
+ int getOffloadedScanResultStorage() {
+ return mOffloadedScanResultStorageBytes;
+ }
+
+ /**
* @return the mBondedDevices
*/
BluetoothDevice[] getBondedDevices() {
@@ -488,6 +529,24 @@
mDiscoverableTimeout = Utils.byteArrayToInt(val, 0);
debugLog("Discoverable Timeout:" + mDiscoverableTimeout);
break;
+
+ case AbstractionLayer.BT_PROPERTY_LOCAL_LE_FEATURES:
+ mNumOfAdvertisementInstancesSupported = (0x000000FF & ((int)val[1]));
+ mRpaOffloadSupported = ((0x000000FF & ((int)val[2]))!= 0);
+ mNumOfOffloadedIrkSupported = (0x000000FF & ((int)val[3]));
+ mNumOfOffloadedScanFilterSupported = (0x000000FF & ((int)val[4]));
+ mOffloadedScanResultStorageBytes = (0x000000FF & ((int)val[5]));
+
+ Log.d(TAG, "BT_PROPERTY_LOCAL_LE_FEATURES: update from BT controller"
+ + " mNumOfAdvertisementInstancesSupported = " + mNumOfAdvertisementInstancesSupported
+ + " mRpaOffloadSupported = " + mRpaOffloadSupported
+ + " mNumOfOffloadedIrkSupported = " + mNumOfOffloadedIrkSupported
+ + " mNumOfOffloadedScanFilterSupported = "
+ + mNumOfOffloadedScanFilterSupported
+ + " mOffloadedScanResultStorageBytes = " + mOffloadedScanResultStorageBytes);
+
+ break;
+
default:
errorLog("Property change not handled in Java land:" + type);
}
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
old mode 100755
new mode 100644
index 3692b0c..91856ee
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -20,11 +20,14 @@
package com.android.bluetooth.btservice;
+import android.app.AlarmManager;
import android.app.Application;
+import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetooth;
import android.bluetooth.IBluetoothCallback;
import android.bluetooth.IBluetoothManager;
@@ -41,9 +44,11 @@
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.ParcelUuid;
+import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import android.util.Pair;
@@ -82,6 +87,9 @@
public static final int PROFILE_CONN_CONNECTED = 1;
public static final int PROFILE_CONN_REJECTED = 2;
+ private static final String ACTION_ALARM_WAKEUP =
+ "com.android.bluetooth.btservice.action.ALARM_WAKEUP";
+
static final String BLUETOOTH_ADMIN_PERM =
android.Manifest.permission.BLUETOOTH_ADMIN;
static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
@@ -140,6 +148,12 @@
private int mCurrentRequestId;
private boolean mQuietmode = false;
+ private AlarmManager mAlarmManager;
+ private PendingIntent mPendingAlarm;
+ private PowerManager mPowerManager;
+ private PowerManager.WakeLock mWakeLock;
+ private String mWakeLockName;
+
public AdapterService() {
super();
if (TRACE_REF) {
@@ -161,9 +175,50 @@
mHandler.sendMessage(m);
}
+ public void initProfilePriorities(BluetoothDevice device, ParcelUuid[] mUuids) {
+ if(mUuids == null) return;
+ Message m = mHandler.obtainMessage(MESSAGE_PROFILE_INIT_PRIORITIES);
+ m.obj = device;
+ m.arg1 = mUuids.length;
+ Bundle b = new Bundle(1);
+ for(int i=0; i<mUuids.length; i++) {
+ b.putParcelable("uuids" + i, mUuids[i]);
+ }
+ m.setData(b);
+ mHandler.sendMessage(m);
+ }
+
+ private void processInitProfilePriorities (BluetoothDevice device, ParcelUuid[] uuids){
+ HidService hidService = HidService.getHidService();
+ A2dpService a2dpService = A2dpService.getA2dpService();
+ HeadsetService headsetService = HeadsetService.getHeadsetService();
+
+ // Set profile priorities only for the profiles discovered on the remote device.
+ // This avoids needless auto-connect attempts to profiles non-existent on the remote device
+ if ((hidService != null) &&
+ (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid)) &&
+ (hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
+ hidService.setPriority(device,BluetoothProfile.PRIORITY_ON);
+ }
+
+ if ((a2dpService != null) &&
+ (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink) ||
+ (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AdvAudioDist)) &&
+ (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED))){
+ a2dpService.setPriority(device,BluetoothProfile.PRIORITY_ON);
+ }
+
+ if ((headsetService != null) &&
+ ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP) ||
+ BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree)) &&
+ (headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED))){
+ headsetService.setPriority(device,BluetoothProfile.PRIORITY_ON);
+ }
+ }
+
private void processProfileStateChanged(BluetoothDevice device, int profileId, int newState, int prevState) {
if (((profileId == BluetoothProfile.A2DP) ||(profileId == BluetoothProfile.HEADSET)) &&
- (newState == BluetoothProfile.STATE_CONNECTED)){
+ (newState == BluetoothProfile.STATE_CONNECTED)){
if (DBG) debugLog( "Profile connected. Schedule missing profile connection if any");
connectOtherProfile(device, PROFILE_CONN_CONNECTED);
setProfileAutoConnectionPriority(device, profileId);
@@ -261,7 +316,10 @@
//Load the name and address
getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDADDR);
getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDNAME);
+ mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ registerReceiver(mAlarmBroadcastReceiver, new IntentFilter(ACTION_ALARM_WAKEUP));
}
@Override
@@ -346,6 +404,18 @@
mCleaningUp = true;
+ unregisterReceiver(mAlarmBroadcastReceiver);
+
+ if (mPendingAlarm != null) {
+ mAlarmManager.cancel(mPendingAlarm);
+ mPendingAlarm = null;
+ }
+
+ if (mWakeLock != null) {
+ mWakeLock.release();
+ mWakeLock = null;
+ }
+
if (mAdapterStateMachine != null) {
mAdapterStateMachine.doQuit();
mAdapterStateMachine.cleanup();
@@ -399,6 +469,7 @@
private static final int MESSAGE_PROFILE_SERVICE_STATE_CHANGED =1;
private static final int MESSAGE_PROFILE_CONNECTION_STATE_CHANGED=20;
private static final int MESSAGE_CONNECT_OTHER_PROFILES = 30;
+ private static final int MESSAGE_PROFILE_INIT_PRIORITIES=40;
private static final int CONNECT_OTHER_PROFILES_TIMEOUT= 6000;
private final Handler mHandler = new Handler() {
@@ -417,6 +488,16 @@
processProfileStateChanged((BluetoothDevice) msg.obj, msg.arg1,msg.arg2, msg.getData().getInt("prevState",BluetoothAdapter.ERROR));
}
break;
+ case MESSAGE_PROFILE_INIT_PRIORITIES: {
+ if (DBG) debugLog( "MESSAGE_PROFILE_INIT_PRIORITIES");
+ ParcelUuid[] mUuids = new ParcelUuid[msg.arg1];
+ for(int i=0; i<mUuids.length; i++) {
+ mUuids[i] = msg.getData().getParcelable("uuids" + i);
+ }
+ processInitProfilePriorities((BluetoothDevice) msg.obj,
+ mUuids);
+ }
+ break;
case MESSAGE_CONNECT_OTHER_PROFILES: {
if (DBG) debugLog( "MESSAGE_CONNECT_OTHER_PROFILES");
processConnectOtherProfiles((BluetoothDevice) msg.obj,msg.arg1);
@@ -736,6 +817,14 @@
return service.getBondState(device);
}
+ public boolean isConnected(BluetoothDevice device) {
+ AdapterService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.isConnected(device);
+ }
+
public String getRemoteName(BluetoothDevice device) {
if (!Utils.checkCaller()) {
Log.w(TAG,"getRemoteName(): not allowed for non-active user");
@@ -1133,6 +1222,7 @@
}
HeadsetService hsService = HeadsetService.getHeadsetService();
A2dpService a2dpService = A2dpService.getA2dpService();
+
// if any of the profile service is null, second profile connection not required
if ((hsService == null) ||(a2dpService == null )){
return;
@@ -1159,10 +1249,10 @@
}
private void adjustOtherHeadsetPriorities(HeadsetService hsService,
- BluetoothDevice connectedDevice) {
+ List<BluetoothDevice> connectedDeviceList) {
for (BluetoothDevice device : getBondedDevices()) {
if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT &&
- !device.equals(connectedDevice)) {
+ !connectedDeviceList.contains(device)) {
hsService.setPriority(device, BluetoothProfile.PRIORITY_ON);
}
}
@@ -1181,9 +1271,10 @@
void setProfileAutoConnectionPriority (BluetoothDevice device, int profileId){
if (profileId == BluetoothProfile.HEADSET) {
HeadsetService hsService = HeadsetService.getHeadsetService();
+ List<BluetoothDevice> deviceList = hsService.getConnectedDevices();
if ((hsService != null) &&
(BluetoothProfile.PRIORITY_AUTO_CONNECT != hsService.getPriority(device))){
- adjustOtherHeadsetPriorities(hsService, device);
+ adjustOtherHeadsetPriorities(hsService, deviceList);
hsService.setPriority(device,BluetoothProfile.PRIORITY_AUTO_CONNECT);
}
}
@@ -1224,6 +1315,14 @@
return deviceProp.getBondState();
}
+ boolean isConnected(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+ DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
+ if (deviceProp == null) return false;
+ return deviceProp.getOpenAclConnectionCount() > 0;
+ }
+
String getRemoteName(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
@@ -1358,6 +1457,31 @@
mCallbacks.unregister(cb);
}
+ public int getNumOfAdvertisementInstancesSupported() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.getNumOfAdvertisementInstancesSupported();
+ }
+
+ public boolean isRpaOffloadSupported() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.isRpaOffloadSupported();
+ }
+
+ public int getNumOfOffloadedIrkSupported() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.getNumOfOffloadedIrkSupported();
+ }
+
+ public int getNumOfOffloadedScanFilterSupported() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.getNumOfOffloadedScanFilterSupported();
+ }
+
+ public int getOffloadedScanResultStorage() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mAdapterProperties.getOffloadedScanResultStorage();
+ }
+
private static int convertScanModeToHal(int mode) {
switch (mode) {
case BluetoothAdapter.SCAN_MODE_NONE:
@@ -1384,6 +1508,66 @@
return -1;
}
+ // This function is called from JNI. It allows native code to set a single wake
+ // alarm. If an alarm is already pending and a new request comes in, the alarm
+ // will be rescheduled (i.e. the previously set alarm will be cancelled).
+ private boolean setWakeAlarm(long delayMillis, boolean shouldWake) {
+ synchronized (this) {
+ if (mPendingAlarm != null) {
+ mAlarmManager.cancel(mPendingAlarm);
+ }
+
+ long wakeupTime = SystemClock.elapsedRealtime() + delayMillis;
+ int type = shouldWake
+ ? AlarmManager.ELAPSED_REALTIME_WAKEUP
+ : AlarmManager.ELAPSED_REALTIME;
+
+ Intent intent = new Intent(ACTION_ALARM_WAKEUP);
+ mPendingAlarm = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+ mAlarmManager.setExact(type, wakeupTime, mPendingAlarm);
+ return true;
+ }
+ }
+
+ // This function is called from JNI. It allows native code to acquire a single wake lock.
+ // If the wake lock is already held, this function returns success. Although this function
+ // only supports acquiring a single wake lock at a time right now, it will eventually be
+ // extended to allow acquiring an arbitrary number of wake locks. The current interface
+ // takes |lockName| as a parameter in anticipation of that implementation.
+ private boolean acquireWakeLock(String lockName) {
+ if (mWakeLock != null) {
+ if (!lockName.equals(mWakeLockName)) {
+ errorLog("Multiple wake lock acquisition attempted; aborting: " + lockName);
+ return false;
+ }
+
+ // We're already holding the desired wake lock so return success.
+ if (mWakeLock.isHeld()) {
+ return true;
+ }
+ }
+
+ mWakeLockName = lockName;
+ mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lockName);
+ mWakeLock.acquire();
+ return true;
+ }
+
+ // This function is called from JNI. It allows native code to release a wake lock acquired
+ // by |acquireWakeLock|. If the wake lock is not held, this function returns failure. See
+ // the comment for |acquireWakeLock| for an explanation of the interface.
+ private boolean releaseWakeLock(String lockName) {
+ if (mWakeLock == null) {
+ errorLog("Repeated wake lock release; aborting release: " + lockName);
+ return false;
+ }
+
+ mWakeLock.release();
+ mWakeLock = null;
+ mWakeLockName = null;
+ return true;
+ }
+
private void debugLog(String msg) {
Log.d(TAG +"(" +hashCode()+")", msg);
}
@@ -1392,6 +1576,16 @@
Log.e(TAG +"(" +hashCode()+")", msg);
}
+ private final BroadcastReceiver mAlarmBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (AdapterService.this) {
+ mPendingAlarm = null;
+ alarmFiredNative();
+ }
+ }
+ };
+
private native static void classInitNative();
private native boolean initNative();
private native void cleanupNative();
@@ -1426,6 +1620,8 @@
/*package*/ native boolean configHciSnoopLogNative(boolean enable);
+ private native void alarmFiredNative();
+
protected void finalize() {
cleanup();
if (TRACE_REF) {
diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
index 0b6478b..e89b5bd 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -191,8 +191,11 @@
}
else if (newState == BluetoothDevice.BOND_BONDED)
{
+ // Do not set profile priority
+ // Profile priority should be set after SDP completion
+
// Restore the profile priorty settings
- setProfilePriorty(dev);
+ //setProfilePriorty(dev);
}
}
else if(!mDevices.contains(dev))
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index 99cc411..732d58e 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -20,12 +20,16 @@
import android.content.Context;
import android.content.res.Resources;
+import android.os.SystemProperties;
import android.util.Log;
import com.android.bluetooth.R;
import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.a2dp.A2dpSinkService;
+import com.android.bluetooth.avrcp.AvrcpControllerService;
import com.android.bluetooth.hdp.HealthService;
import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.hfpclient.HeadsetClientService;
import com.android.bluetooth.hid.HidService;
import com.android.bluetooth.pan.PanService;
import com.android.bluetooth.gatt.GattService;
@@ -42,11 +46,14 @@
private static final Class[] PROFILE_SERVICES = {
HeadsetService.class,
A2dpService.class,
+ A2dpSinkService.class,
HidService.class,
HealthService.class,
PanService.class,
GattService.class,
- BluetoothMapService.class
+ BluetoothMapService.class,
+ HeadsetClientService.class,
+ AvrcpControllerService.class,
};
/**
* Resource flag to indicate whether profile is supported or not.
@@ -54,11 +61,14 @@
private static final int[] PROFILE_SERVICES_FLAG = {
R.bool.profile_supported_hs_hfp,
R.bool.profile_supported_a2dp,
+ R.bool.profile_supported_a2dp_sink,
R.bool.profile_supported_hid,
R.bool.profile_supported_hdp,
R.bool.profile_supported_pan,
R.bool.profile_supported_gatt,
- R.bool.profile_supported_map
+ R.bool.profile_supported_map,
+ R.bool.profile_supported_hfpclient,
+ R.bool.profile_supported_avrcp_controller,
};
private static Class[] SUPPORTED_PROFILES = new Class[0];
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index acb5488..d0ea45b 100755
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -29,7 +29,9 @@
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
@@ -105,9 +107,11 @@
private int mDeviceType;
private String mAlias;
private int mBondState;
+ private AtomicInteger mOpenAclConnectionCount;
DeviceProperties() {
mBondState = BluetoothDevice.BOND_NONE;
+ mOpenAclConnectionCount = new AtomicInteger(0);
}
/**
@@ -208,14 +212,18 @@
return mBondState;
}
}
- }
+ int getOpenAclConnectionCount() {
+ return mOpenAclConnectionCount.get();
+ }
+ }
private void sendUuidIntent(BluetoothDevice device) {
DeviceProperties prop = getDeviceProperties(device);
Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothDevice.EXTRA_UUID, prop == null? null: prop.mUuids);
+ mAdapterService.initProfilePriorities(device, prop.mUuids);
mAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_ADMIN_PERM);
//Remove the outstanding UUID request
@@ -410,13 +418,24 @@
return;
}
+ int openAclConnectionCount;
+ DeviceProperties prop = getDeviceProperties(device);
+ if (prop == null) {
+ errorLog("aclStateChangeCallback reported unknown device " + Arrays.toString(address));
+ }
Intent intent = null;
if (newState == AbstractionLayer.BT_ACL_STATE_CONNECTED) {
intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
debugLog("aclStateChangeCallback: State:Connected to Device:" + device);
+ if (prop != null) {
+ prop.mOpenAclConnectionCount.incrementAndGet();
+ }
} else {
intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
debugLog("aclStateChangeCallback: State:DisConnected to Device:" + device);
+ if (prop != null) {
+ prop.mOpenAclConnectionCount.decrementAndGet();
+ }
}
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
diff --git a/src/com/android/bluetooth/gatt/AdvertiseClient.java b/src/com/android/bluetooth/gatt/AdvertiseClient.java
new file mode 100644
index 0000000..163cc5b
--- /dev/null
+++ b/src/com/android/bluetooth/gatt/AdvertiseClient.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.gatt;
+
+import android.annotation.Nullable;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.AdvertisementData;
+
+/**
+ * @hide
+ */
+class AdvertiseClient {
+ int clientIf;
+ AdvertiseSettings settings;
+ AdvertisementData advertiseData;
+ @Nullable
+ AdvertisementData scanResponse;
+
+ AdvertiseClient(int clientIf, AdvertiseSettings settings, AdvertisementData data,
+ AdvertisementData scanResponse) {
+ this.clientIf = clientIf;
+ this.settings = settings;
+ this.advertiseData = data;
+ this.scanResponse = scanResponse;
+ }
+}
diff --git a/src/com/android/bluetooth/gatt/GattDebugUtils.java b/src/com/android/bluetooth/gatt/GattDebugUtils.java
index 3911e6c..5c42db6 100644
--- a/src/com/android/bluetooth/gatt/GattDebugUtils.java
+++ b/src/com/android/bluetooth/gatt/GattDebugUtils.java
@@ -53,6 +53,7 @@
private static final String EXTRA_ADDRESS = "address";
private static final String EXTRA_UUID = "uuid";
private static final String EXTRA_TYPE = "type";
+ private static final String EXTRA_ADDR_TYPE = "addr_type";
private static final String EXTRA_SHANDLE = "start";
private static final String EXTRA_EHANDLE = "end";
private static final String EXTRA_AUTH_REQ = "auth_req";
@@ -107,7 +108,8 @@
} else if (ACTION_GATT_TEST_CONNECT.equals(action)) {
String address = intent.getStringExtra(EXTRA_ADDRESS);
int type = intent.getIntExtra(EXTRA_TYPE, 2 /* LE device */);
- svc.gattTestCommand( 0x02, null, address, type, 0,0,0,0);
+ int addr_type = intent.getIntExtra(EXTRA_ADDR_TYPE, 0 /* Static */);
+ svc.gattTestCommand( 0x02, null, address, type, addr_type, 0,0,0);
} else if (ACTION_GATT_TEST_DISCONNECT.equals(action)) {
svc.gattTestCommand( 0x03, null, null, 0,0,0,0,0);
@@ -181,6 +183,9 @@
b.append("\n defaults to true (enable).\n");
b.append("\nGATT_TEST_CONNECT");
b.append("\n --es address <bda>");
+ b.append("\n [--ei addr_type <type>] Possible values:");
+ b.append("\n 0 = Static (default)");
+ b.append("\n 1 = Random\n");
b.append("\n [--ei type <type>] Default is 2 (LE Only)\n");
b.append("\nGATT_TEST_DISCONNECT\n");
b.append("\nGATT_TEST_DISCOVER");
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index 0e120bb..4c328ac 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -24,13 +24,23 @@
import android.bluetooth.IBluetoothGatt;
import android.bluetooth.IBluetoothGattCallback;
import android.bluetooth.IBluetoothGattServerCallback;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.AdvertisementData;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
import android.content.Intent;
import android.os.IBinder;
+import android.os.Message;
import android.os.ParcelUuid;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Log;
+import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.R;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -39,8 +49,10 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.TimeUnit;
/**
* Provides Bluetooth Gatt profile, as a service in
@@ -75,6 +87,19 @@
*/
private static final int FULL_UUID_BYTES = 16;
+ static final int SCAN_FILTER_ENABLED = 1;
+ static final int SCAN_FILTER_MODIFIED = 2;
+
+ /**
+ * Scan params corresponding to scan setting
+ */
+ private static final int SCAN_MODE_LOW_POWER_WINDOW_MS = 500;
+ private static final int SCAN_MODE_LOW_POWER_INTERVAL_MS = 5000;
+ private static final int SCAN_MODE_BALANCED_WINDOW_MS = 2000;
+ private static final int SCAN_MODE_BALANCED_INTERVAL_MS = 5000;
+ private static final int SCAN_MODE_LOW_LATENCY_WINDOW_MS = 5000;
+ private static final int SCAN_MODE_LOW_LATENCY_INTERVAL_MS = 5000;
+
/**
* Search queue to serialize remote onbject inspection.
*/
@@ -106,6 +131,8 @@
private byte[] mManufacturerData = new byte[0];
private Integer mAdvertisingState = BluetoothAdapter.STATE_ADVERTISE_STOPPED;
private final Object mLock = new Object();
+ private static int lastConfiguredScanSetting = Integer.MIN_VALUE;
+ private int mMaxScanFilters;
/**
* Pending service declaration queue
@@ -146,7 +173,9 @@
* List of clients interested in scan results.
*/
private List<ScanClient> mScanQueue = new ArrayList<ScanClient>();
+ private Set<ScanFilter> mScanFilters = new HashSet<ScanFilter>();
+ private GattServiceStateMachine mStateMachine;
private ScanClient getScanClient(int appIf, boolean isServer) {
for(ScanClient client : mScanQueue) {
if (client.appIf == appIf && client.isServer == isServer) {
@@ -185,6 +214,7 @@
protected boolean start() {
if (DBG) Log.d(TAG, "start()");
initializeNative();
+ mStateMachine = GattServiceStateMachine.make(this);
return true;
}
@@ -194,8 +224,10 @@
mServerMap.clear();
mSearchQueue.clear();
mScanQueue.clear();
+ mScanFilters.clear();
mHandleMap.clear();
mServiceDeclarations.clear();
+ mStateMachine.doQuit();
mReliableQueue.clear();
return true;
}
@@ -203,6 +235,7 @@
protected boolean cleanup() {
if (DBG) Log.d(TAG, "cleanup()");
cleanupNative();
+ mStateMachine.cleanup();
return true;
}
@@ -231,7 +264,11 @@
if (mAdvertisingClientIf == mAppIf) {
stopAdvertising(true); // force stop advertising.
} else {
- stopScan(mAppIf, false);
+ if (getScanClient(mAppIf, false) != null) {
+ stopScan(mAppIf, false);
+ } else {
+ stopMultiAdvertising(mAppIf);
+ }
}
unregisterClient(mAppIf);
}
@@ -305,16 +342,24 @@
service.startScanWithUuids(appIf, isServer, uuids);
}
+ @Override
+ public void startScanWithFilters(int appIf, boolean isServer, ScanSettings settings,
+ List<ScanFilter> filters) {
+ GattService service = getService();
+ if (service == null) return;
+ service.startScanWithFilters(appIf, isServer, settings, filters);
+ }
+
public void stopScan(int appIf, boolean isServer) {
GattService service = getService();
if (service == null) return;
service.stopScan(appIf, isServer);
}
- public void clientConnect(int clientIf, String address, boolean isDirect) {
+ public void clientConnect(int clientIf, String address, boolean isDirect, int transport) {
GattService service = getService();
if (service == null) return;
- service.clientConnect(clientIf, address, isDirect);
+ service.clientConnect(clientIf, address, isDirect, transport);
}
public void clientDisconnect(int clientIf, String address) {
@@ -433,10 +478,10 @@
service.unregisterServer(serverIf);
}
- public void serverConnect(int serverIf, String address, boolean isDirect) {
+ public void serverConnect(int serverIf, String address, boolean isDirect, int transport) {
GattService service = getService();
if (service == null) return;
- service.serverConnect(serverIf, address, isDirect);
+ service.serverConnect(serverIf, address, isDirect, transport);
}
public void serverDisconnect(int serverIf, String address) {
@@ -522,6 +567,22 @@
}
@Override
+ public void startMultiAdvertising(int clientIf, AdvertisementData advertiseData,
+ AdvertisementData scanResponse,
+ AdvertiseSettings settings) {
+ GattService service = getService();
+ if (service == null) return;
+ service.startMultiAdvertising(clientIf, advertiseData, scanResponse, settings);
+ }
+
+ @Override
+ public void stopMultiAdvertising(int clientIf) {
+ GattService service = getService();
+ if (service == null) return;
+ service.stopMultiAdvertising(clientIf);
+ }
+
+ @Override
public boolean isAdvertising() {
GattService service = getService();
if (service == null) return false;
@@ -573,10 +634,8 @@
@Override
public void removeAdvManufacturerCodeAndData(int manufacturerCode) throws RemoteException {
- GattService service = getService();
- if (service == null) return;
- service.removeAdvManufacturerCodeAndData(manufacturerCode);
}
+
};
/**************************************************************************
@@ -606,12 +665,19 @@
if (!client.isServer) {
ClientMap.App app = mClientMap.getById(client.appIf);
if (app != null) {
- try {
- app.callback.onScanResult(address, rssi, adv_data);
- } catch (RemoteException e) {
- Log.e(TAG, "Exception: " + e);
- mClientMap.remove(client.appIf);
- mScanQueue.remove(client);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter()
+ .getRemoteDevice(address);
+ long scanTimeMicros =
+ TimeUnit.NANOSECONDS.toMicros(SystemClock.elapsedRealtimeNanos());
+ ScanResult result = new ScanResult(device, adv_data, rssi, scanTimeMicros);
+ if (matchesFilters(client, result)) {
+ try {
+ app.callback.onScanResult(address, rssi, adv_data);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception: " + e);
+ mClientMap.remove(client.appIf);
+ mScanQueue.remove(client);
+ }
}
}
} else {
@@ -629,6 +695,20 @@
}
}
+ // Check if a scan record matches a specific filters.
+ private boolean matchesFilters(ScanClient client, ScanResult scanResult) {
+ if (client.filters == null || client.filters.isEmpty()) {
+ return true;
+ }
+ for (ScanFilter filter : client.filters) {
+ if (DBG) Log.d(TAG, "filter: " + filter.toString());
+ if (filter.matches(scanResult)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
void onClientRegistered(int status, int clientIf, long uuidLsb, long uuidMsb)
throws RemoteException {
UUID uuid = new UUID(uuidMsb, uuidLsb);
@@ -926,6 +1006,30 @@
}
}
+ void onScanFilterConfig(int action, int status) {
+ log("action = " + action + " status = " + status);
+ Message message = mStateMachine.obtainMessage();
+ if (status != 0) {
+ // If something is wrong with filter configuration, just start scan.
+ message.what = GattServiceStateMachine.ENABLE_BLE_SCAN;
+ mStateMachine.sendMessage(message);
+ return;
+ }
+ message.arg1 = action;
+ if (action == SCAN_FILTER_MODIFIED) {
+ if (mScanFilters.isEmpty()) {
+ message.what = GattServiceStateMachine.ENABLE_BLE_SCAN;
+ } else {
+ message.what = GattServiceStateMachine.ADD_SCAN_FILTER;
+ }
+ } else {
+ if (action == SCAN_FILTER_ENABLED) {
+ message.what = GattServiceStateMachine.ENABLE_BLE_SCAN;
+ }
+ }
+ mStateMachine.sendMessage(message);
+ }
+
void onAdvertiseCallback(int status, int clientIf) throws RemoteException {
if (DBG) Log.d(TAG, "onClientListen() status=" + status);
synchronized (mLock) {
@@ -974,6 +1078,15 @@
app.callback.onAdvertiseStateChange(mAdvertisingState, status);
}
+ void onMultipleAdvertiseCallback(int clientIf, int status) throws RemoteException {
+ ClientMap.App app = mClientMap.getById(clientIf);
+ if (app == null || app.callback == null) {
+ Log.e(TAG, "app or callback is null");
+ return;
+ }
+ app.callback.onMultiAdvertiseCallback(status);
+ }
+
void onConfigureMTU(int connId, int status, int mtu) throws RemoteException {
String address = mClientMap.addressByConnId(connId);
@@ -986,6 +1099,57 @@
}
}
+ void onClientEnable(int status, int clientIf) throws RemoteException{
+ if (DBG) Log.d(TAG, "onClientEnable() - clientIf=" + clientIf + ", status=" + status);
+ if (status == 0) {
+ Message message = mStateMachine.obtainMessage(
+ GattServiceStateMachine.SET_ADVERTISING_DATA);
+ message.arg1 = clientIf;
+ mStateMachine.sendMessage(message);
+ } else {
+ Message message =
+ mStateMachine.obtainMessage(GattServiceStateMachine.CANCEL_ADVERTISING);
+ message.arg1 = clientIf;
+ mStateMachine.sendMessage(message);
+ }
+ }
+
+ void onClientUpdate(int status, int client_if) throws RemoteException {
+ if (DBG) Log.d(TAG, "onClientUpdate() - client_if=" + client_if
+ + ", status=" + status);
+ }
+
+ void onClientData(int status, int clientIf) throws RemoteException{
+ if (DBG) Log.d(TAG, "onClientData() - clientIf=" + clientIf
+ + ", status=" + status);
+
+ ClientMap.App app = mClientMap.getById(clientIf);
+ if (app != null) {
+ if (status == 0) {
+ app.callback.onMultiAdvertiseCallback(AdvertiseCallback.SUCCESS);
+ } else {
+ app.callback.onMultiAdvertiseCallback(
+ AdvertiseCallback.ADVERTISE_FAILED_CONTROLLER_FAILURE);
+ }
+ }
+ }
+
+ void onClientDisable(int status, int client_if) throws RemoteException{
+ if (DBG) Log.d(TAG, "onClientDisable() - client_if=" + client_if
+ + ", status=" + status);
+
+ ClientMap.App app = mClientMap.getById(client_if);
+ if (app != null) {
+ Log.d(TAG, "Client app is not null!");
+ if (status == 0) {
+ app.callback.onMultiAdvertiseCallback(AdvertiseCallback.SUCCESS);
+ } else {
+ app.callback.onMultiAdvertiseCallback(
+ AdvertiseCallback.ADVERTISE_FAILED_CONTROLLER_FAILURE);
+ }
+ }
+ }
+
/**************************************************************************
* GATT Service functions - Shared CLIENT/SERVER
*************************************************************************/
@@ -1044,10 +1208,10 @@
if (DBG) Log.d(TAG, "startScan() - adding client=" + appIf);
mScanQueue.add(new ScanClient(appIf, isServer));
}
-
- gattClientScanNative(appIf, true);
+ configureScanParams();
}
+
void startScanWithUuids(int appIf, boolean isServer, UUID[] uuids) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
@@ -1057,8 +1221,136 @@
if (DBG) Log.d(TAG, "startScanWithUuids() - adding client=" + appIf);
mScanQueue.add(new ScanClient(appIf, isServer, uuids));
}
+ configureScanParams();
+ }
- gattClientScanNative(appIf, true);
+ void startScanWithFilters(int appIf, boolean isServer, ScanSettings settings,
+ List<ScanFilter> filters) {
+ if (DBG) Log.d(TAG, "start scan with filters " + filters.size());
+ enforceAdminPermission();
+ // TODO: use settings to configure scan params.
+ // TODO: move logic to state machine to avoid locking.
+ synchronized(mScanQueue) {
+ boolean isScaning = (!mScanQueue.isEmpty());
+ if (getScanClient(appIf, isServer) == null) {
+ if (DBG) Log.d(TAG, "startScan() - adding client=" + appIf);
+ mScanQueue.add(new ScanClient(appIf, isServer, settings, filters));
+ }
+ Set<ScanFilter> newFilters = configureScanFiltersLocked();
+ if (isScaning) {
+ // Reset scan filters if BLE scan was started and scan filters changed.
+ if (!Objects.deepEquals(newFilters, mScanFilters)) {
+ mScanFilters = newFilters;
+ // Restart scan using new filters.
+ sendStopScanMessage();
+ sendStartScanMessage(mScanFilters);
+ }
+ } else {
+ // Always start scanning with new filters if scan not started yet.
+ mScanFilters = newFilters;
+ sendStartScanMessage(mScanFilters);
+ }
+ }
+ }
+
+ void configureScanParams() {
+ if (DBG) Log.d(TAG, "configureScanParams() - queue=" + mScanQueue.size());
+ int curScanSetting = Integer.MIN_VALUE;
+
+ for(ScanClient client : mScanQueue) {
+ // ScanClient scan settings are assumed to be monotonically increasing in value for more
+ // power hungry(higher duty cycle) operation
+ if (client.settings.getScanMode() > curScanSetting) {
+ curScanSetting = client.settings.getScanMode();
+ }
+ }
+
+ if (DBG) Log.d(TAG, "configureScanParams() - ScanSetting Scan mode=" + curScanSetting +
+ " lastConfiguredScanSetting=" + lastConfiguredScanSetting);
+
+ if (curScanSetting != Integer.MIN_VALUE) {
+ if (curScanSetting != lastConfiguredScanSetting) {
+ int scanWindow, scanInterval;
+ switch (curScanSetting){
+ case ScanSettings.SCAN_MODE_LOW_POWER:
+ scanWindow = SCAN_MODE_LOW_POWER_WINDOW_MS;
+ scanInterval = SCAN_MODE_LOW_POWER_INTERVAL_MS;
+ break;
+ case ScanSettings.SCAN_MODE_BALANCED:
+ scanWindow = SCAN_MODE_BALANCED_WINDOW_MS;
+ scanInterval = SCAN_MODE_BALANCED_INTERVAL_MS;
+ break;
+ case ScanSettings.SCAN_MODE_LOW_LATENCY:
+ scanWindow = SCAN_MODE_LOW_LATENCY_WINDOW_MS;
+ scanInterval = SCAN_MODE_LOW_LATENCY_INTERVAL_MS;
+ break;
+ default:
+ Log.e(TAG, "Invalid value for curScanSetting " + curScanSetting);
+ scanWindow = SCAN_MODE_LOW_POWER_WINDOW_MS;
+ scanInterval = SCAN_MODE_LOW_POWER_INTERVAL_MS;
+ break;
+ }
+ // convert scanWindow and scanInterval from ms to LE scan units(0.625ms)
+ scanWindow = (scanWindow * 1000)/625;
+ scanInterval = (scanInterval * 1000)/625;
+ // Presence of scan clients means scan is active.
+ sendStopScanMessage();
+ gattSetScanParametersNative(scanInterval, scanWindow);
+ lastConfiguredScanSetting = curScanSetting;
+ mScanFilters.clear();
+ sendStartScanMessage(mScanFilters);
+ } else {
+ // Duty cycle did not change but scan filters changed.
+ if (!mScanFilters.isEmpty()) {
+ mScanFilters.clear();
+ sendStopScanMessage();
+ sendStartScanMessage(mScanFilters);
+ }
+ }
+ } else {
+ lastConfiguredScanSetting = curScanSetting;
+ mScanFilters.clear();
+ sendStopScanMessage();
+ if (DBG) Log.d(TAG, "configureScanParams() - queue emtpy, scan stopped");
+ }
+ }
+
+ private Set<ScanFilter> configureScanFiltersLocked() {
+ Set<ScanFilter> filters = new HashSet<ScanFilter>();
+ for (ScanClient client : mScanQueue) {
+ if (client.filters == null || client.filters.isEmpty()) {
+ filters.clear();
+ return filters;
+ }
+ filters.addAll(client.filters);
+ }
+
+ AdapterService adapter = AdapterService.getAdapterService();
+ if (filters.size() > adapter.getNumOfOffloadedScanFilterSupported()) {
+ if (DBG) Log.d(TAG, "filters size > " + adapter.getNumOfOffloadedScanFilterSupported()
+ + ", clearing filters");
+ filters = new HashSet<ScanFilter>();
+ }
+ return filters;
+ }
+
+ /**
+ * Returns whether scan filter is supported.
+ */
+ boolean isScanFilterSupported() {
+ AdapterService adapter = AdapterService.getAdapterService();
+ return adapter.getNumOfOffloadedScanFilterSupported() > 0;
+ }
+
+ private void sendStartScanMessage(Set<ScanFilter> filters) {
+ Message message = mStateMachine.obtainMessage(GattServiceStateMachine.START_BLE_SCAN);
+ message.obj = filters;
+ mStateMachine.sendMessage(message);
+ }
+
+ private void sendStopScanMessage() {
+ Message message = mStateMachine.obtainMessage(GattServiceStateMachine.STOP_BLE_SCAN);
+ mStateMachine.sendMessage(message);
}
void stopScan(int appIf, boolean isServer) {
@@ -1066,10 +1358,10 @@
if (DBG) Log.d(TAG, "stopScan() - queue=" + mScanQueue.size());
removeScanClient(appIf, isServer);
+ configureScanParams();
if (mScanQueue.isEmpty()) {
if (DBG) Log.d(TAG, "stopScan() - queue empty; stopping scan");
- gattClientScanNative(appIf, false);
}
}
@@ -1094,11 +1386,11 @@
gattClientUnregisterAppNative(clientIf);
}
- void clientConnect(int clientIf, String address, boolean isDirect) {
+ void clientConnect(int clientIf, String address, boolean isDirect, int transport) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (DBG) Log.d(TAG, "clientConnect() - address=" + address + ", isDirect=" + isDirect);
- gattClientConnectNative(clientIf, address, isDirect);
+ gattClientConnectNative(clientIf, address, isDirect, transport);
}
void clientDisconnect(int clientIf, String address) {
@@ -1165,13 +1457,13 @@
}
synchronized List<ParcelUuid> getAdvServiceUuids() {
- enforcePrivilegedPermission();;
+ enforcePrivilegedPermission();
boolean fullUuidFound = false;
List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
for (HandleMap.Entry entry : mHandleMap.mEntries) {
if (entry.advertisePreferred) {
ParcelUuid parcelUuid = new ParcelUuid(entry.uuid);
- if (BluetoothUuid.isShortUuid(parcelUuid)) {
+ if (BluetoothUuid.is16BitUuid(parcelUuid)) {
serviceUuids.add(parcelUuid);
} else {
// Allow at most one 128 bit service uuid to be advertised.
@@ -1243,6 +1535,21 @@
}
}
+ void startMultiAdvertising(int clientIf, AdvertisementData advertiseData,
+ AdvertisementData scanResponse, AdvertiseSettings settings) {
+ enforceAdminPermission();
+ Message message = mStateMachine.obtainMessage(GattServiceStateMachine.START_ADVERTISING);
+ message.obj = new AdvertiseClient(clientIf, settings, advertiseData, scanResponse);
+ mStateMachine.sendMessage(message);
+ }
+
+ void stopMultiAdvertising(int clientIf) {
+ enforceAdminPermission();
+ Message message = mStateMachine.obtainMessage(GattServiceStateMachine.STOP_ADVERTISING);
+ message.arg1 = clientIf;
+ mStateMachine.sendMessage(message);
+ }
+
List<String> getConnectedDevices() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
@@ -1655,11 +1962,11 @@
gattServerUnregisterAppNative(serverIf);
}
- void serverConnect(int serverIf, String address, boolean isDirect) {
+ void serverConnect(int serverIf, String address, boolean isDirect, int transport) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (DBG) Log.d(TAG, "serverConnect() - address=" + address);
- gattServerConnectNative(serverIf, address, isDirect);
+ gattServerConnectNative(serverIf, address, isDirect,transport);
}
void serverDisconnect(int serverIf, String address) {
@@ -1776,6 +2083,7 @@
}
}
+
/**************************************************************************
* Private functions
*************************************************************************/
@@ -1792,7 +2100,7 @@
int availableSize = ADVERTISING_PACKET_MAX_BYTES - ADVERTISING_FLAGS_BYTES;
for (ParcelUuid parcelUuid : getAdvServiceUuids()) {
- if (BluetoothUuid.isShortUuid(parcelUuid)) {
+ if (BluetoothUuid.is16BitUuid(parcelUuid)) {
availableSize -= FIELD_OVERHEAD_BYTES + SHORT_UUID_BYTES;
} else {
availableSize -= FIELD_OVERHEAD_BYTES + FULL_UUID_BYTES;
@@ -1807,6 +2115,10 @@
return availableSize;
}
+ private void enforceAdminPermission() {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+ }
+
// Enforce caller has BLUETOOTH_PRIVILEGED permission. A {@link SecurityException} will be
// thrown if the caller app does not have BLUETOOTH_PRIVILEGED permission.
private void enforcePrivilegedPermission() {
@@ -1889,7 +2201,8 @@
}
}
} else {
- gattServerStartServiceNative(serverIf, srvcHandle, (byte)2 /*BREDR/LE*/);
+ gattServerStartServiceNative(serverIf, srvcHandle,
+ (byte)BluetoothDevice.TRANSPORT_BREDR | BluetoothDevice.TRANSPORT_LE);
finished = true;
}
@@ -2017,10 +2330,10 @@
private native void gattClientUnregisterAppNative(int clientIf);
- private native void gattClientScanNative(int clientIf, boolean start);
+ private native void gattSetScanParametersNative(int scan_interval, int scan_window);
private native void gattClientConnectNative(int clientIf, String address,
- boolean isDirect);
+ boolean isDirect, int transport);
private native void gattClientDisconnectNative(int clientIf, String address,
int conn_id);
@@ -2093,7 +2406,7 @@
private native void gattServerUnregisterAppNative(int serverIf);
private native void gattServerConnectNative(int server_if, String address,
- boolean is_direct);
+ boolean is_direct, int transport);
private native void gattServerDisconnectNative(int serverIf, String address,
int conn_id);
diff --git a/src/com/android/bluetooth/gatt/GattServiceStateMachine.java b/src/com/android/bluetooth/gatt/GattServiceStateMachine.java
new file mode 100644
index 0000000..ba70c07
--- /dev/null
+++ b/src/com/android/bluetooth/gatt/GattServiceStateMachine.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.gatt;
+
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.AdvertisementData;
+import android.bluetooth.le.ScanFilter;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The state machine that handles state transitions for GATT related operations, including BLE scan,
+ * advertising and connection.
+ * <p>
+ * Scan state transitions are Idle -> ScanStarting -> Scanning -> Idle.
+ * <p>
+ * TODO:add connection states. move scan clients and related callbacks to state machine.
+ *
+ * @hide
+ */
+final class GattServiceStateMachine extends StateMachine {
+
+ /**
+ * Message to start BLE scan.
+ */
+ static final int START_BLE_SCAN = 1;
+ /**
+ * Message to stop BLE scan.
+ */
+ static final int STOP_BLE_SCAN = 2;
+
+ static final int START_ADVERTISING = 3;
+ static final int STOP_ADVERTISING = 4;
+
+ // Message for internal state transitions.
+ static final int ENABLE_BLE_SCAN = 11;
+ static final int ENABLE_ADVERTISING = 12;
+ static final int SET_ADVERTISING_DATA = 13;
+ static final int CANCEL_ADVERTISING = 14;
+ static final int CLEAR_SCAN_FILTER = 15;
+ static final int ADD_SCAN_FILTER = 16;
+ static final int ENABLE_SCAN_FILTER = 17;
+
+ // TODO: Remove this once stack callback is stable.
+ private static final int OPERATION_TIMEOUT = 101;
+ private static final int TIMEOUT_MILLIS = 3000;
+
+ private static final String TAG = "GattServiceStateMachine";
+ private static final boolean DBG = true;
+
+ private static final int ADVERTISING_INTERVAL_HIGH_MILLS = 1000;
+ private static final int ADVERTISING_INTERVAL_MEDIUM_MILLS = 250;
+ private static final int ADVERTISING_INTERVAL_LOW_MILLS = 100;
+ // Add some randomness to the advertising min/max interval so the controller can do some
+ // optimization.
+ private static final int ADVERTISING_INTERVAL_DELTA_UNIT = 10;
+ private static final int ADVERTISING_INTERVAL_MICROS_PER_UNIT = 625;
+
+ // The following constants should be kept the same as those defined in bt stack.
+ private static final int ADVERTISING_CHANNEL_37 = 1 << 0;
+ private static final int ADVERTISING_CHANNEL_38 = 1 << 1;
+ private static final int ADVERTISING_CHANNEL_39 = 1 << 2;
+ private static final int ADVERTISING_CHANNEL_ALL =
+ ADVERTISING_CHANNEL_37 | ADVERTISING_CHANNEL_38 | ADVERTISING_CHANNEL_39;
+
+ private static final int ADVERTISING_TX_POWER_MIN = 0;
+ private static final int ADVERTISING_TX_POWER_LOW = 1;
+ private static final int ADVERTISING_TX_POWER_MID = 2;
+ private static final int ADVERTISING_TX_POWER_UPPER = 3;
+ private static final int ADVERTISING_TX_POWER_MAX = 4;
+
+ // Note we don't expose connectable directed advertising to API.
+ private static final int ADVERTISING_EVENT_TYPE_CONNECTABLE = 0;
+ private static final int ADVERTISING_EVENT_TYPE_SCANNABLE = 2;
+ private static final int ADVERTISING_EVENT_TYPE_NON_CONNECTABLE = 3;
+
+ private final GattService mService;
+ private final Map<Integer, AdvertiseClient> mAdvertiseClients;
+ private final ScanFilterQueue mScanFilterQueue;
+ // Keep track of whether scan filters exist.
+ private boolean hasFilter = false;
+
+ // All states for the state machine.
+ private final Idle mIdle;
+ private final ScanStarting mScanStarting;
+ private final AdvertiseStarting mAdvertiseStarting;
+
+ private GattServiceStateMachine(GattService context) {
+ super(TAG);
+ mService = context;
+
+ // Add all possible states to the state machine.
+ mScanStarting = new ScanStarting();
+ mIdle = new Idle();
+ mAdvertiseStarting = new AdvertiseStarting();
+ mAdvertiseClients = new HashMap<Integer, AdvertiseClient>();
+ mScanFilterQueue = new ScanFilterQueue();
+
+ addState(mIdle);
+ addState(mScanStarting);
+ addState(mAdvertiseStarting);
+
+ // Initial state is idle.
+ setInitialState(mIdle);
+ }
+
+ /**
+ * Make a {@link GattServiceStateMachine} object from {@link GattService} and start the machine
+ * after it's created.
+ */
+ static GattServiceStateMachine make(GattService context) {
+ GattServiceStateMachine stateMachine = new GattServiceStateMachine(context);
+ stateMachine.start();
+ return stateMachine;
+ }
+
+ void doQuit() {
+ quitNow();
+ }
+
+ void cleanup() {
+ }
+
+ /**
+ * {@link Idle} state is the state where there is no scanning or advertising activity.
+ */
+ private class Idle extends State {
+ @Override
+ public void enter() {
+ if (DBG) {
+ log("enter scan idle state: " + getCurrentMessage().what);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) {
+ log("idle processing message: " + getCurrentMessage().what);
+ }
+
+ switch (message.what) {
+ case START_BLE_SCAN:
+ // TODO: check whether scan is already started for the app.
+ // Send the enable scan message to starting state for processing.
+ Message newMessage;
+ if (mService.isScanFilterSupported()) {
+ newMessage = obtainMessage(CLEAR_SCAN_FILTER);
+ } else {
+ newMessage = obtainMessage(ENABLE_BLE_SCAN);
+ }
+ newMessage.obj = message.obj;
+ sendMessage(newMessage);
+ transitionTo(mScanStarting);
+ break;
+ case STOP_BLE_SCAN:
+ // Note this should only happen no client is doing scans any more.
+ gattClientScanNative(false);
+ break;
+ case START_ADVERTISING:
+ AdvertiseClient client = (AdvertiseClient) message.obj;
+ if (mAdvertiseClients.containsKey(client.clientIf)) {
+ // do something.
+ loge("advertising already started for client : " + client.clientIf);
+ try {
+ mService.onMultipleAdvertiseCallback(client.clientIf,
+ AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
+ } catch (RemoteException e) {
+ loge("failed to start advertising", e);
+ }
+ transitionTo(mIdle);
+ break;
+ }
+ AdapterService adapter = AdapterService.getAdapterService();
+ int numOfAdvtInstances = adapter.getNumOfAdvertisementInstancesSupported();
+ if (mAdvertiseClients.size() >= numOfAdvtInstances) {
+ loge("too many advertisier, current size : " + mAdvertiseClients.size());
+ try {
+ mService.onMultipleAdvertiseCallback(client.clientIf,
+ AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS);
+ } catch (RemoteException e) {
+ loge("failed to start advertising", e);
+ }
+ transitionTo(mIdle);
+ break;
+ }
+ newMessage = obtainMessage(ENABLE_ADVERTISING);
+ newMessage.obj = message.obj;
+ sendMessage(newMessage);
+ transitionTo(mAdvertiseStarting);
+ break;
+ case STOP_ADVERTISING:
+ int clientIf = message.arg1;
+ if (!mAdvertiseClients.containsKey(clientIf)) {
+ try {
+ mService.onMultipleAdvertiseCallback(clientIf,
+ AdvertiseCallback.ADVERTISE_FAILED_NOT_STARTED);
+ } catch (RemoteException e) {
+ loge("failed to stop advertising", e);
+ }
+ }
+ log("disabling client" + clientIf);
+ gattClientDisableAdvNative(clientIf);
+ mAdvertiseClients.remove(clientIf);
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ /**
+ * State where scan status is being changing.
+ */
+ private class ScanStarting extends State {
+
+ @Override
+ public void enter() {
+ if (DBG) {
+ log("enter scan starting state: " + getCurrentMessage().what);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) {
+ log("Staring process message: " + message.what);
+ }
+ switch (message.what) {
+ case START_BLE_SCAN: // fall through
+ case STOP_BLE_SCAN:
+ // In scan starting state we don't handle start or stop scans. Defer processing
+ // the message until the state changes.
+ deferMessage(message);
+ break;
+ case CLEAR_SCAN_FILTER:
+ // TODO: Don't change anything if the filter did not change.
+ Object obj = message.obj;
+ if (obj == null || ((Set<ScanFilter>) obj).isEmpty()) {
+ mScanFilterQueue.clear();
+ hasFilter = false;
+ } else {
+ mScanFilterQueue.addAll((Set<ScanFilter>) message.obj);
+ hasFilter = true;
+ }
+ gattClientScanFilterClearNative();
+ sendMessageDelayed(OPERATION_TIMEOUT, TIMEOUT_MILLIS);
+ break;
+ case ADD_SCAN_FILTER:
+ if (mScanFilterQueue.isEmpty()) {
+ if (hasFilter) {
+ // Note we can only enable filter if there are filters added.
+ // TODO: Use callback action to detect if any filter has been added
+ // after stack provides different callback actions between filter
+ // cleared and filter added.
+ message = obtainMessage(ENABLE_SCAN_FILTER);
+ } else {
+ message = obtainMessage(ENABLE_BLE_SCAN);
+ }
+ sendMessage(message);
+ } else {
+ addFilterToController(mScanFilterQueue.pop());
+ }
+ break;
+ case ENABLE_SCAN_FILTER:
+ gattClientScanFilterEnableNative(true);
+ break;
+ case ENABLE_BLE_SCAN:
+ gattClientScanNative(true);
+ if (mService.isScanFilterSupported()) {
+ removeMessages(OPERATION_TIMEOUT);
+ }
+ transitionTo(mIdle);
+ break;
+ case OPERATION_TIMEOUT:
+ transitionTo(mIdle);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ private void addFilterToController(ScanFilterQueue.Entry entry) {
+ switch (entry.type) {
+ case ScanFilterQueue.TYPE_DEVICE_ADDRESS:
+ gattClientScanFilterAddNative(entry.type, 0, 0, 0, 0, 0, 0,
+ "", entry.address, entry.addr_type, new byte[0]);
+ break;
+
+ case ScanFilterQueue.TYPE_SERVICE_DATA:
+ gattClientScanFilterAddNative(entry.type, 0, 0, 0, 0, 0, 0, "",
+ "", (byte) 0, new byte[0]);
+ break;
+
+ case ScanFilterQueue.TYPE_SERVICE_UUID:
+ case ScanFilterQueue.TYPE_SOLICIT_UUID:
+ gattClientScanFilterAddNative(entry.type, 0, 0,
+ entry.uuid.getLeastSignificantBits(),
+ entry.uuid.getMostSignificantBits(),
+ entry.uuid_mask.getLeastSignificantBits(),
+ entry.uuid_mask.getMostSignificantBits(),
+ "", "", (byte) 0, new byte[0]);
+ break;
+
+ case ScanFilterQueue.TYPE_LOCAL_NAME:
+ gattClientScanFilterAddNative(entry.type, 0, 0, 0, 0, 0, 0,
+ entry.name, "", (byte) 0, new byte[0]);
+ break;
+
+ case ScanFilterQueue.TYPE_MANUFACTURER_DATA:
+ {
+ int len = entry.data.length;
+ byte[] data = new byte[len * 2];
+ for (int i = 0; i != len; ++i)
+ {
+ data[i] = entry.data[i];
+ if (entry.data_mask.length == len) {
+ data[i + len] = entry.data_mask[i];
+ } else {
+ data[i + len] = (byte) 0xFF;
+ }
+ }
+ gattClientScanFilterAddNative(entry.type, entry.company, entry.company_mask, 0, 0,
+ 0,
+ 0, "", "", (byte) 0, entry.data);
+ break;
+ }
+ }
+ }
+
+ private class AdvertiseStarting extends State {
+
+ @Override
+ public void enter() {
+ if (DBG) {
+ log("enter advertising state: " + getCurrentMessage().what);
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ log("advertising starting " + message.what);
+ switch (message.what) {
+ case START_ADVERTISING:
+ case STOP_ADVERTISING:
+ deferMessage(message);
+ break;
+ case ENABLE_ADVERTISING:
+ AdvertiseClient client = (AdvertiseClient) message.obj;
+ mAdvertiseClients.put(client.clientIf, client);
+ enableAdvertising(client);
+ sendMessageDelayed(OPERATION_TIMEOUT, client.clientIf, TIMEOUT_MILLIS);
+ break;
+ case SET_ADVERTISING_DATA:
+ int clientIf = message.arg1;
+ log("setting advertisement: " + clientIf);
+ client = mAdvertiseClients.get(clientIf);
+ setAdvertisingData(clientIf, client.advertiseData, false);
+ if (client.scanResponse != null) {
+ setAdvertisingData(clientIf, client.scanResponse, true);
+ }
+ removeMessages(OPERATION_TIMEOUT);
+ transitionTo(mIdle);
+ break;
+ case CANCEL_ADVERTISING:
+ case OPERATION_TIMEOUT:
+ clientIf = message.arg1;
+ try {
+ mService.onMultipleAdvertiseCallback(clientIf,
+ AdvertiseCallback.ADVERTISE_FAILED_CONTROLLER_FAILURE);
+ } catch (RemoteException e) {
+ loge("failed to start advertising", e);
+ }
+ transitionTo(mIdle);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ private void setAdvertisingData(int clientIf, AdvertisementData data, boolean isScanResponse) {
+ if (data == null) {
+ return;
+ }
+ boolean includeName = false;
+ boolean includeTxPower = data.getIncludeTxPowerLevel();
+ int appearance = 0;
+ byte[] manufacturerData = data.getManufacturerSpecificData() == null ? new byte[0]
+ : data.getManufacturerSpecificData();
+ byte[] serviceData = data.getServiceData() == null ? new byte[0] : data.getServiceData();
+
+ byte[] serviceUuids;
+ if (data.getServiceUuids() == null) {
+ serviceUuids = new byte[0];
+ } else {
+ ByteBuffer advertisingUuidBytes = ByteBuffer.allocate(
+ data.getServiceUuids().size() * 16)
+ .order(ByteOrder.LITTLE_ENDIAN);
+ for (ParcelUuid parcelUuid : data.getServiceUuids()) {
+ UUID uuid = parcelUuid.getUuid();
+ // Least significant bits first as the advertising uuid should be in little-endian.
+ advertisingUuidBytes.putLong(uuid.getLeastSignificantBits())
+ .putLong(uuid.getMostSignificantBits());
+ }
+ serviceUuids = advertisingUuidBytes.array();
+ }
+ log("isScanResponse " + isScanResponse + " manu data " + Arrays.toString(manufacturerData));
+ log("include tx power " + includeTxPower);
+ gattClientSetAdvDataNative(clientIf, isScanResponse, includeName, includeTxPower,
+ appearance,
+ manufacturerData, serviceData, serviceUuids);
+ }
+
+ private void enableAdvertising(AdvertiseClient client) {
+ int clientIf = client.clientIf;
+ log("enabling advertisement: " + clientIf);
+ int minAdvertiseUnit = (int) getAdvertisingIntervalUnit(client.settings);
+ int maxAdvertiseUnit = minAdvertiseUnit + ADVERTISING_INTERVAL_DELTA_UNIT;
+ log("enabling advertising: " + clientIf + "minAdvertisingMills " + minAdvertiseUnit);
+ gattClientEnableAdvNative(
+ clientIf,
+ minAdvertiseUnit, maxAdvertiseUnit,
+ getAdvertisingEventType(client.settings),
+ ADVERTISING_CHANNEL_ALL,
+ getTxPowerLevel(client.settings));
+ }
+
+ // Convert settings tx power level to stack tx power level.
+ private int getTxPowerLevel(AdvertiseSettings settings) {
+ switch (settings.getTxPowerLevel()) {
+ case AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW:
+ return ADVERTISING_TX_POWER_MIN;
+ case AdvertiseSettings.ADVERTISE_TX_POWER_LOW:
+ return ADVERTISING_TX_POWER_LOW;
+ case AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM:
+ return ADVERTISING_TX_POWER_MID;
+ case AdvertiseSettings.ADVERTISE_TX_POWER_HIGH:
+ return ADVERTISING_TX_POWER_UPPER;
+ default:
+ // Shouldn't happen, just in case.
+ return ADVERTISING_TX_POWER_MID;
+ }
+ }
+
+ // Convert advertising event type to stack advertising event type.
+ private int getAdvertisingEventType(AdvertiseSettings settings) {
+ switch (settings.getType()) {
+ case AdvertiseSettings.ADVERTISE_TYPE_CONNECTABLE:
+ return ADVERTISING_EVENT_TYPE_CONNECTABLE;
+ case AdvertiseSettings.ADVERTISE_TYPE_SCANNABLE:
+ return ADVERTISING_EVENT_TYPE_SCANNABLE;
+ case AdvertiseSettings.ADVERTISE_TYPE_NON_CONNECTABLE:
+ return ADVERTISING_EVENT_TYPE_NON_CONNECTABLE;
+ default:
+ // Should't happen, just in case.
+ return ADVERTISING_EVENT_TYPE_NON_CONNECTABLE;
+ }
+ }
+
+ // Convert advertising milliseconds to advertising units(one unit is 0.625 millisecond).
+ private long getAdvertisingIntervalUnit(AdvertiseSettings settings) {
+ switch (settings.getMode()) {
+ case AdvertiseSettings.ADVERTISE_MODE_LOW_POWER:
+ return millsToUnit(ADVERTISING_INTERVAL_LOW_MILLS);
+ case AdvertiseSettings.ADVERTISE_MODE_BALANCED:
+ return millsToUnit(ADVERTISING_INTERVAL_MEDIUM_MILLS);
+ case AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY:
+ return millsToUnit(ADVERTISING_INTERVAL_HIGH_MILLS);
+ default:
+ // Shouldn't happen, just in case.
+ return millsToUnit(ADVERTISING_INTERVAL_LOW_MILLS);
+ }
+ }
+
+ private long millsToUnit(int millisecond) {
+ return TimeUnit.MILLISECONDS.toMicros(millisecond) / ADVERTISING_INTERVAL_MICROS_PER_UNIT;
+ }
+
+ // Native functions.
+ private native void gattClientScanNative(boolean start);
+
+ private native void gattClientEnableAdvNative(int client_if,
+ int min_interval, int max_interval, int adv_type, int chnl_map,
+ int tx_power);
+
+ private native void gattClientUpdateAdvNative(int client_if,
+ int min_interval, int max_interval, int adv_type, int chnl_map,
+ int tx_power);
+
+ private native void gattClientSetAdvDataNative(int client_if,
+ boolean set_scan_rsp, boolean incl_name, boolean incl_txpower, int appearance,
+ byte[] manufacturer_data, byte[] service_data, byte[] service_uuid);
+
+ private native void gattClientDisableAdvNative(int client_if);
+
+ private native void gattClientScanFilterAddNative(int type,
+ int company_id, int company_mask, long uuid_lsb, long uuid_msb,
+ long uuid_mask_lsb, long uuid_mask_msb,
+ String name, String address, byte addr_type, byte[] data);
+
+ private native void gattClientScanFilterEnableNative(boolean enable);
+
+ private native void gattClientScanFilterClearNative();
+}
diff --git a/src/com/android/bluetooth/gatt/ScanClient.java b/src/com/android/bluetooth/gatt/ScanClient.java
index 3b1e421..088dfa5 100644
--- a/src/com/android/bluetooth/gatt/ScanClient.java
+++ b/src/com/android/bluetooth/gatt/ScanClient.java
@@ -16,26 +16,46 @@
package com.android.bluetooth.gatt;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanSettings;
+
+import java.util.List;
import java.util.UUID;
/**
* Helper class identifying a client that has requested LE scan results.
+ *
* @hide
*/
-/*package*/ class ScanClient {
+/* package */class ScanClient {
int appIf;
boolean isServer;
UUID[] uuids;
+ ScanSettings settings;
+ List<ScanFilter> filters;
+ private static final ScanSettings DEFAULT_SCAN_SETTINGS = new ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
ScanClient(int appIf, boolean isServer) {
- this.appIf = appIf;
- this.isServer = isServer;
- this.uuids = new UUID[0];
+ this(appIf, isServer, new UUID[0], DEFAULT_SCAN_SETTINGS, null);
}
ScanClient(int appIf, boolean isServer, UUID[] uuids) {
+ this(appIf, isServer, uuids, DEFAULT_SCAN_SETTINGS, null);
+ }
+
+ ScanClient(int appIf, boolean isServer, ScanSettings settings,
+ List<ScanFilter> filters) {
+ this(appIf, isServer, new UUID[0], settings, filters);
+ }
+
+ private ScanClient(int appIf, boolean isServer, UUID[] uuids, ScanSettings settings,
+ List<ScanFilter> filters) {
this.appIf = appIf;
this.isServer = isServer;
this.uuids = uuids;
+ this.settings = settings;
+ this.filters = filters;
+
}
}
diff --git a/src/com/android/bluetooth/gatt/ScanFilterQueue.java b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
new file mode 100644
index 0000000..9821c59
--- /dev/null
+++ b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.gatt;
+
+import android.bluetooth.le.ScanFilter;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Helper class used to manage advertisement package filters.
+ *
+ * @hide
+ */
+/* package */class ScanFilterQueue {
+ public static final int TYPE_DEVICE_ADDRESS = 0;
+ public static final int TYPE_SERVICE_DATA = 1;
+ public static final int TYPE_SERVICE_UUID = 2;
+ public static final int TYPE_SOLICIT_UUID = 3;
+ public static final int TYPE_LOCAL_NAME = 4;
+ public static final int TYPE_MANUFACTURER_DATA = 5;
+
+ // Values defined in bluedroid.
+ private static final byte DEVICE_TYPE_ALL = 1;
+
+ class Entry {
+ public String address;
+ public byte addr_type;
+ public byte type;
+ public UUID uuid;
+ public UUID uuid_mask;
+ public String name;
+ public int company;
+ public int company_mask;
+ public byte[] data;
+ public byte[] data_mask;
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(address, addr_type, type, uuid, uuid_mask, name, company,
+ company_mask, data, data_mask);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ Entry other = (Entry)obj;
+ return Objects.equals(address, other.address) &&
+ addr_type == other.addr_type && type == other.type &&
+ Objects.equals(uuid, other.uuid) &&
+ Objects.equals(uuid_mask, other.uuid_mask) &&
+ Objects.equals(name, other.name) &&
+ company == other.company && company_mask == other.company_mask &&
+ Objects.deepEquals(data, other.data) &&
+ Objects.deepEquals(data_mask, other.data_mask);
+ }
+ }
+
+ private Set<Entry> mEntries = new HashSet<Entry>();
+
+ void addDeviceAddress(String address, byte type) {
+ Entry entry = new Entry();
+ entry.type = TYPE_DEVICE_ADDRESS;
+ entry.address = address;
+ entry.addr_type = type;
+ mEntries.add(entry);
+ }
+
+ void addServiceChanged() {
+ Entry entry = new Entry();
+ entry.type = TYPE_SERVICE_DATA;
+ mEntries.add(entry);
+ }
+
+ void addUuid(UUID uuid) {
+ Entry entry = new Entry();
+ entry.type = TYPE_SERVICE_UUID;
+ entry.uuid = uuid;
+ entry.uuid_mask = new UUID(0, 0);
+ mEntries.add(entry);
+ }
+
+ void addUuid(UUID uuid, UUID uuid_mask) {
+ Entry entry = new Entry();
+ entry.type = TYPE_SERVICE_UUID;
+ entry.uuid = uuid;
+ entry.uuid_mask = uuid_mask;
+ mEntries.add(entry);
+ }
+
+ void addSolicitUuid(UUID uuid) {
+ Entry entry = new Entry();
+ entry.type = TYPE_SOLICIT_UUID;
+ entry.uuid = uuid;
+ mEntries.add(entry);
+ }
+
+ void addName(String name) {
+ Entry entry = new Entry();
+ entry.type = TYPE_LOCAL_NAME;
+ entry.name = name;
+ mEntries.add(entry);
+ }
+
+ void addManufacturerData(int company, byte[] data) {
+ Entry entry = new Entry();
+ entry.type = TYPE_MANUFACTURER_DATA;
+ entry.company = company;
+ entry.company_mask = 0xFFFF;
+ entry.data = data;
+ entry.data_mask = new byte[data.length];
+ Arrays.fill(entry.data_mask, (byte) 0xFF);
+ mEntries.add(entry);
+ }
+
+ void addManufacturerData(int company, int company_mask, byte[] data, byte[] data_mask) {
+ Entry entry = new Entry();
+ entry.type = TYPE_MANUFACTURER_DATA;
+ entry.company = company;
+ entry.company_mask = company_mask;
+ entry.data = data;
+ entry.data_mask = data_mask;
+ mEntries.add(entry);
+ }
+
+ Entry pop() {
+ if (isEmpty()) {
+ return null;
+ }
+ Iterator<Entry> iterator = mEntries.iterator();
+ Entry entry = iterator.next();
+ iterator.remove();
+ return entry;
+ }
+
+ boolean isEmpty() {
+ return mEntries.isEmpty();
+ }
+
+ void clearUuids() {
+ for (Iterator<Entry> it = mEntries.iterator(); it.hasNext();) {
+ Entry entry = it.next();
+ if (entry.type == TYPE_SERVICE_UUID)
+ it.remove();
+ }
+ }
+
+ void clear() {
+ mEntries.clear();
+ }
+
+ void addAll(Set<ScanFilter> filters) {
+ if (filters == null)
+ return;
+ for (ScanFilter filter : filters) {
+ if (filter.getLocalName() != null) {
+ addName(filter.getLocalName());
+ }
+ if (filter.getDeviceAddress() != null) {
+ addDeviceAddress(filter.getDeviceAddress(), DEVICE_TYPE_ALL);
+ }
+ if (filter.getServiceUuid() != null) {
+ if (filter.getServiceUuidMask() == null) {
+ addUuid(filter.getServiceUuid().getUuid());
+ } else {
+ addUuid(filter.getServiceUuid().getUuid(),
+ filter.getServiceUuidMask().getUuid());
+ }
+ }
+ if (filter.getManufacturerData() != null) {
+ if (filter.getManufacturerDataMask() == null) {
+ addManufacturerData(filter.getManufacturerId(), filter.getManufacturerData());
+ } else {
+ addManufacturerData(filter.getManufacturerId(), 0xFFFF,
+ filter.getManufacturerData(), filter.getManufacturerDataMask());
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/hdp/HealthService.java b/src/com/android/bluetooth/hdp/HealthService.java
index b418dcb..cef06e8 100644
--- a/src/com/android/bluetooth/hdp/HealthService.java
+++ b/src/com/android/bluetooth/hdp/HealthService.java
@@ -110,15 +110,16 @@
}
private void cleanupApps(){
- Iterator <Map.Entry<BluetoothHealthAppConfiguration,AppInfo>>it
- = mApps.entrySet().iterator();
- while (it.hasNext())
- {
- Map.Entry<BluetoothHealthAppConfiguration,AppInfo> entry = it.next();
- AppInfo appInfo = entry.getValue();
- if (appInfo != null)
- appInfo.cleanup();
- it.remove();
+ if (mApps != null) {
+ Iterator <Map.Entry<BluetoothHealthAppConfiguration,AppInfo>>it
+ = mApps.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<BluetoothHealthAppConfiguration,AppInfo> entry = it.next();
+ AppInfo appInfo = entry.getValue();
+ if (appInfo != null)
+ appInfo.cleanup();
+ it.remove();
+ }
}
}
protected boolean cleanup() {
diff --git a/src/com/android/bluetooth/hfp/AtPhonebook.java b/src/com/android/bluetooth/hfp/AtPhonebook.java
index c9b58de..b82df88 100755
--- a/src/com/android/bluetooth/hfp/AtPhonebook.java
+++ b/src/com/android/bluetooth/hfp/AtPhonebook.java
@@ -31,6 +31,8 @@
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
+import com.android.bluetooth.Utils;
+
import java.util.HashMap;
@@ -147,7 +149,11 @@
mCpbrIndex1 = mCpbrIndex2 = cpbrIndex;
}
- public void handleCscsCommand(String atString, int type)
+ private byte[] getByteAddress(BluetoothDevice device) {
+ return Utils.getBytesFromAddress(device.getAddress());
+ }
+
+ public void handleCscsCommand(String atString, int type, BluetoothDevice device)
{
log("handleCscsCommand - atString = " +atString);
// Select Character Set
@@ -169,7 +175,8 @@
log("handleCscsCommand - Set Command");
String[] args = atString.split("=");
if (args.length < 2 || !(args[1] instanceof String)) {
- mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+ mStateMachine.atResponseCodeNative(atCommandResult,
+ atCommandErrorCode, getByteAddress(device));
break;
}
String characterSet = ((atString.split("="))[1]);
@@ -188,11 +195,12 @@
atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
}
if (atCommandResponse != null)
- mStateMachine.atResponseStringNative(atCommandResponse);
- mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+ mStateMachine.atResponseStringNative(atCommandResponse, getByteAddress(device));
+ mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
+ getByteAddress(device));
}
- public void handleCpbsCommand(String atString, int type) {
+ public void handleCpbsCommand(String atString, int type, BluetoothDevice device) {
// Select PhoneBook memory Storage
log("handleCpbsCommand - atString = " +atString);
int atCommandResult = HeadsetHalConstants.AT_RESPONSE_ERROR;
@@ -248,8 +256,9 @@
atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
}
if (atCommandResponse != null)
- mStateMachine.atResponseStringNative(atCommandResponse);
- mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+ mStateMachine.atResponseStringNative(atCommandResponse, getByteAddress(device));
+ mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
+ getByteAddress(device));
}
public void handleCpbrCommand(String atString, int type, BluetoothDevice remoteDevice) {
@@ -272,7 +281,8 @@
PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true); //false);
if (pbr == null) {
atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
- mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+ mStateMachine.atResponseCodeNative(atCommandResult,
+ atCommandErrorCode, getByteAddress(remoteDevice));
break;
}
size = pbr.cursor.getCount();
@@ -287,8 +297,10 @@
atCommandResponse = "+CPBR: (1-" + size + "),30,30";
atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
if (atCommandResponse != null)
- mStateMachine.atResponseStringNative(atCommandResponse);
- mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+ mStateMachine.atResponseStringNative(atCommandResponse,
+ getByteAddress(remoteDevice));
+ mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
+ getByteAddress(remoteDevice));
break;
// Read PhoneBook Entries
case TYPE_READ:
@@ -299,14 +311,16 @@
if (mCpbrIndex1 != -1) {
/* handling a CPBR at the moment, reject this CPBR command */
atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
- mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+ mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
+ getByteAddress(remoteDevice));
break;
}
// Parse indexes
int index1;
int index2;
if ((atString.split("=")).length < 2) {
- mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+ mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
+ getByteAddress(remoteDevice));
break;
}
String atCommand = (atString.split("="))[1];
@@ -324,7 +338,8 @@
catch (Exception e) {
log("handleCpbrCommand - exception - invalid chars: " + e.toString());
atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
- mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+ mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
+ getByteAddress(remoteDevice));
break;
}
mCpbrIndex1 = index1;
@@ -333,9 +348,10 @@
if (checkAccessPermission(remoteDevice)) {
mCheckingAccessPermission = false;
- atCommandResult = processCpbrCommand();
+ atCommandResult = processCpbrCommand(remoteDevice);
mCpbrIndex1 = mCpbrIndex2 = -1;
- mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+ mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
+ getByteAddress(remoteDevice));
break;
}
// no reponse here, will continue the process in handleAccessPermissionResult
@@ -344,7 +360,8 @@
default:
log("handleCpbrCommand - invalid chars");
atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
- mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+ mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
+ getByteAddress(remoteDevice));
}
}
@@ -446,7 +463,7 @@
}
// process CPBR command after permission check
- /*package*/ int processCpbrCommand()
+ /*package*/ int processCpbrCommand(BluetoothDevice device)
{
log("processCpbrCommand");
int atCommandResult = HeadsetHalConstants.AT_RESPONSE_ERROR;
@@ -550,7 +567,7 @@
record = record + "\r\n\r\n";
atCommandResponse = record;
log("processCpbrCommand - atCommandResponse = "+atCommandResponse);
- mStateMachine.atResponseStringNative(atCommandResponse);
+ mStateMachine.atResponseStringNative(atCommandResponse, getByteAddress(device));
if (!pbr.cursor.moveToNext()) {
break;
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
index bf42c11..eb6d8e5 100755
--- a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
+++ b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
@@ -22,6 +22,8 @@
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.util.Log;
+import android.bluetooth.BluetoothDevice;
+
// Note:
// All methods in this class are not thread safe, donot call them from
@@ -327,10 +329,12 @@
}
class HeadsetVendorSpecificResultCode {
+ BluetoothDevice mDevice;
String mCommand;
String mArg;
- public HeadsetVendorSpecificResultCode(String command, String arg) {
+ public HeadsetVendorSpecificResultCode(BluetoothDevice device, String command, String arg) {
+ mDevice = device;
mCommand = command;
mArg = arg;
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index d66009c..eae0281 100755
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -149,6 +149,7 @@
public boolean disconnect(BluetoothDevice device) {
HeadsetService service = getService();
if (service == null) return false;
+ if (DBG) Log.d(TAG, "disconnect in HeadsetService");
return service.disconnect(device);
}
@@ -323,6 +324,7 @@
}
int connectionState = mStateMachine.getConnectionState(device);
+ Log.d(TAG,"connectionState = " + connectionState);
if (connectionState == BluetoothProfile.STATE_CONNECTED ||
connectionState == BluetoothProfile.STATE_CONNECTING) {
return false;
@@ -507,7 +509,7 @@
return false;
}
mStateMachine.sendMessage(HeadsetStateMachine.SEND_VENDOR_SPECIFIC_RESULT_CODE,
- new HeadsetVendorSpecificResultCode(command, arg));
+ new HeadsetVendorSpecificResultCode(device, command, arg));
return true;
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
old mode 100755
new mode 100644
index 7d9dc92..cc5cce8
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -66,10 +66,11 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import android.os.SystemProperties;
final class HeadsetStateMachine extends StateMachine {
private static final String TAG = "HeadsetStateMachine";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
//For Debugging only
private static int sRefCount=0;
@@ -99,14 +100,25 @@
private static final int STACK_EVENT = 101;
private static final int DIALING_OUT_TIMEOUT = 102;
private static final int START_VR_TIMEOUT = 103;
+ private static final int CLCC_RSP_TIMEOUT = 104;
private static final int CONNECT_TIMEOUT = 201;
private static final int DIALING_OUT_TIMEOUT_VALUE = 10000;
private static final int START_VR_TIMEOUT_VALUE = 5000;
+ private static final int CLCC_RSP_TIMEOUT_VALUE = 5000;
+
+ // Max number of HF connections at any time
+ private int max_hf_connections = 2;
// Keys are AT commands, and values are the company IDs.
private static final Map<String, Integer> VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID;
+ // Hash for storing the Audio Parameters like NREC for connected headsets
+ private HashMap<BluetoothDevice, HashMap> mHeadsetAudioParam =
+ new HashMap<BluetoothDevice, HashMap>();
+ // Hash for storing the Remotedevice BRSF
+ private HashMap<BluetoothDevice, Integer> mHeadsetBrsf =
+ new HashMap<BluetoothDevice, Integer>();
private static final ParcelUuid[] HEADSET_UUIDS = {
BluetoothUuid.HSP,
@@ -117,6 +129,8 @@
private Pending mPending;
private Connected mConnected;
private AudioOn mAudioOn;
+ // Multi HFP: add new class object
+ private MultiHFPending mMultiHFPending;
private HeadsetService mService;
private PowerManager mPowerManager;
@@ -162,6 +176,12 @@
private BluetoothDevice mCurrentDevice = null;
private BluetoothDevice mTargetDevice = null;
private BluetoothDevice mIncomingDevice = null;
+ private BluetoothDevice mActiveScoDevice = null;
+ private BluetoothDevice mMultiDisconnectDevice = null;
+
+ // Multi HFP: Connected devices list holds all currently connected headsets
+ private ArrayList<BluetoothDevice> mConnectedDevicesList =
+ new ArrayList<BluetoothDevice>();
static {
classInitNative();
@@ -194,13 +214,19 @@
Log.e(TAG, "Could not bind to Bluetooth Headset Phone Service");
}
- initializeNative();
+ String max_hfp_clients = SystemProperties.get("bt.max.hfpclient.connections");
+ if (!max_hfp_clients.isEmpty() && (Integer.parseInt(max_hfp_clients) == 2))
+ max_hf_connections = Integer.parseInt(max_hfp_clients);
+ Log.d(TAG, "max_hf_connections = " + max_hf_connections);
+ initializeNative(max_hf_connections);
mNativeAvailable=true;
mDisconnected = new Disconnected();
mPending = new Pending();
mConnected = new Connected();
mAudioOn = new AudioOn();
+ // Multi HFP: initialise new class variable
+ mMultiHFPending = new MultiHFPending();
if (sVoiceCommandIntent == null) {
sVoiceCommandIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
@@ -211,6 +237,8 @@
addState(mPending);
addState(mConnected);
addState(mAudioOn);
+ // Multi HFP: add State
+ addState(mMultiHFPending);
setInitialState(mDisconnected);
}
@@ -246,6 +274,15 @@
if (mPhonebook != null) {
mPhonebook.cleanup();
}
+ if (mHeadsetAudioParam != null) {
+ mHeadsetAudioParam.clear();
+ }
+ if (mHeadsetBrsf != null) {
+ mHeadsetBrsf.clear();
+ }
+ if (mConnectedDevicesList != null) {
+ mConnectedDevicesList.clear();
+ }
if (mNativeAvailable) {
cleanupNative();
mNativeAvailable = false;
@@ -255,16 +292,22 @@
private class Disconnected extends State {
@Override
public void enter() {
- log("Enter Disconnected: " + getCurrentMessage().what);
+ log("Enter Disconnected: " + getCurrentMessage().what +
+ ", size: " + mConnectedDevicesList.size());
mPhonebook.resetAtState();
mPhoneState.listenForPhoneState(false);
+ mVoiceRecognitionStarted = false;
+ mWaitingForVoiceRecognition = false;
}
@Override
public boolean processMessage(Message message) {
- log("Disconnected process message: " + message.what);
- if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) {
- Log.e(TAG, "ERROR: current, target, or mIncomingDevice not null in Disconnected");
+ log("Disconnected process message: " + message.what +
+ ", size: " + mConnectedDevicesList.size());
+ if (mConnectedDevicesList.size() != 0 || mTargetDevice != null ||
+ mIncomingDevice != null) {
+ Log.e(TAG, "ERROR: mConnectedDevicesList is not empty," +
+ "target, or mIncomingDevice not null in Disconnected");
return NOT_HANDLED;
}
@@ -287,7 +330,9 @@
}
// TODO(BT) remove CONNECT_TIMEOUT when the stack
// sends back events consistently
- sendMessageDelayed(CONNECT_TIMEOUT, 30000);
+ Message m = obtainMessage(CONNECT_TIMEOUT);
+ m.obj = device;
+ sendMessageDelayed(m, 30000);
break;
case DISCONNECT:
// ignore
@@ -326,12 +371,14 @@
// in Disconnected state
private void processConnectionEvent(int state, BluetoothDevice device) {
+ Log.d(TAG, "processConnectionEvent state = " + state +
+ ", device = " + device);
switch (state) {
case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
Log.w(TAG, "Ignore HF DISCONNECTED event, device: " + device);
break;
case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
- if (okToConnect(device)){
+ if (okToConnect(device)) {
Log.i(TAG,"Incoming Hf accepted");
broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
@@ -346,7 +393,7 @@
disconnectHfpNative(getByteAddress(device));
// the other profile connection should be initiated
AdapterService adapterService = AdapterService.getAdapterService();
- if ( adapterService != null) {
+ if (adapterService != null) {
adapterService.connectOtherProfile(device,
AdapterService.PROFILE_CONN_REJECTED);
}
@@ -354,15 +401,20 @@
break;
case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
Log.w(TAG, "HFP Connected from Disconnected state");
- if (okToConnect(device)){
+ if (okToConnect(device)) {
Log.i(TAG,"Incoming Hf accepted");
broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_DISCONNECTED);
synchronized (HeadsetStateMachine.this) {
+ if (!mConnectedDevicesList.contains(device)) {
+ mConnectedDevicesList.add(device);
+ Log.d(TAG, "device " + device.getAddress() +
+ " is adding in Disconnected state");
+ }
mCurrentDevice = device;
transitionTo(mConnected);
}
- configAudioParameters();
+ configAudioParameters(device);
} else {
//reject the connection and stay in Disconnected state itself
Log.i(TAG,"Incoming Hf rejected. priority=" + mService.getPriority(device) +
@@ -370,7 +422,7 @@
disconnectHfpNative(getByteAddress(device));
// the other profile connection should be initiated
AdapterService adapterService = AdapterService.getAdapterService();
- if ( adapterService != null) {
+ if (adapterService != null) {
adapterService.connectOtherProfile(device,
AdapterService.PROFILE_CONN_REJECTED);
}
@@ -394,7 +446,8 @@
@Override
public boolean processMessage(Message message) {
- log("Pending process message: " + message.what);
+ log("Pending process message: " + message.what + ", size: "
+ + mConnectedDevicesList.size());
boolean retValue = HANDLED;
switch(message.what) {
@@ -434,7 +487,11 @@
}
switch (event.type) {
case EVENT_TYPE_CONNECTION_STATE_CHANGED:
- removeMessages(CONNECT_TIMEOUT);
+ BluetoothDevice device1 = getDeviceForMessage(CONNECT_TIMEOUT);
+ if (device1 != null && device1.equals(event.device)) {
+ Log.d(TAG, "remove connect timeout for device = " + device1);
+ removeMessages(CONNECT_TIMEOUT);
+ }
processConnectionEvent(event.valueInt, event.device);
break;
default:
@@ -450,10 +507,21 @@
// in Pending state
private void processConnectionEvent(int state, BluetoothDevice device) {
+ Log.d(TAG, "processConnectionEvent state = " + state +
+ ", device = " + device);
switch (state) {
case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
- broadcastConnectionState(mCurrentDevice,
+ if (mConnectedDevicesList.contains(device)) {
+
+ synchronized (HeadsetStateMachine.this) {
+ mConnectedDevicesList.remove(device);
+ mHeadsetAudioParam.remove(device);
+ mHeadsetBrsf.remove(device);
+ Log.d(TAG, "device " + device.getAddress() +
+ " is removed in Pending state");
+ }
+
+ broadcastConnectionState(device,
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_DISCONNECTING);
synchronized (HeadsetStateMachine.this) {
@@ -473,7 +541,12 @@
} else {
synchronized (HeadsetStateMachine.this) {
mIncomingDevice = null;
- transitionTo(mDisconnected);
+ if (mConnectedDevicesList.size() == 0) {
+ transitionTo(mDisconnected);
+ }
+ else {
+ processMultiHFConnected(device);
+ }
}
}
} else if (mTargetDevice != null && mTargetDevice.equals(device)) {
@@ -482,7 +555,13 @@
BluetoothProfile.STATE_CONNECTING);
synchronized (HeadsetStateMachine.this) {
mTargetDevice = null;
- transitionTo(mDisconnected);
+ if (mConnectedDevicesList.size() == 0) {
+ transitionTo(mDisconnected);
+ }
+ else {
+ transitionTo(mConnected);
+ }
+
}
} else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
broadcastConnectionState(mIncomingDevice,
@@ -490,109 +569,162 @@
BluetoothProfile.STATE_CONNECTING);
synchronized (HeadsetStateMachine.this) {
mIncomingDevice = null;
- transitionTo(mDisconnected);
+ if (mConnectedDevicesList.size() == 0) {
+ transitionTo(mDisconnected);
+ }
+ else {
+ transitionTo(mConnected);
+ }
}
} else {
Log.e(TAG, "Unknown device Disconnected: " + device);
}
break;
- case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
- // disconnection failed
- broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
+ case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
+ if (mConnectedDevicesList.contains(device)) {
+ // disconnection failed
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_DISCONNECTING);
- if (mTargetDevice != null) {
- broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
+ if (mTargetDevice != null) {
+ broadcastConnectionState(mTargetDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTING);
- }
- synchronized (HeadsetStateMachine.this) {
- mTargetDevice = null;
- transitionTo(mConnected);
- }
- } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
- broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
+ }
+ synchronized (HeadsetStateMachine.this) {
+ mTargetDevice = null;
+ transitionTo(mConnected);
+ }
+ } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
+
+ synchronized (HeadsetStateMachine.this) {
+ mCurrentDevice = device;
+ mConnectedDevicesList.add(device);
+ Log.d(TAG, "device " + device.getAddress() +
+ " is added in Pending state");
+ mTargetDevice = null;
+ transitionTo(mConnected);
+ }
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
- synchronized (HeadsetStateMachine.this) {
- mCurrentDevice = mTargetDevice;
- mTargetDevice = null;
- transitionTo(mConnected);
- }
- } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
- broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
+ configAudioParameters(device);
+ } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
+
+ synchronized (HeadsetStateMachine.this) {
+ mCurrentDevice = device;
+ mConnectedDevicesList.add(device);
+ Log.d(TAG, "device " + device.getAddress() +
+ " is added in Pending state");
+ mIncomingDevice = null;
+ transitionTo(mConnected);
+ }
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
- synchronized (HeadsetStateMachine.this) {
- mCurrentDevice = mIncomingDevice;
- mIncomingDevice = null;
- transitionTo(mConnected);
+ configAudioParameters(device);
+ } else {
+ Log.w(TAG, "Some other incoming HF connected in Pending state");
+ if (okToConnect(device)) {
+ Log.i(TAG,"Incoming Hf accepted");
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ synchronized (HeadsetStateMachine.this) {
+ mCurrentDevice = device;
+ mConnectedDevicesList.add(device);
+ Log.d(TAG, "device " + device.getAddress() +
+ " is added in Pending state");
+ }
+ configAudioParameters(device);
+ } else {
+ //reject the connection and stay in Pending state itself
+ Log.i(TAG,"Incoming Hf rejected. priority=" +
+ mService.getPriority(device) + " bondState=" +
+ device.getBondState());
+ disconnectHfpNative(getByteAddress(device));
+ // the other profile connection should be initiated
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if (adapterService != null) {
+ adapterService.connectOtherProfile(device,
+ AdapterService.PROFILE_CONN_REJECTED);
+ }
+ }
}
- } else {
- Log.e(TAG, "Unknown device Connected: " + device);
- // something is wrong here, but sync our state with stack
- broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_DISCONNECTED);
- synchronized (HeadsetStateMachine.this) {
- mCurrentDevice = device;
- mTargetDevice = null;
- mIncomingDevice = null;
- transitionTo(mConnected);
+ break;
+ case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
+ if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
+ log("current device tries to connect back");
+ // TODO(BT) ignore or reject
+ } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
+ // The stack is connecting to target device or
+ // there is an incoming connection from the target device at the same time
+ // we already broadcasted the intent, doing nothing here
+ if (DBG) {
+ log("Stack and target device are connecting");
+ }
}
- }
- configAudioParameters();
- break;
- case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
- log("current device tries to connect back");
- // TODO(BT) ignore or reject
- } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
- // The stack is connecting to target device or
- // there is an incoming connection from the target device at the same time
- // we already broadcasted the intent, doing nothing here
- if (DBG) {
- log("Stack and target device are connecting");
+ else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
+ Log.e(TAG, "Another connecting event on the incoming device");
+ } else {
+ // We get an incoming connecting request while Pending
+ // TODO(BT) is stack handing this case? let's ignore it for now
+ log("Incoming connection while pending, ignore");
}
- }
- else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
- Log.e(TAG, "Another connecting event on the incoming device");
- } else {
- // We get an incoming connecting request while Pending
- // TODO(BT) is stack handing this case? let's ignore it for now
- log("Incoming connection while pending, ignore");
- }
- break;
- case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
- // we already broadcasted the intent, doing nothing here
- if (DBG) {
- log("stack is disconnecting mCurrentDevice");
+ break;
+ case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
+ if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
+ // we already broadcasted the intent, doing nothing here
+ if (DBG) {
+ log("stack is disconnecting mCurrentDevice");
+ }
+ } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
+ Log.e(TAG, "TargetDevice is getting disconnected");
+ } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
+ Log.e(TAG, "IncomingDevice is getting disconnected");
+ } else {
+ Log.e(TAG, "Disconnecting unknow device: " + device);
}
- } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
- Log.e(TAG, "TargetDevice is getting disconnected");
- } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
- Log.e(TAG, "IncomingDevice is getting disconnected");
- } else {
- Log.e(TAG, "Disconnecting unknow device: " + device);
- }
- break;
- default:
- Log.e(TAG, "Incorrect state: " + state);
- break;
+ break;
+ default:
+ Log.e(TAG, "Incorrect state: " + state);
+ break;
}
}
+ private void processMultiHFConnected(BluetoothDevice device) {
+ log("Pending state: processMultiHFConnected");
+ /* Assign the current activedevice again if the disconnected
+ device equals to the current active device*/
+ if (mCurrentDevice != null && mCurrentDevice.equals(device)) {
+ transitionTo(mConnected);
+ int deviceSize = mConnectedDevicesList.size();
+ mCurrentDevice = mConnectedDevicesList.get(deviceSize-1);
+ } else {
+ // The disconnected device is not current active device
+ if (mAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED)
+ transitionTo(mAudioOn);
+ else transitionTo(mConnected);
+ }
+ log("processMultiHFConnected , the latest mCurrentDevice is:"
+ + mCurrentDevice);
+ log("Pending state: processMultiHFConnected ," +
+ "fake broadcasting for mCurrentDevice");
+ broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ }
}
private class Connected extends State {
@Override
public void enter() {
- log("Enter Connected: " + getCurrentMessage().what);
+ log("Enter Connected: " + getCurrentMessage().what +
+ ", size: " + mConnectedDevicesList.size());
}
@Override
public boolean processMessage(Message message) {
- log("Connected process message: " + message.what);
+ log("Connected process message: " + message.what +
+ ", size: " + mConnectedDevicesList.size());
if (DBG) {
- if (mCurrentDevice == null) {
- log("ERROR: mCurrentDevice is null in Connected");
+ if (mConnectedDevicesList.size() == 0) {
+ log("ERROR: mConnectedDevicesList is empty in Connected");
return NOT_HANDLED;
}
}
@@ -602,28 +734,68 @@
case CONNECT:
{
BluetoothDevice device = (BluetoothDevice) message.obj;
- if (mCurrentDevice.equals(device)) {
+ if (device == null) {
break;
}
- broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
+ if (mConnectedDevicesList.contains(device)) {
+ Log.e(TAG, "ERROR: Connect received for already connected device, Ignore");
+ break;
+ }
+
+ if (mConnectedDevicesList.size() >= max_hf_connections) {
+ BluetoothDevice DisconnectConnectedDevice = null;
+ IState CurrentAudioState = getCurrentState();
+ Log.d(TAG, "Reach to max size, disconnect one of them first");
+ /* TODO: Disconnect based on CoD */
+ DisconnectConnectedDevice = mConnectedDevicesList.get(0);
+
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
- if (!disconnectHfpNative(getByteAddress(mCurrentDevice))) {
- broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- break;
- }
- synchronized (HeadsetStateMachine.this) {
- mTargetDevice = device;
- transitionTo(mPending);
+ if (!disconnectHfpNative(getByteAddress(DisconnectConnectedDevice))) {
+ broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ break;
+ } else {
+ broadcastConnectionState(DisconnectConnectedDevice,
+ BluetoothProfile.STATE_DISCONNECTING,
+ BluetoothProfile.STATE_CONNECTED);
+ }
+
+ synchronized (HeadsetStateMachine.this) {
+ mTargetDevice = device;
+ if (max_hf_connections == 1) {
+ transitionTo(mPending);
+ } else {
+ mMultiDisconnectDevice = DisconnectConnectedDevice;
+ transitionTo(mMultiHFPending);
+ }
+ DisconnectConnectedDevice = null;
+ }
+ }else if (mConnectedDevicesList.size() < max_hf_connections) {
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTED);
+ if (!connectHfpNative(getByteAddress(device))) {
+ broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ break;
+ }
+ synchronized (HeadsetStateMachine.this) {
+ mTargetDevice = device;
+ // Transtion to MultiHFPending state for Multi HF connection
+ transitionTo(mMultiHFPending);
+ }
}
+ Message m = obtainMessage(CONNECT_TIMEOUT);
+ m.obj = device;
+ sendMessageDelayed(m, 30000);
}
break;
case DISCONNECT:
{
BluetoothDevice device = (BluetoothDevice) message.obj;
- if (!mCurrentDevice.equals(device)) {
+ if (!mConnectedDevicesList.contains(device)) {
break;
}
broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
@@ -633,13 +805,27 @@
BluetoothProfile.STATE_DISCONNECTED);
break;
}
- transitionTo(mPending);
+
+ if (mConnectedDevicesList.size() > 1) {
+ mMultiDisconnectDevice = device;
+ transitionTo(mMultiHFPending);
+ } else {
+ transitionTo(mPending);
+ }
}
break;
case CONNECT_AUDIO:
+ {
+ BluetoothDevice device = mCurrentDevice;
// TODO(BT) when failure, broadcast audio connecting to disconnected intent
// check if device matches mCurrentDevice
- connectAudioNative(getByteAddress(mCurrentDevice));
+ if (mActiveScoDevice != null) {
+ log("connectAudioNative in Connected; mActiveScoDevice is not null");
+ device = mActiveScoDevice;
+ }
+ log("connectAudioNative in Connected for device = " + device);
+ connectAudioNative(getByteAddress(device));
+ }
break;
case VOICE_RECOGNITION_START:
processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED);
@@ -659,15 +845,25 @@
case SEND_CCLC_RESPONSE:
processSendClccResponse((HeadsetClccResponse) message.obj);
break;
+ case CLCC_RSP_TIMEOUT:
+ {
+ BluetoothDevice device = (BluetoothDevice) message.obj;
+ clccResponseNative(0, 0, 0, 0, false, "", 0, getByteAddress(device));
+ }
+ break;
case SEND_VENDOR_SPECIFIC_RESULT_CODE:
processSendVendorSpecificResultCode(
(HeadsetVendorSpecificResultCode) message.obj);
break;
case DIALING_OUT_TIMEOUT:
+ {
+ BluetoothDevice device = (BluetoothDevice) message.obj;
if (mDialingOut) {
mDialingOut= false;
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR,
+ 0, getByteAddress(device));
}
+ }
break;
case VIRTUAL_CALL_START:
initiateScoUsingVirtualVoiceCall();
@@ -676,16 +872,22 @@
terminateScoUsingVirtualVoiceCall();
break;
case START_VR_TIMEOUT:
+ {
+ BluetoothDevice device = (BluetoothDevice) message.obj;
if (mWaitingForVoiceRecognition) {
+ device = (BluetoothDevice) message.obj;
mWaitingForVoiceRecognition = false;
Log.e(TAG, "Timeout waiting for voice recognition to start");
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR,
+ 0, getByteAddress(device));
}
+ }
break;
case STACK_EVENT:
StackEvent event = (StackEvent) message.obj;
if (DBG) {
- log("event type: " + event.type);
+ log("event type: " + event.type + "event device : "
+ + event.device);
}
switch (event.type) {
case EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -695,48 +897,49 @@
processAudioEvent(event.valueInt, event.device);
break;
case EVENT_TYPE_VR_STATE_CHANGED:
- processVrEvent(event.valueInt);
+ processVrEvent(event.valueInt, event.device);
break;
case EVENT_TYPE_ANSWER_CALL:
// TODO(BT) could answer call happen on Connected state?
- processAnswerCall();
+ processAnswerCall(event.device);
break;
case EVENT_TYPE_HANGUP_CALL:
// TODO(BT) could hangup call happen on Connected state?
- processHangupCall();
+ processHangupCall(event.device);
break;
case EVENT_TYPE_VOLUME_CHANGED:
- processVolumeEvent(event.valueInt, event.valueInt2);
+ processVolumeEvent(event.valueInt, event.valueInt2,
+ event.device);
break;
case EVENT_TYPE_DIAL_CALL:
- processDialCall(event.valueString);
+ processDialCall(event.valueString, event.device);
break;
case EVENT_TYPE_SEND_DTMF:
- processSendDtmf(event.valueInt);
+ processSendDtmf(event.valueInt, event.device);
break;
case EVENT_TYPE_NOICE_REDUCTION:
- processNoiceReductionEvent(event.valueInt);
+ processNoiceReductionEvent(event.valueInt, event.device);
break;
case EVENT_TYPE_AT_CHLD:
- processAtChld(event.valueInt);
+ processAtChld(event.valueInt, event.device);
break;
case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
- processSubscriberNumberRequest();
+ processSubscriberNumberRequest(event.device);
break;
case EVENT_TYPE_AT_CIND:
- processAtCind();
+ processAtCind(event.device);
break;
case EVENT_TYPE_AT_COPS:
- processAtCops();
+ processAtCops(event.device);
break;
case EVENT_TYPE_AT_CLCC:
- processAtClcc();
+ processAtClcc(event.device);
break;
case EVENT_TYPE_UNKNOWN_AT:
- processUnknownAt(event.valueString);
+ processUnknownAt(event.valueString, event.device);
break;
case EVENT_TYPE_KEY_PRESSED:
- processKeyPressed();
+ processKeyPressed(event.device);
break;
default:
Log.e(TAG, "Unknown stack event: " + event.type);
@@ -751,14 +954,27 @@
// in Connected state
private void processConnectionEvent(int state, BluetoothDevice device) {
+ Log.d(TAG, "processConnectionEvent state = " + state + ", device = "
+ + device);
switch (state) {
case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
- if (mCurrentDevice.equals(device)) {
- broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
+ if (mConnectedDevicesList.contains(device)) {
+ broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTED);
synchronized (HeadsetStateMachine.this) {
- mCurrentDevice = null;
- transitionTo(mDisconnected);
+ mConnectedDevicesList.remove(device);
+ mHeadsetAudioParam.remove(device);
+ mHeadsetBrsf.remove(device);
+ Log.d(TAG, "device " + device.getAddress() +
+ " is removed in Connected state");
+
+ if (mConnectedDevicesList.size() == 0) {
+ mCurrentDevice = null;
+ transitionTo(mDisconnected);
+ }
+ else {
+ processMultiHFConnected(device);
+ }
}
} else {
Log.e(TAG, "Disconnected from unknown device: " + device);
@@ -767,15 +983,51 @@
case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
processSlcConnected();
break;
- default:
+ case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
+ if (mConnectedDevicesList.contains(device)) {
+ mIncomingDevice = null;
+ mTargetDevice = null;
+ break;
+ }
+ Log.w(TAG, "HFP to be Connected in Connected state");
+ if (okToConnect(device) && (mConnectedDevicesList.size()
+ < max_hf_connections)) {
+ Log.i(TAG,"Incoming Hf accepted");
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ synchronized (HeadsetStateMachine.this) {
+ if(!mConnectedDevicesList.contains(device)) {
+ mCurrentDevice = device;
+ mConnectedDevicesList.add(device);
+ Log.d(TAG, "device " + device.getAddress() +
+ " is added in Connected state");
+ }
+ transitionTo(mConnected);
+ }
+ configAudioParameters(device);
+ } else {
+ // reject the connection and stay in Connected state itself
+ Log.i(TAG,"Incoming Hf rejected. priority=" +
+ mService.getPriority(device) + " bondState=" +
+ device.getBondState());
+ disconnectHfpNative(getByteAddress(device));
+ // the other profile connection should be initiated
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if (adapterService != null) {
+ adapterService.connectOtherProfile(device,
+ AdapterService.PROFILE_CONN_REJECTED);
+ }
+ }
+ break;
+ default:
Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
- break;
+ break;
}
}
// in Connected state
private void processAudioEvent(int state, BluetoothDevice device) {
- if (!mCurrentDevice.equals(device)) {
+ if (!mConnectedDevicesList.contains(device)) {
Log.e(TAG, "Audio changed on disconnected device: " + device);
return;
}
@@ -784,9 +1036,11 @@
case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
// TODO(BT) should I save the state for next broadcast as the prevState?
mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTED;
+ setAudioParameters(device); /*Set proper Audio Paramters.*/
mAudioManager.setBluetoothScoOn(true);
broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTED,
BluetoothHeadset.STATE_AUDIO_CONNECTING);
+ mActiveScoDevice = device;
transitionTo(mAudioOn);
break;
case HeadsetHalConstants.AUDIO_STATE_CONNECTING:
@@ -818,42 +1072,178 @@
}
}
+
+ private void processMultiHFConnected(BluetoothDevice device) {
+ log("Connect state: processMultiHFConnected");
+ if (mActiveScoDevice != null && mActiveScoDevice.equals(device)) {
+ log ("mActiveScoDevice is disconnected, setting it to null");
+ mActiveScoDevice = null;
+ }
+ /* Assign the current activedevice again if the disconnected
+ device equals to the current active device */
+ if (mCurrentDevice != null && mCurrentDevice.equals(device)) {
+ transitionTo(mConnected);
+ int deviceSize = mConnectedDevicesList.size();
+ mCurrentDevice = mConnectedDevicesList.get(deviceSize-1);
+ } else {
+ // The disconnected device is not current active device
+ transitionTo(mConnected);
+ }
+ log("processMultiHFConnected , the latest mCurrentDevice is:" +
+ mCurrentDevice);
+ log("Connect state: processMultiHFConnected ," +
+ "fake broadcasting for mCurrentDevice");
+ broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ }
}
private class AudioOn extends State {
@Override
public void enter() {
- log("Enter AudioOn: " + getCurrentMessage().what);
+ log("Enter AudioOn: " + getCurrentMessage().what + ", size: " +
+ mConnectedDevicesList.size());
}
@Override
public boolean processMessage(Message message) {
- log("AudioOn process message: " + message.what);
+ log("AudioOn process message: " + message.what + ", size: " +
+ mConnectedDevicesList.size());
if (DBG) {
- if (mCurrentDevice == null) {
- log("ERROR: mCurrentDevice is null in AudioOn");
+ if (mConnectedDevicesList.size() == 0) {
+ log("ERROR: mConnectedDevicesList is empty in AudioOn");
return NOT_HANDLED;
}
}
boolean retValue = HANDLED;
switch(message.what) {
- case DISCONNECT:
+ case CONNECT:
{
BluetoothDevice device = (BluetoothDevice) message.obj;
- if (!mCurrentDevice.equals(device)) {
+ if (device == null) {
break;
}
- deferMessage(obtainMessage(DISCONNECT, message.obj));
+
+ if (mConnectedDevicesList.contains(device)) {
+ break;
+ }
+
+ if (max_hf_connections == 1) {
+ deferMessage(obtainMessage(DISCONNECT, mCurrentDevice));
+ deferMessage(obtainMessage(CONNECT, device));
+ if (disconnectAudioNative(getByteAddress(mCurrentDevice))) {
+ Log.d(TAG, "Disconnecting SCO audio for device = " + mCurrentDevice);
+ } else {
+ Log.e(TAG, "disconnectAudioNative failed");
+ }
+ break;
+ }
+
+ if (mConnectedDevicesList.size() >= max_hf_connections) {
+ BluetoothDevice DisconnectConnectedDevice = null;
+ IState CurrentAudioState = getCurrentState();
+ Log.d(TAG, "Reach to max size, disconnect " +
+ "one of them first");
+ DisconnectConnectedDevice = mConnectedDevicesList.get(0);
+
+ if (mActiveScoDevice.equals(DisconnectConnectedDevice)) {
+ DisconnectConnectedDevice = mConnectedDevicesList.get(1);
+ }
+
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTED);
+
+ if (!disconnectHfpNative(getByteAddress(DisconnectConnectedDevice))) {
+ broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ break;
+ } else {
+ broadcastConnectionState(DisconnectConnectedDevice,
+ BluetoothProfile.STATE_DISCONNECTING,
+ BluetoothProfile.STATE_CONNECTED);
+ }
+
+ synchronized (HeadsetStateMachine.this) {
+ mTargetDevice = device;
+ mMultiDisconnectDevice = DisconnectConnectedDevice;
+ transitionTo(mMultiHFPending);
+ DisconnectConnectedDevice = null;
+ }
+ } else if(mConnectedDevicesList.size() < max_hf_connections) {
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTED);
+ if (!connectHfpNative(getByteAddress(device))) {
+ broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ break;
+ }
+ synchronized (HeadsetStateMachine.this) {
+ mTargetDevice = device;
+ // Transtion to MultilHFPending state for Multi handsfree connection
+ transitionTo(mMultiHFPending);
+ }
+ }
+ Message m = obtainMessage(CONNECT_TIMEOUT);
+ m.obj = device;
+ sendMessageDelayed(m, 30000);
}
- // fall through
+ break;
+ case CONNECT_TIMEOUT:
+ onConnectionStateChanged(HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED,
+ getByteAddress(mTargetDevice));
+ break;
+ case DISCONNECT:
+ {
+ BluetoothDevice device = (BluetoothDevice)message.obj;
+ if (!mConnectedDevicesList.contains(device)) {
+ break;
+ }
+ if (mActiveScoDevice != null && mActiveScoDevice.equals(device)) {
+ // The disconnected device is active SCO device
+ Log.d(TAG, "AudioOn, the disconnected device" +
+ "is active SCO device");
+ deferMessage(obtainMessage(DISCONNECT, message.obj));
+ // Disconnect BT SCO first
+ if (disconnectAudioNative(getByteAddress(mActiveScoDevice))) {
+ log("Disconnecting SCO audio");
+ } else {
+ // if disconnect BT SCO failed, transition to mConnected state
+ transitionTo(mConnected);
+ }
+ } else {
+ /* Do not disconnect BT SCO if the disconnected
+ device is not active SCO device */
+ Log.d(TAG, "AudioOn, the disconnected device" +
+ "is not active SCO device");
+ broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
+ BluetoothProfile.STATE_CONNECTED);
+ // Should be still in AudioOn state
+ if (!disconnectHfpNative(getByteAddress(device))) {
+ Log.w(TAG, "AudioOn, disconnect device failed");
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTING);
+ break;
+ }
+ /* Transtion to MultiHFPending state for Multi
+ handsfree connection */
+ if (mConnectedDevicesList.size() > 1) {
+ mMultiDisconnectDevice = device;
+ transitionTo(mMultiHFPending);
+ }
+ }
+ }
+ break;
case DISCONNECT_AUDIO:
- if (disconnectAudioNative(getByteAddress(mCurrentDevice))) {
- mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
- mAudioManager.setBluetoothScoOn(false);
- broadcastAudioState(mCurrentDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
- BluetoothHeadset.STATE_AUDIO_CONNECTED);
+ if (mActiveScoDevice != null) {
+ if (disconnectAudioNative(getByteAddress(mActiveScoDevice))) {
+ log("Disconnecting SCO audio for device = " +
+ mActiveScoDevice);
+ } else {
+ Log.e(TAG, "disconnectAudioNative failed" +
+ "for device = " + mActiveScoDevice);
+ }
}
break;
case VOICE_RECOGNITION_START:
@@ -863,7 +1253,7 @@
processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED);
break;
case INTENT_SCO_VOLUME_CHANGED:
- processIntentScoVolume((Intent) message.obj);
+ processIntentScoVolume((Intent) message.obj, mActiveScoDevice);
break;
case CALL_STATE_CHANGED:
processCallState((HeadsetCallState) message.obj, ((message.arg1 == 1)?true:false));
@@ -877,6 +1267,12 @@
case SEND_CCLC_RESPONSE:
processSendClccResponse((HeadsetClccResponse) message.obj);
break;
+ case CLCC_RSP_TIMEOUT:
+ {
+ BluetoothDevice device = (BluetoothDevice) message.obj;
+ clccResponseNative(0, 0, 0, 0, false, "", 0, getByteAddress(device));
+ }
+ break;
case SEND_VENDOR_SPECIFIC_RESULT_CODE:
processSendVendorSpecificResultCode(
(HeadsetVendorSpecificResultCode) message.obj);
@@ -890,17 +1286,26 @@
break;
case DIALING_OUT_TIMEOUT:
+ {
if (mDialingOut) {
+ BluetoothDevice device = (BluetoothDevice)message.obj;
mDialingOut= false;
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR,
+ 0, getByteAddress(device));
}
+ }
break;
case START_VR_TIMEOUT:
+ {
if (mWaitingForVoiceRecognition) {
+ BluetoothDevice device = (BluetoothDevice)message.obj;
mWaitingForVoiceRecognition = false;
- Log.e(TAG, "Timeout waiting for voice recognition to start");
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ Log.e(TAG, "Timeout waiting for voice recognition" +
+ "to start");
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR,
+ 0, getByteAddress(device));
}
+ }
break;
case STACK_EVENT:
StackEvent event = (StackEvent) message.obj;
@@ -909,52 +1314,58 @@
}
switch (event.type) {
case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ BluetoothDevice device1 = getDeviceForMessage(CONNECT_TIMEOUT);
+ if (device1 != null && device1.equals(event.device)) {
+ Log.d(TAG, "remove connect timeout for device = " + device1);
+ removeMessages(CONNECT_TIMEOUT);
+ }
processConnectionEvent(event.valueInt, event.device);
break;
case EVENT_TYPE_AUDIO_STATE_CHANGED:
processAudioEvent(event.valueInt, event.device);
break;
case EVENT_TYPE_VR_STATE_CHANGED:
- processVrEvent(event.valueInt);
+ processVrEvent(event.valueInt, event.device);
break;
case EVENT_TYPE_ANSWER_CALL:
- processAnswerCall();
+ processAnswerCall(event.device);
break;
case EVENT_TYPE_HANGUP_CALL:
- processHangupCall();
+ processHangupCall(event.device);
break;
case EVENT_TYPE_VOLUME_CHANGED:
- processVolumeEvent(event.valueInt, event.valueInt2);
+ processVolumeEvent(event.valueInt, event.valueInt2,
+ event.device);
break;
case EVENT_TYPE_DIAL_CALL:
- processDialCall(event.valueString);
+ processDialCall(event.valueString, event.device);
break;
case EVENT_TYPE_SEND_DTMF:
- processSendDtmf(event.valueInt);
+ processSendDtmf(event.valueInt, event.device);
break;
case EVENT_TYPE_NOICE_REDUCTION:
- processNoiceReductionEvent(event.valueInt);
+ processNoiceReductionEvent(event.valueInt, event.device);
break;
case EVENT_TYPE_AT_CHLD:
- processAtChld(event.valueInt);
+ processAtChld(event.valueInt, event.device);
break;
case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
- processSubscriberNumberRequest();
+ processSubscriberNumberRequest(event.device);
break;
case EVENT_TYPE_AT_CIND:
- processAtCind();
+ processAtCind(event.device);
break;
case EVENT_TYPE_AT_COPS:
- processAtCops();
+ processAtCops(event.device);
break;
case EVENT_TYPE_AT_CLCC:
- processAtClcc();
+ processAtClcc(event.device);
break;
case EVENT_TYPE_UNKNOWN_AT:
- processUnknownAt(event.valueString);
+ processUnknownAt(event.valueString, event.device);
break;
case EVENT_TYPE_KEY_PRESSED:
- processKeyPressed();
+ processKeyPressed(event.device);
break;
default:
Log.e(TAG, "Unknown stack event: " + event.type);
@@ -969,29 +1380,84 @@
// in AudioOn state. Some headsets disconnect RFCOMM prior to SCO down. Handle this
private void processConnectionEvent(int state, BluetoothDevice device) {
+ Log.d(TAG, "processConnectionEvent state = " + state + ", device = " +
+ device);
switch (state) {
case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
- if (mCurrentDevice.equals(device)) {
- processAudioEvent (HeadsetHalConstants.AUDIO_STATE_DISCONNECTED, device);
- broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTED);
+ if (mConnectedDevicesList.contains(device)) {
+ if (mActiveScoDevice != null
+ && mActiveScoDevice.equals(device)&& mAudioState
+ != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ processAudioEvent(
+ HeadsetHalConstants.AUDIO_STATE_DISCONNECTED, device);
+ }
+
synchronized (HeadsetStateMachine.this) {
- mCurrentDevice = null;
- transitionTo(mDisconnected);
+ mConnectedDevicesList.remove(device);
+ mHeadsetAudioParam.remove(device);
+ mHeadsetBrsf.remove(device);
+ Log.d(TAG, "device " + device.getAddress() +
+ " is removed in AudioOn state");
+ broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED);
+ if (mConnectedDevicesList.size() == 0) {
+ transitionTo(mDisconnected);
+ }
+ else {
+ processMultiHFConnected(device);
+ }
}
} else {
Log.e(TAG, "Disconnected from unknown device: " + device);
}
break;
- default:
- Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
- break;
+ case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
+ processSlcConnected();
+ break;
+ case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
+ if (mConnectedDevicesList.contains(device)) {
+ mIncomingDevice = null;
+ mTargetDevice = null;
+ break;
+ }
+ Log.w(TAG, "HFP to be Connected in AudioOn state");
+ if (okToConnect(device) && (mConnectedDevicesList.size()
+ < max_hf_connections) ) {
+ Log.i(TAG,"Incoming Hf accepted");
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ synchronized (HeadsetStateMachine.this) {
+ if (!mConnectedDevicesList.contains(device)) {
+ mCurrentDevice = device;
+ mConnectedDevicesList.add(device);
+ Log.d(TAG, "device " + device.getAddress() +
+ " is added in AudioOn state");
+ }
+ }
+ configAudioParameters(device);
+ } else {
+ // reject the connection and stay in Connected state itself
+ Log.i(TAG,"Incoming Hf rejected. priority="
+ + mService.getPriority(device) +
+ " bondState=" + device.getBondState());
+ disconnectHfpNative(getByteAddress(device));
+ // the other profile connection should be initiated
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if (adapterService != null) {
+ adapterService.connectOtherProfile(device,
+ AdapterService.PROFILE_CONN_REJECTED);
+ }
+ }
+ break;
+ default:
+ Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
+ break;
}
}
// in AudioOn state
private void processAudioEvent(int state, BluetoothDevice device) {
- if (!mCurrentDevice.equals(device)) {
+ if (!mConnectedDevicesList.contains(device)) {
Log.e(TAG, "Audio changed on disconnected device: " + device);
return;
}
@@ -1017,15 +1483,491 @@
}
}
- private void processIntentScoVolume(Intent intent) {
+ private void processSlcConnected() {
+ if (mPhoneProxy != null) {
+ try {
+ // start phone state listener here, instead of on disconnected exit()
+ // On BT off, exitting SM sends a SM exit() call which incorrectly forces
+ // a listenForPhoneState(true).
+ // Additionally, no indicator updates should be sent prior to SLC setup
+ mPhoneState.listenForPhoneState(true);
+ mPhoneProxy.queryPhoneState();
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ } else {
+ Log.e(TAG, "Handsfree phone proxy null for query phone state");
+ }
+ }
+
+ private void processIntentScoVolume(Intent intent, BluetoothDevice device) {
int volumeValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
if (mPhoneState.getSpeakerVolume() != volumeValue) {
mPhoneState.setSpeakerVolume(volumeValue);
- setVolumeNative(HeadsetHalConstants.VOLUME_TYPE_SPK, volumeValue);
+ setVolumeNative(HeadsetHalConstants.VOLUME_TYPE_SPK,
+ volumeValue, getByteAddress(device));
}
}
+
+ private void processMultiHFConnected(BluetoothDevice device) {
+ log("AudioOn state: processMultiHFConnected");
+ /* Assign the current activedevice again if the disconnected
+ device equals to the current active device */
+ if (mCurrentDevice != null && mCurrentDevice.equals(device)) {
+ int deviceSize = mConnectedDevicesList.size();
+ mCurrentDevice = mConnectedDevicesList.get(deviceSize-1);
+ }
+ if (mAudioState != BluetoothHeadset.STATE_AUDIO_CONNECTED)
+ transitionTo(mConnected);
+
+ log("processMultiHFConnected , the latest mCurrentDevice is:"
+ + mCurrentDevice);
+ log("AudioOn state: processMultiHFConnected ," +
+ "fake broadcasting for mCurrentDevice");
+ broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ }
}
+ /* Add MultiHFPending state when atleast 1 HS is connected
+ and disconnect/connect new HS */
+ private class MultiHFPending extends State {
+ @Override
+ public void enter() {
+ log("Enter MultiHFPending: " + getCurrentMessage().what +
+ ", size: " + mConnectedDevicesList.size());
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ log("MultiHFPending process message: " + message.what +
+ ", size: " + mConnectedDevicesList.size());
+
+ boolean retValue = HANDLED;
+ switch(message.what) {
+ case CONNECT:
+ deferMessage(message);
+ break;
+
+ case CONNECT_AUDIO:
+ if (mCurrentDevice != null) {
+ connectAudioNative(getByteAddress(mCurrentDevice));
+ }
+ break;
+ case CONNECT_TIMEOUT:
+ onConnectionStateChanged(HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED,
+ getByteAddress(mTargetDevice));
+ break;
+
+ case DISCONNECT_AUDIO:
+ if (mActiveScoDevice != null) {
+ if (disconnectAudioNative(getByteAddress(mActiveScoDevice))) {
+ Log.d(TAG, "MultiHFPending, Disconnecting SCO audio for " +
+ mActiveScoDevice);
+ } else {
+ Log.e(TAG, "disconnectAudioNative failed" +
+ "for device = " + mActiveScoDevice);
+ }
+ }
+ break;
+ case DISCONNECT:
+ BluetoothDevice device = (BluetoothDevice) message.obj;
+ if (mConnectedDevicesList.contains(device) &&
+ mTargetDevice != null && mTargetDevice.equals(device)) {
+ // cancel connection to the mTargetDevice
+ broadcastConnectionState(device,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ synchronized (HeadsetStateMachine.this) {
+ mTargetDevice = null;
+ }
+ } else {
+ deferMessage(message);
+ }
+ break;
+ case VOICE_RECOGNITION_START:
+ device = (BluetoothDevice) message.obj;
+ if (mConnectedDevicesList.contains(device)) {
+ processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED);
+ }
+ break;
+ case VOICE_RECOGNITION_STOP:
+ device = (BluetoothDevice) message.obj;
+ if (mConnectedDevicesList.contains(device)) {
+ processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED);
+ }
+ break;
+ case INTENT_BATTERY_CHANGED:
+ processIntentBatteryChanged((Intent) message.obj);
+ break;
+ case CALL_STATE_CHANGED:
+ processCallState((HeadsetCallState) message.obj,
+ ((message.arg1 == 1)?true:false));
+ break;
+ case DEVICE_STATE_CHANGED:
+ processDeviceStateChanged((HeadsetDeviceState) message.obj);
+ break;
+ case SEND_CCLC_RESPONSE:
+ processSendClccResponse((HeadsetClccResponse) message.obj);
+ break;
+ case CLCC_RSP_TIMEOUT:
+ {
+ device = (BluetoothDevice) message.obj;
+ clccResponseNative(0, 0, 0, 0, false, "", 0, getByteAddress(device));
+ }
+ break;
+ case DIALING_OUT_TIMEOUT:
+ if (mDialingOut) {
+ device = (BluetoothDevice) message.obj;
+ mDialingOut= false;
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR,
+ 0, getByteAddress(device));
+ }
+ break;
+ case VIRTUAL_CALL_START:
+ device = (BluetoothDevice) message.obj;
+ if(mConnectedDevicesList.contains(device)) {
+ initiateScoUsingVirtualVoiceCall();
+ }
+ break;
+ case VIRTUAL_CALL_STOP:
+ device = (BluetoothDevice) message.obj;
+ if (mConnectedDevicesList.contains(device)) {
+ terminateScoUsingVirtualVoiceCall();
+ }
+ break;
+ case START_VR_TIMEOUT:
+ if (mWaitingForVoiceRecognition) {
+ device = (BluetoothDevice) message.obj;
+ mWaitingForVoiceRecognition = false;
+ Log.e(TAG, "Timeout waiting for voice" +
+ "recognition to start");
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR,
+ 0, getByteAddress(device));
+ }
+ break;
+ case STACK_EVENT:
+ StackEvent event = (StackEvent) message.obj;
+ if (DBG) {
+ log("event type: " + event.type);
+ }
+ switch (event.type) {
+ case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ BluetoothDevice device1 = getDeviceForMessage(CONNECT_TIMEOUT);
+ if (device1 != null && device1.equals(event.device)) {
+ Log.d(TAG, "remove connect timeout for device = " + device1);
+ removeMessages(CONNECT_TIMEOUT);
+ }
+ processConnectionEvent(event.valueInt, event.device);
+ break;
+ case EVENT_TYPE_AUDIO_STATE_CHANGED:
+ processAudioEvent(event.valueInt, event.device);
+ break;
+ case EVENT_TYPE_VR_STATE_CHANGED:
+ processVrEvent(event.valueInt,event.device);
+ break;
+ case EVENT_TYPE_ANSWER_CALL:
+ //TODO(BT) could answer call happen on Connected state?
+ processAnswerCall(event.device);
+ break;
+ case EVENT_TYPE_HANGUP_CALL:
+ // TODO(BT) could hangup call happen on Connected state?
+ processHangupCall(event.device);
+ break;
+ case EVENT_TYPE_VOLUME_CHANGED:
+ processVolumeEvent(event.valueInt, event.valueInt2,
+ event.device);
+ break;
+ case EVENT_TYPE_DIAL_CALL:
+ processDialCall(event.valueString, event.device);
+ break;
+ case EVENT_TYPE_SEND_DTMF:
+ processSendDtmf(event.valueInt, event.device);
+ break;
+ case EVENT_TYPE_NOICE_REDUCTION:
+ processNoiceReductionEvent(event.valueInt, event.device);
+ break;
+ case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
+ processSubscriberNumberRequest(event.device);
+ break;
+ case EVENT_TYPE_AT_CIND:
+ processAtCind(event.device);
+ break;
+ case EVENT_TYPE_AT_CHLD:
+ processAtChld(event.valueInt, event.device);
+ break;
+ case EVENT_TYPE_AT_COPS:
+ processAtCops(event.device);
+ break;
+ case EVENT_TYPE_AT_CLCC:
+ processAtClcc(event.device);
+ break;
+ case EVENT_TYPE_UNKNOWN_AT:
+ processUnknownAt(event.valueString,event.device);
+ break;
+ case EVENT_TYPE_KEY_PRESSED:
+ processKeyPressed(event.device);
+ break;
+ default:
+ Log.e(TAG, "Unexpected event: " + event.type);
+ break;
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return retValue;
+ }
+
+ // in MultiHFPending state
+ private void processConnectionEvent(int state, BluetoothDevice device) {
+ Log.d(TAG, "processConnectionEvent state = " + state +
+ ", device = " + device);
+ switch (state) {
+ case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
+ if (mConnectedDevicesList.contains(device)) {
+ if (mMultiDisconnectDevice != null &&
+ mMultiDisconnectDevice.equals(device)) {
+ mMultiDisconnectDevice = null;
+
+ synchronized (HeadsetStateMachine.this) {
+ mConnectedDevicesList.remove(device);
+ mHeadsetAudioParam.remove(device);
+ mHeadsetBrsf.remove(device);
+ Log.d(TAG, "device " + device.getAddress() +
+ " is removed in MultiHFPending state");
+ broadcastConnectionState(device,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_DISCONNECTING);
+ }
+
+ if (mTargetDevice != null) {
+ if (!connectHfpNative(getByteAddress(mTargetDevice))) {
+
+ broadcastConnectionState(mTargetDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ synchronized (HeadsetStateMachine.this) {
+ mTargetDevice = null;
+ if (mConnectedDevicesList.size() == 0) {
+ // Should be not in this state since it has at least
+ // one HF connected in MultiHFPending state
+ Log.d(TAG, "Should be not in this state, error handling");
+ transitionTo(mDisconnected);
+ }
+ else {
+ processMultiHFConnected(device);
+ }
+ }
+ }
+ } else {
+ synchronized (HeadsetStateMachine.this) {
+ mIncomingDevice = null;
+ if (mConnectedDevicesList.size() == 0) {
+ transitionTo(mDisconnected);
+ }
+ else {
+ processMultiHFConnected(device);
+ }
+ }
+ }
+ } else {
+ /* Another HF disconnected when one HF is connecting */
+ synchronized (HeadsetStateMachine.this) {
+ mConnectedDevicesList.remove(device);
+ mHeadsetAudioParam.remove(device);
+ mHeadsetBrsf.remove(device);
+ Log.d(TAG, "device " + device.getAddress() +
+ " is removed in MultiHFPending state");
+ }
+ broadcastConnectionState(device,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED);
+ }
+ } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
+
+ broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ synchronized (HeadsetStateMachine.this) {
+ mTargetDevice = null;
+ if (mConnectedDevicesList.size() == 0) {
+ transitionTo(mDisconnected);
+ }
+ else
+ {
+ if (mAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED)
+ transitionTo(mAudioOn);
+ else transitionTo(mConnected);
+ }
+ }
+ } else {
+ Log.e(TAG, "Unknown device Disconnected: " + device);
+ }
+ break;
+ case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
+ /* Outgoing disconnection for device failed */
+ if (mConnectedDevicesList.contains(device)) {
+
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTING);
+ if (mTargetDevice != null) {
+ broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ }
+ synchronized (HeadsetStateMachine.this) {
+ mTargetDevice = null;
+ if (mAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED)
+ transitionTo(mAudioOn);
+ else transitionTo(mConnected);
+ }
+ } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
+
+ synchronized (HeadsetStateMachine.this) {
+ mCurrentDevice = device;
+ mConnectedDevicesList.add(device);
+ Log.d(TAG, "device " + device.getAddress() +
+ " is added in MultiHFPending state");
+ mTargetDevice = null;
+ if (mAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED)
+ transitionTo(mAudioOn);
+ else transitionTo(mConnected);
+ }
+
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ configAudioParameters(device);
+ } else {
+ Log.w(TAG, "Some other incoming HF connected" +
+ "in Multi Pending state");
+ if (okToConnect(device) &&
+ (mConnectedDevicesList.size() < max_hf_connections)) {
+ Log.i(TAG,"Incoming Hf accepted");
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ synchronized (HeadsetStateMachine.this) {
+ if (!mConnectedDevicesList.contains(device)) {
+ mCurrentDevice = device;
+ mConnectedDevicesList.add(device);
+ Log.d(TAG, "device " + device.getAddress() +
+ " is added in MultiHFPending state");
+ }
+ }
+ configAudioParameters(device);
+ } else {
+ // reject the connection and stay in Pending state itself
+ Log.i(TAG,"Incoming Hf rejected. priority=" +
+ mService.getPriority(device) +
+ " bondState=" + device.getBondState());
+ disconnectHfpNative(getByteAddress(device));
+ // the other profile connection should be initiated
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if (adapterService != null) {
+ adapterService.connectOtherProfile(device,
+ AdapterService.PROFILE_CONN_REJECTED);
+ }
+ }
+ }
+ break;
+ case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
+ if (mConnectedDevicesList.contains(device)) {
+ Log.e(TAG, "current device tries to connect back");
+ } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
+ if (DBG) {
+ log("Stack and target device are connecting");
+ }
+ }
+ else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
+ Log.e(TAG, "Another connecting event on" +
+ "the incoming device");
+ }
+ break;
+ case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
+ if (mConnectedDevicesList.contains(device)) {
+ if (DBG) {
+ log("stack is disconnecting mCurrentDevice");
+ }
+ } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
+ Log.e(TAG, "TargetDevice is getting disconnected");
+ } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
+ Log.e(TAG, "IncomingDevice is getting disconnected");
+ } else {
+ Log.e(TAG, "Disconnecting unknow device: " + device);
+ }
+ break;
+ default:
+ Log.e(TAG, "Incorrect state: " + state);
+ break;
+ }
+ }
+
+ private void processAudioEvent(int state, BluetoothDevice device) {
+ if (!mConnectedDevicesList.contains(device)) {
+ Log.e(TAG, "Audio changed on disconnected device: " + device);
+ return;
+ }
+
+ switch (state) {
+ case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
+ mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTED;
+ setAudioParameters(device); /* Set proper Audio Parameters. */
+ mAudioManager.setBluetoothScoOn(true);
+ broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTED,
+ BluetoothHeadset.STATE_AUDIO_CONNECTING);
+ mActiveScoDevice = device;
+ /* The state should be still in MultiHFPending state when
+ audio connected since other device is still connecting/
+ disconnecting */
+ break;
+ case HeadsetHalConstants.AUDIO_STATE_CONNECTING:
+ mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTING;
+ broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTING,
+ BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+ break;
+ case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
+ if (mAudioState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+ mAudioManager.setBluetoothScoOn(false);
+ broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+ BluetoothHeadset.STATE_AUDIO_CONNECTED);
+ }
+ /* The state should be still in MultiHFPending state when audio
+ disconnected since other device is still connecting/
+ disconnecting */
+ break;
+
+ default:
+ Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
+ break;
+ }
+ }
+
+ private void processMultiHFConnected(BluetoothDevice device) {
+ log("MultiHFPending state: processMultiHFConnected");
+ if (mActiveScoDevice != null && mActiveScoDevice.equals(device)) {
+ log ("mActiveScoDevice is disconnected, setting it to null");
+ mActiveScoDevice = null;
+ }
+ /* Assign the current activedevice again if the disconnected
+ device equals to the current active device */
+ if (mCurrentDevice != null && mCurrentDevice.equals(device)) {
+ int deviceSize = mConnectedDevicesList.size();
+ mCurrentDevice = mConnectedDevicesList.get(deviceSize-1);
+ }
+ // The disconnected device is not current active device
+ if (mAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED)
+ transitionTo(mAudioOn);
+ else transitionTo(mConnected);
+ log("processMultiHFConnected , the latest mCurrentDevice is:"
+ + mCurrentDevice);
+ log("MultiHFPending state: processMultiHFConnected ," +
+ "fake broadcasting for mCurrentDevice");
+ broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ }
+
+
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
if (DBG) Log.d(TAG, "Proxy object connected");
@@ -1042,16 +1984,18 @@
// in separate thread while this method is executing.
int getConnectionState(BluetoothDevice device) {
if (getCurrentState() == mDisconnected) {
+ if (DBG) Log.d(TAG, "currentState is Disconnected");
return BluetoothProfile.STATE_DISCONNECTED;
}
synchronized (this) {
IState currentState = getCurrentState();
+ if (DBG) Log.d(TAG, "currentState = " + currentState);
if (currentState == mPending) {
if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
return BluetoothProfile.STATE_CONNECTING;
}
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
+ if (mConnectedDevicesList.contains(device)) {
return BluetoothProfile.STATE_DISCONNECTING;
}
if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
@@ -1060,6 +2004,24 @@
return BluetoothProfile.STATE_DISCONNECTED;
}
+ if (currentState == mMultiHFPending) {
+ if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
+ return BluetoothProfile.STATE_CONNECTING;
+ }
+ if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
+ return BluetoothProfile.STATE_CONNECTING; // incoming connection
+ }
+ if (mConnectedDevicesList.contains(device)) {
+ if ((mMultiDisconnectDevice != null) &&
+ (!mMultiDisconnectDevice.equals(device))) {
+ // The device is still connected
+ return BluetoothProfile.STATE_CONNECTED;
+ }
+ return BluetoothProfile.STATE_DISCONNECTING;
+ }
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
if (currentState == mConnected || currentState == mAudioOn) {
if (mCurrentDevice.equals(device)) {
return BluetoothProfile.STATE_CONNECTED;
@@ -1075,10 +2037,10 @@
List<BluetoothDevice> getConnectedDevices() {
List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
synchronized(this) {
- if (isConnected()) {
- devices.add(mCurrentDevice);
+ for (int i = 0; i < mConnectedDevicesList.size(); i++)
+ devices.add(mConnectedDevicesList.get(i));
}
- }
+
return devices;
}
@@ -1105,14 +2067,19 @@
int getAudioState(BluetoothDevice device) {
synchronized(this) {
- if (mCurrentDevice == null || !mCurrentDevice.equals(device)) {
+ if (mConnectedDevicesList.size() == 0) {
return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
}
}
return mAudioState;
}
- private void processVrEvent(int state) {
+ private void processVrEvent(int state, BluetoothDevice device) {
+
+ if(device == null) {
+ Log.w(TAG, "processVrEvent device is null");
+ return;
+ }
Log.d(TAG, "processVrEvent: state=" + state + " mVoiceRecognitionStarted: " +
mVoiceRecognitionStarted + " mWaitingforVoiceRecognition: " + mWaitingForVoiceRecognition +
" isInCall: " + isInCall());
@@ -1123,25 +2090,28 @@
try {
mService.startActivity(sVoiceCommandIntent);
} catch (ActivityNotFoundException e) {
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR,
+ 0, getByteAddress(device));
return;
}
- expectVoiceRecognition();
+ expectVoiceRecognition(device);
}
} else if (state == HeadsetHalConstants.VR_STATE_STOPPED) {
if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition)
{
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK,
+ 0, getByteAddress(device));
mVoiceRecognitionStarted = false;
mWaitingForVoiceRecognition = false;
- if (!isInCall()) {
- disconnectAudioNative(getByteAddress(mCurrentDevice));
+ if (!isInCall() && (mActiveScoDevice != null)) {
+ disconnectAudioNative(getByteAddress(mActiveScoDevice));
mAudioManager.setParameters("A2dpSuspended=false");
}
}
else
{
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR,
+ 0, getByteAddress(device));
}
} else {
Log.e(TAG, "Bad Voice Recognition state: " + state);
@@ -1150,6 +2120,7 @@
private void processLocalVrEvent(int state)
{
+ BluetoothDevice device = null;
if (state == HeadsetHalConstants.VR_STATE_STARTED)
{
boolean needAudio = true;
@@ -1163,15 +2134,22 @@
if (mWaitingForVoiceRecognition)
{
+ device = getDeviceForMessage(START_VR_TIMEOUT);
+ if (device == null)
+ return;
+
Log.d(TAG, "Voice recognition started successfully");
mWaitingForVoiceRecognition = false;
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK,
+ 0, getByteAddress(device));
removeMessages(START_VR_TIMEOUT);
}
else
{
Log.d(TAG, "Voice recognition started locally");
- needAudio = startVoiceRecognitionNative();
+ needAudio = startVoiceRecognitionNative(getByteAddress(mCurrentDevice));
+ if (mCurrentDevice != null)
+ device = mCurrentDevice;
}
if (needAudio && !isAudioOn())
@@ -1186,7 +2164,7 @@
// Whereas for VoiceDial we want to activate the SCO connection but we are still
// in MODE_NORMAL and hence the need to explicitly suspend the A2DP stream
mAudioManager.setParameters("A2dpSuspended=true");
- connectAudioNative(getByteAddress(mCurrentDevice));
+ connectAudioNative(getByteAddress(device));
}
if (mStartVoiceRecognitionWakeLock.isHeld()) {
@@ -1202,17 +2180,21 @@
mVoiceRecognitionStarted = false;
mWaitingForVoiceRecognition = false;
- if (stopVoiceRecognitionNative() && !isInCall()) {
- disconnectAudioNative(getByteAddress(mCurrentDevice));
+ if (stopVoiceRecognitionNative(getByteAddress(mCurrentDevice))
+ && !isInCall() && mActiveScoDevice != null) {
+ disconnectAudioNative(getByteAddress(mActiveScoDevice));
mAudioManager.setParameters("A2dpSuspended=false");
}
}
}
}
- private synchronized void expectVoiceRecognition() {
+ private synchronized void expectVoiceRecognition(BluetoothDevice device) {
mWaitingForVoiceRecognition = true;
- sendMessageDelayed(START_VR_TIMEOUT, START_VR_TIMEOUT_VALUE);
+ Message m = obtainMessage(START_VR_TIMEOUT);
+ m.obj = getMatchingDevice(device);
+ sendMessageDelayed(m, START_VR_TIMEOUT_VALUE);
+
if (!mStartVoiceRecognitionWakeLock.isHeld()) {
mStartVoiceRecognitionWakeLock.acquire(START_VR_TIMEOUT_VALUE);
}
@@ -1239,6 +2221,40 @@
return deviceList;
}
+ private BluetoothDevice getDeviceForMessage(int what)
+ {
+ if (what == CONNECT_TIMEOUT) {
+ log("getDeviceForMessage: returning mTargetDevice for what=" + what);
+ return mTargetDevice;
+ }
+ if (mConnectedDevicesList.size() == 0) {
+ log("getDeviceForMessage: No connected device. what=" + what);
+ return null;
+ }
+ for (BluetoothDevice device : mConnectedDevicesList)
+ {
+ if (getHandler().hasMessages(what, device))
+ {
+ log("getDeviceForMessage: returning " + device);
+ return device;
+ }
+ }
+ log("getDeviceForMessage: No matching device for " + what + ". Returning null");
+ return null;
+ }
+
+ private BluetoothDevice getMatchingDevice(BluetoothDevice device)
+ {
+ for (BluetoothDevice matchingDevice : mConnectedDevicesList)
+ {
+ if (matchingDevice.equals(device))
+ {
+ return matchingDevice;
+ }
+ }
+ return null;
+ }
+
// This method does not check for error conditon (newState == prevState)
private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
log("Connection state " + device + ": " + prevState + "->" + newState);
@@ -1297,11 +2313,33 @@
mService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
}
- private void configAudioParameters()
+ private void configAudioParameters(BluetoothDevice device)
{
// Reset NREC on connect event. Headset will override later
- mAudioManager.setParameters(HEADSET_NAME + "=" + getCurrentDeviceName() + ";" +
+ HashMap<String, Integer> AudioParamConfig = new HashMap<String, Integer>();
+ AudioParamConfig.put("NREC", 1);
+ mHeadsetAudioParam.put(device, AudioParamConfig);
+ mAudioManager.setParameters(HEADSET_NAME + "=" + getCurrentDeviceName(device) + ";" +
HEADSET_NREC + "=on");
+ Log.d(TAG, "configAudioParameters for device:" + device + " are: nrec = " +
+ AudioParamConfig.get("NREC"));
+ }
+
+ private void setAudioParameters(BluetoothDevice device)
+ {
+ // 1. update nrec value
+ // 2. update headset name
+ HashMap<String, Integer> AudioParam = mHeadsetAudioParam.get(device);
+ int mNrec = AudioParam.get("NREC");
+
+ if (mNrec == 1) {
+ Log.d(TAG, "Set NREC: 1 for device:" + device);
+ mAudioManager.setParameters(HEADSET_NREC + "=on");
+ } else {
+ Log.d(TAG, "Set NREC: 0 for device:" + device);
+ mAudioManager.setParameters(HEADSET_NREC + "=off");
+ }
+ mAudioManager.setParameters(HEADSET_NAME + "=" + getCurrentDeviceName(device));
}
private String parseUnknownAt(String atString)
@@ -1400,7 +2438,12 @@
return true;
}
- private void processAnswerCall() {
+ private void processAnswerCall(BluetoothDevice device) {
+ if(device == null) {
+ Log.w(TAG, "processAnswerCall device is null");
+ return;
+ }
+
if (mPhoneProxy != null) {
try {
mPhoneProxy.answerCall();
@@ -1412,7 +2455,11 @@
}
}
- private void processHangupCall() {
+ private void processHangupCall(BluetoothDevice device) {
+ if(device == null) {
+ Log.w(TAG, "processHangupCall device is null");
+ return;
+ }
// Close the virtual call if active. Virtual call should be
// terminated for CHUP callback event
if (isVirtualCallInProgress()) {
@@ -1430,27 +2477,35 @@
}
}
- private void processDialCall(String number) {
+ private void processDialCall(String number, BluetoothDevice device) {
+ if(device == null) {
+ Log.w(TAG, "processDialCall device is null");
+ return;
+ }
+
String dialNumber;
if ((number == null) || (number.length() == 0)) {
dialNumber = mPhonebook.getLastDialledNumber();
if (dialNumber == null) {
if (DBG) log("processDialCall, last dial number null");
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0,
+ getByteAddress(device));
return;
}
} else if (number.charAt(0) == '>') {
// Yuck - memory dialling requested.
// Just dial last number for now
if (number.startsWith(">9999")) { // for PTS test
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0,
+ getByteAddress(device));
return;
}
if (DBG) log("processDialCall, memory dial do last dial for now");
dialNumber = mPhonebook.getLastDialledNumber();
if (dialNumber == null) {
if (DBG) log("processDialCall, last dial number null");
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0,
+ getByteAddress(device));
return;
}
} else {
@@ -1472,10 +2527,17 @@
// hold wait lock, start a timer, set wait call flag
// Get call started indication from bluetooth phone
mDialingOut = true;
- sendMessageDelayed(DIALING_OUT_TIMEOUT, DIALING_OUT_TIMEOUT_VALUE);
+ Message m = obtainMessage(DIALING_OUT_TIMEOUT);
+ m.obj = getMatchingDevice(device);
+ sendMessageDelayed(m, DIALING_OUT_TIMEOUT_VALUE);
}
- private void processVolumeEvent(int volumeType, int volume) {
+ private void processVolumeEvent(int volumeType, int volume, BluetoothDevice device) {
+ if(device != null && !device.equals(mActiveScoDevice) && mPhoneState.isInCall()) {
+ Log.w(TAG, "ignore processVolumeEvent");
+ return;
+ }
+
if (volumeType == HeadsetHalConstants.VOLUME_TYPE_SPK) {
mPhoneState.setSpeakerVolume(volume);
int flag = (getCurrentState() == mAudioOn) ? AudioManager.FLAG_SHOW_UI : 0;
@@ -1487,7 +2549,12 @@
}
}
- private void processSendDtmf(int dtmf) {
+ private void processSendDtmf(int dtmf, BluetoothDevice device) {
+ if(device == null) {
+ Log.w(TAG, "processSendDtmf device is null");
+ return;
+ }
+
if (mPhoneProxy != null) {
try {
mPhoneProxy.sendDtmf(dtmf);
@@ -1508,12 +2575,28 @@
mPhoneState.setNumActiveCall(callState.mNumActive);
mPhoneState.setNumHeldCall(callState.mNumHeld);
mPhoneState.setCallState(callState.mCallState);
- if (mDialingOut && callState.mCallState ==
- HeadsetHalConstants.CALL_STATE_DIALING) {
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
+ if (mDialingOut) {
+ if (callState.mCallState ==
+ HeadsetHalConstants.CALL_STATE_DIALING) {
+ BluetoothDevice device = getDeviceForMessage(DIALING_OUT_TIMEOUT);
+ if (device == null) {
+ return;
+ }
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK,
+ 0, getByteAddress(device));
removeMessages(DIALING_OUT_TIMEOUT);
+ } else if (callState.mCallState ==
+ HeadsetHalConstants.CALL_STATE_ACTIVE || callState.mCallState
+ == HeadsetHalConstants.CALL_STATE_IDLE) {
mDialingOut = false;
+ }
}
+
+ /* Set ActiveScoDevice to null when call ends */
+ if ((mActiveScoDevice != null) && !isInCall() &&
+ callState.mCallState == HeadsetHalConstants.CALL_STATE_IDLE)
+ mActiveScoDevice = null;
+
log("mNumActive: " + callState.mNumActive + " mNumHeld: " +
callState.mNumHeld +" mCallState: " + callState.mCallState);
log("mNumber: " + callState.mNumber + " mType: " + callState.mType);
@@ -1528,55 +2611,78 @@
}
}
- // enable 1 enable noice reduction
- // 0 disable noice reduction
- private void processNoiceReductionEvent(int enable) {
- if (enable == 1) {
- mAudioManager.setParameters(HEADSET_NREC + "=on");
- } else {
- mAudioManager.setParameters(HEADSET_NREC + "=off");
- }
+ // 1 enable noice reduction
+ // 0 disable noice reduction
+ private void processNoiceReductionEvent(int enable, BluetoothDevice device) {
+ HashMap<String, Integer> AudioParamNrec = mHeadsetAudioParam.get(device);
+ if (enable == 1)
+ AudioParamNrec.put("NREC", 1);
+ else
+ AudioParamNrec.put("NREC", 0);
+ Log.d(TAG, "NREC value for device :" + device + " is: " + AudioParamNrec.get("NREC"));
}
- private void processAtChld(int chld) {
+ private void processAtChld(int chld, BluetoothDevice device) {
+ if(device == null) {
+ Log.w(TAG, "processAtChld device is null");
+ return;
+ }
+
if (mPhoneProxy != null) {
try {
if (mPhoneProxy.processChld(chld)) {
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK,
+ 0, getByteAddress(device));
} else {
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR,
+ 0, getByteAddress(device));
}
} catch (RemoteException e) {
Log.e(TAG, Log.getStackTraceString(new Throwable()));
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR,
+ 0, getByteAddress(device));
}
} else {
Log.e(TAG, "Handsfree phone proxy null for At+Chld");
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR,
+ 0, getByteAddress(device));
}
}
- private void processSubscriberNumberRequest() {
+ private void processSubscriberNumberRequest(BluetoothDevice device) {
+ if(device == null) {
+ Log.w(TAG, "processSubscriberNumberRequest device is null");
+ return;
+ }
+
if (mPhoneProxy != null) {
try {
String number = mPhoneProxy.getSubscriberNumber();
if (number != null) {
atResponseStringNative("+CNUM: ,\"" + number + "\"," +
- PhoneNumberUtils.toaFromString(number) + ",,4");
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
+ PhoneNumberUtils.toaFromString(number) +
+ ",,4", getByteAddress(device));
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK,
+ 0, getByteAddress(device));
}
} catch (RemoteException e) {
Log.e(TAG, Log.getStackTraceString(new Throwable()));
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR,
+ 0, getByteAddress(device));
}
} else {
Log.e(TAG, "Handsfree phone proxy null for At+CNUM");
}
}
- private void processAtCind() {
+ private void processAtCind(BluetoothDevice device) {
int call, call_setup;
+ if(device == null) {
+ Log.w(TAG, "processAtCind device is null");
+ return;
+ }
+
/* Handsfree carkits expect that +CIND is properly responded to
Hence we ensure that a proper response is sent
for the virtual call too.*/
@@ -1592,28 +2698,38 @@
cindResponseNative(mPhoneState.getService(), call,
call_setup, mPhoneState.getCallState(),
mPhoneState.getSignal(), mPhoneState.getRoam(),
- mPhoneState.getBatteryCharge());
+ mPhoneState.getBatteryCharge(), getByteAddress(device));
}
- private void processAtCops() {
+ private void processAtCops(BluetoothDevice device) {
+ if(device == null) {
+ Log.w(TAG, "processAtCops device is null");
+ return;
+ }
+
if (mPhoneProxy != null) {
try {
String operatorName = mPhoneProxy.getNetworkOperator();
if (operatorName == null) {
operatorName = "";
}
- copsResponseNative(operatorName);
+ copsResponseNative(operatorName, getByteAddress(device));
} catch (RemoteException e) {
Log.e(TAG, Log.getStackTraceString(new Throwable()));
- copsResponseNative("");
+ copsResponseNative("", getByteAddress(device));
}
} else {
Log.e(TAG, "Handsfree phone proxy null for At+COPS");
- copsResponseNative("");
+ copsResponseNative("", getByteAddress(device));
}
}
- private void processAtClcc() {
+ private void processAtClcc(BluetoothDevice device) {
+ if(device == null) {
+ Log.w(TAG, "processAtClcc device is null");
+ return;
+ }
+
if (mPhoneProxy != null) {
try {
if(isVirtualCallInProgress()) {
@@ -1627,51 +2743,61 @@
"using IBluetoothHeadsetPhone proxy");
phoneNumber = "";
}
- clccResponseNative(1, 0, 0, 0, false, phoneNumber, type);
+ clccResponseNative(1, 0, 0, 0, false, phoneNumber, type,
+ getByteAddress(device));
}
else if (!mPhoneProxy.listCurrentCalls()) {
- clccResponseNative(0, 0, 0, 0, false, "", 0);
+ clccResponseNative(0, 0, 0, 0, false, "", 0,
+ getByteAddress(device));
+ }
+ else
+ {
+ Log.d(TAG, "Starting CLCC response timeout for device: "
+ + device);
+ Message m = obtainMessage(CLCC_RSP_TIMEOUT);
+ m.obj = getMatchingDevice(device);
+ sendMessageDelayed(m, CLCC_RSP_TIMEOUT_VALUE);
}
} catch (RemoteException e) {
Log.e(TAG, Log.getStackTraceString(new Throwable()));
- clccResponseNative(0, 0, 0, 0, false, "", 0);
+ clccResponseNative(0, 0, 0, 0, false, "", 0, getByteAddress(device));
}
} else {
Log.e(TAG, "Handsfree phone proxy null for At+CLCC");
- clccResponseNative(0, 0, 0, 0, false, "", 0);
+ clccResponseNative(0, 0, 0, 0, false, "", 0, getByteAddress(device));
}
}
- private void processAtCscs(String atString, int type) {
+ private void processAtCscs(String atString, int type, BluetoothDevice device) {
log("processAtCscs - atString = "+ atString);
if(mPhonebook != null) {
- mPhonebook.handleCscsCommand(atString, type);
+ mPhonebook.handleCscsCommand(atString, type, device);
}
else {
Log.e(TAG, "Phonebook handle null for At+CSCS");
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
}
}
- private void processAtCpbs(String atString, int type) {
+ private void processAtCpbs(String atString, int type, BluetoothDevice device) {
log("processAtCpbs - atString = "+ atString);
if(mPhonebook != null) {
- mPhonebook.handleCpbsCommand(atString, type);
+ mPhonebook.handleCpbsCommand(atString, type, device);
}
else {
Log.e(TAG, "Phonebook handle null for At+CPBS");
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
}
}
- private void processAtCpbr(String atString, int type, BluetoothDevice mCurrentDevice) {
+ private void processAtCpbr(String atString, int type, BluetoothDevice device) {
log("processAtCpbr - atString = "+ atString);
if(mPhonebook != null) {
- mPhonebook.handleCpbrCommand(atString, type, mCurrentDevice);
+ mPhonebook.handleCpbrCommand(atString, type, device);
}
else {
Log.e(TAG, "Phonebook handle null for At+CPBR");
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
}
}
@@ -1750,26 +2876,36 @@
BluetoothHeadset.AT_CMD_TYPE_SET,
args,
mCurrentDevice);
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0, getByteAddress(mCurrentDevice));
return true;
}
- private void processUnknownAt(String atString) {
+ private void processUnknownAt(String atString, BluetoothDevice device) {
+ if(device == null) {
+ Log.w(TAG, "processUnknownAt device is null");
+ return;
+ }
+
// TODO (BT)
log("processUnknownAt - atString = "+ atString);
String atCommand = parseUnknownAt(atString);
int commandType = getAtCommandType(atCommand);
if (atCommand.startsWith("+CSCS"))
- processAtCscs(atCommand.substring(5), commandType);
+ processAtCscs(atCommand.substring(5), commandType, device);
else if (atCommand.startsWith("+CPBS"))
- processAtCpbs(atCommand.substring(5), commandType);
+ processAtCpbs(atCommand.substring(5), commandType, device);
else if (atCommand.startsWith("+CPBR"))
- processAtCpbr(atCommand.substring(5), commandType, mCurrentDevice);
+ processAtCpbr(atCommand.substring(5), commandType, device);
else if (!processVendorSpecificAt(atCommand))
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
}
- private void processKeyPressed() {
+ private void processKeyPressed(BluetoothDevice device) {
+ if(device == null) {
+ Log.w(TAG, "processKeyPressed device is null");
+ return;
+ }
+
if (mPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING) {
if (mPhoneProxy != null) {
try {
@@ -1824,81 +2960,95 @@
sendMessage(STACK_EVENT, event);
}
- private void onVrStateChanged(int state) {
+ private void onVrStateChanged(int state, byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_VR_STATE_CHANGED);
event.valueInt = state;
+ event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
- private void onAnswerCall() {
+ private void onAnswerCall(byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_ANSWER_CALL);
+ event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
- private void onHangupCall() {
+ private void onHangupCall(byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_HANGUP_CALL);
+ event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
- private void onVolumeChanged(int type, int volume) {
+ private void onVolumeChanged(int type, int volume, byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_VOLUME_CHANGED);
event.valueInt = type;
event.valueInt2 = volume;
+ event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
- private void onDialCall(String number) {
+ private void onDialCall(String number, byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_DIAL_CALL);
event.valueString = number;
+ event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
- private void onSendDtmf(int dtmf) {
+ private void onSendDtmf(int dtmf, byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_SEND_DTMF);
event.valueInt = dtmf;
+ event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
- private void onNoiceReductionEnable(boolean enable) {
+ private void onNoiceReductionEnable(boolean enable, byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_NOICE_REDUCTION);
event.valueInt = enable ? 1 : 0;
+ event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
- private void onAtChld(int chld) {
+ private void onAtChld(int chld, byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_AT_CHLD);
event.valueInt = chld;
+ event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
- private void onAtCnum() {
+ private void onAtCnum(byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST);
+ event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
- private void onAtCind() {
+ private void onAtCind(byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_AT_CIND);
+ event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
- private void onAtCops() {
+ private void onAtCops(byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_AT_COPS);
+ event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
- private void onAtClcc() {
+ private void onAtClcc(byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_AT_CLCC);
+ event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
- private void onUnknownAt(String atString) {
+ private void onUnknownAt(String atString, byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_UNKNOWN_AT);
event.valueString = atString;
+ event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
- private void onKeyPressed() {
+ private void onKeyPressed(byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_KEY_PRESSED);
+ event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
@@ -1919,8 +3069,15 @@
}
private void processSendClccResponse(HeadsetClccResponse clcc) {
+ BluetoothDevice device = getDeviceForMessage(CLCC_RSP_TIMEOUT);
+ if (device == null) {
+ return;
+ }
+ if (clcc.mIndex == 0) {
+ removeMessages(CLCC_RSP_TIMEOUT);
+ }
clccResponseNative(clcc.mIndex, clcc.mDirection, clcc.mStatus, clcc.mMode, clcc.mMpty,
- clcc.mNumber, clcc.mType);
+ clcc.mNumber, clcc.mType, getByteAddress(device));
}
private void processSendVendorSpecificResultCode(HeadsetVendorSpecificResultCode resultCode) {
@@ -1928,15 +3085,17 @@
if (resultCode.mArg != null) {
stringToSend += resultCode.mArg;
}
- atResponseStringNative(stringToSend);
+ atResponseStringNative(stringToSend, getByteAddress(resultCode.mDevice));
}
- private String getCurrentDeviceName() {
+ private String getCurrentDeviceName(BluetoothDevice device) {
String defaultName = "<unknown>";
- if (mCurrentDevice == null) {
+
+ if(device == null) {
return defaultName;
}
- String deviceName = mCurrentDevice.getName();
+
+ String deviceName = device.getName();
if (deviceName == null) {
return defaultName;
}
@@ -1991,6 +3150,7 @@
public void handleAccessPermissionResult(Intent intent) {
log("handleAccessPermissionResult");
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if(mPhonebook != null) {
if (!mPhonebook.getCheckingAccessPermission()) {
return;
@@ -2008,21 +3168,24 @@
if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
mCurrentDevice.setTrust(true);
}
- atCommandResult = mPhonebook.processCpbrCommand();
+ atCommandResult = mPhonebook.processCpbrCommand(device);
}
}
mPhonebook.setCpbrIndex(-1);
mPhonebook.setCheckingAccessPermission(false);
if (atCommandResult >= 0) {
- atResponseCodeNative(atCommandResult, atCommandErrorCode);
+ atResponseCodeNative(atCommandResult, atCommandErrorCode, getByteAddress(device));
}
else
log("handleAccessPermissionResult - RESULT_NONE");
}
else {
Log.e(TAG, "Phonebook handle null");
- atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ if(device != null) {
+ atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0,
+ getByteAddress(device));
+ }
}
}
@@ -2059,28 +3222,30 @@
}
}
- /*package*/native boolean atResponseCodeNative(int responseCode, int errorCode);
- /*package*/ native boolean atResponseStringNative(String responseString);
+ /*package*/native boolean atResponseCodeNative(int responseCode, int errorCode,
+ byte[] address);
+ /*package*/ native boolean atResponseStringNative(String responseString, byte[] address);
private native static void classInitNative();
- private native void initializeNative();
+ private native void initializeNative(int max_hf_clients);
private native void cleanupNative();
private native boolean connectHfpNative(byte[] address);
private native boolean disconnectHfpNative(byte[] address);
private native boolean connectAudioNative(byte[] address);
private native boolean disconnectAudioNative(byte[] address);
- private native boolean startVoiceRecognitionNative();
- private native boolean stopVoiceRecognitionNative();
- private native boolean setVolumeNative(int volumeType, int volume);
+ private native boolean startVoiceRecognitionNative(byte[] address);
+ private native boolean stopVoiceRecognitionNative(byte[] address);
+ private native boolean setVolumeNative(int volumeType, int volume, byte[] address);
private native boolean cindResponseNative(int service, int numActive, int numHeld,
int callState, int signal, int roam,
- int batteryCharge);
+ int batteryCharge, byte[] address);
private native boolean notifyDeviceStatusNative(int networkState, int serviceType, int signal,
int batteryCharge);
private native boolean clccResponseNative(int index, int dir, int status, int mode,
- boolean mpty, String number, int type);
- private native boolean copsResponseNative(String operatorName);
+ boolean mpty, String number, int type,
+ byte[] address);
+ private native boolean copsResponseNative(String operatorName, byte[] address);
private native boolean phoneStateChangeNative(int numActive, int numHeld, int callState,
String number, int type);
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java b/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java
new file mode 100644
index 0000000..ba9655d
--- /dev/null
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2014 The Android Open Source Project
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.hfpclient;
+
+/*
+ * @hide
+ */
+
+final public class HeadsetClientHalConstants {
+ // Do not modify without updating the HAL bt_hf_client.h files.
+
+ // match up with bthf_client_connection_state_t enum of bt_hf_client.h
+ final static int CONNECTION_STATE_DISCONNECTED = 0;
+ final static int CONNECTION_STATE_CONNECTING = 1;
+ final static int CONNECTION_STATE_CONNECTED = 2;
+ final static int CONNECTION_STATE_SLC_CONNECTED = 3;
+ final static int CONNECTION_STATE_DISCONNECTING = 4;
+
+ // match up with bthf_client_audio_state_t enum of bt_hf_client.h
+ final static int AUDIO_STATE_DISCONNECTED = 0;
+ final static int AUDIO_STATE_CONNECTING = 1;
+ final static int AUDIO_STATE_CONNECTED = 2;
+ final static int AUDIO_STATE_CONNECTED_MSBC = 3;
+
+ // match up with bthf_client_vr_state_t enum of bt_hf_client.h
+ final static int VR_STATE_STOPPED = 0;
+ final static int VR_STATE_STARTED = 1;
+
+ // match up with bthf_client_volume_type_t enum of bt_hf_client.h
+ final static int VOLUME_TYPE_SPK = 0;
+ final static int VOLUME_TYPE_MIC = 1;
+
+ // match up with bthf_client_network_state_t enum of bt_hf_client.h
+ final static int NETWORK_STATE_NOT_AVAILABLE = 0;
+ final static int NETWORK_STATE_AVAILABLE = 1;
+
+ // match up with bthf_client_service_type_t enum of bt_hf_client.h
+ final static int SERVICE_TYPE_HOME = 0;
+ final static int SERVICE_TYPE_ROAMING = 1;
+
+ // match up with bthf_client_call_state_t enum of bt_hf_client.h
+ final static int CALL_STATE_ACTIVE = 0;
+ final static int CALL_STATE_HELD = 1;
+ final static int CALL_STATE_DIALING = 2;
+ final static int CALL_STATE_ALERTING = 3;
+ final static int CALL_STATE_INCOMING = 4;
+ final static int CALL_STATE_WAITING = 5;
+ final static int CALL_STATE_HELD_BY_RESP_HOLD = 6;
+
+ // match up with bthf_client_call_t enum of bt_hf_client.h
+ final static int CALL_NO_CALLS_IN_PROGRESS = 0;
+ final static int CALL_CALLS_IN_PROGRESS = 1;
+
+ // match up with bthf_client_callsetup_t enum of bt_hf_client.h
+ final static int CALLSETUP_NONE = 0;
+ final static int CALLSETUP_INCOMING = 1;
+ final static int CALLSETUP_OUTGOING = 2;
+ final static int CALLSETUP_ALERTING = 3;
+
+ // match up with bthf_client_callheld_t enum of bt_hf_client.h
+ final static int CALLHELD_NONE = 0;
+ final static int CALLHELD_HOLD_AND_ACTIVE = 1;
+ final static int CALLHELD_HOLD = 2;
+
+ // match up with btrh_client_resp_and_hold_t of bt_hf_client.h
+ final static int RESP_AND_HOLD_HELD = 0;
+ final static int RESP_AND_HOLD_ACCEPT = 1;
+ final static int RESP_AND_HOLD_REJECT = 2;
+
+ // match up with bthf_client_call_direction_t enum of bt_hf_client.h
+ final static int CALL_DIRECTION_OUTGOING = 0;
+ final static int CALL_DIRECTION_INCOMING = 1;
+
+ // match up with bthf_client_call_mpty_type_t enum of bt_hf_client.h
+ final static int CALL_MPTY_TYPE_SINGLE = 0;
+ final static int CALL_MPTY_TYPE_MULTI = 1;
+
+ // match up with bthf_client_cmd_complete_t enum of bt_hf_client.h
+ final static int CMD_COMPLETE_OK = 0;
+ final static int CMD_COMPLETE_ERROR = 1;
+ final static int CMD_COMPLETE_ERROR_NO_CARRIER = 2;
+ final static int CMD_COMPLETE_ERROR_BUSY = 3;
+ final static int CMD_COMPLETE_ERROR_NO_ANSWER = 4;
+ final static int CMD_COMPLETE_ERROR_DELAYED = 5;
+ final static int CMD_COMPLETE_ERROR_BLACKLISTED = 6;
+ final static int CMD_COMPLETE_ERROR_CME = 7;
+
+ // match up with bthf_client_call_action_t enum of bt_hf_client.h
+ final static int CALL_ACTION_CHLD_0 = 0;
+ final static int CALL_ACTION_CHLD_1 = 1;
+ final static int CALL_ACTION_CHLD_2 = 2;
+ final static int CALL_ACTION_CHLD_3 = 3;
+ final static int CALL_ACTION_CHLD_4 = 4;
+ final static int CALL_ACTION_CHLD_1x = 5;
+ final static int CALL_ACTION_CHLD_2x = 6;
+ final static int CALL_ACTION_ATA = 7;
+ final static int CALL_ACTION_CHUP = 8;
+ final static int CALL_ACTION_BTRH_0 = 9;
+ final static int CALL_ACTION_BTRH_1 = 10;
+ final static int CALL_ACTION_BTRH_2 = 11;
+
+ // match up with bthf_client_subscriber_service_type_t enum of
+ // bt_hf_client.h
+ final static int SUBSCRIBER_SERVICE_TYPE_UNKNOWN = 0;
+ final static int SUBSCRIBER_SERVICE_TYPE_VOICE = 1;
+ final static int SUBSCRIBER_SERVICE_TYPE_FAX = 2;
+
+ // match up with bthf_client_in_band_ring_state_t enum in bt_hf_client.h
+ final static int IN_BAND_RING_NOT_PROVIDED = 0;
+ final static int IN_BAND_RING_PROVIDED = 1;
+
+ // AG features masks
+ // match up with masks in bt_hf_client.h
+ // Three-way calling
+ final static int PEER_FEAT_3WAY = 0x00000001;
+ // Echo cancellation and/or noise reduction
+ final static int PEER_FEAT_ECNR = 0x00000002;
+ // Voice recognition
+ final static int PEER_FEAT_VREC = 0x00000004;
+ // In-band ring tone
+ final static int PEER_FEAT_INBAND = 0x00000008;
+ // Attach a phone number to a voice tag
+ final static int PEER_FEAT_VTAG = 0x00000010;
+ // Ability to reject incoming call
+ final static int PEER_FEAT_REJECT = 0x00000020;
+ // Enhanced Call Status
+ final static int PEER_FEAT_ECS = 0x00000040;
+ // Enhanced Call Control
+ final static int PEER_FEAT_ECC = 0x00000080;
+ // Extended error codes
+ final static int PEER_FEAT_EXTERR = 0x00000100;
+ // Codec Negotiation
+ final static int PEER_FEAT_CODEC = 0x00000200;
+
+ // AG's 3WC features masks
+ // match up with masks in bt_hf_client.h
+ // 0 Release waiting call or held calls
+ final static int CHLD_FEAT_REL = 0x00000001;
+ // 1 Release active calls and accept other (waiting or held) cal
+ final static int CHLD_FEAT_REL_ACC = 0x00000002;
+ // 1x Release specified active call only
+ final static int CHLD_FEAT_REL_X = 0x00000004;
+ // 2 Active calls on hold and accept other (waiting or held) call
+ final static int CHLD_FEAT_HOLD_ACC = 0x00000008;
+ // 2x Request private mode with specified call (put the rest on hold)
+ final static int CHLD_FEAT_PRIV_X = 0x00000010;
+ // 3 Add held call to multiparty */
+ final static int CHLD_FEAT_MERGE = 0x00000020;
+ // 4 Connect two calls and leave (disconnect from) multiparty */
+ final static int CHLD_FEAT_MERGE_DETACH = 0x00000040;
+
+ // AT Commands
+ // These Commands values must match with Constants defined in
+ // tBTA_HF_CLIENT_AT_CMD_TYPE in bta_hf_client_api.h
+ // used for sending vendor specific AT cmds to AG.
+
+ final static int HANDSFREECLIENT_AT_CMD_NREC = 15;
+
+ // Flag to check for local NREC support
+ final static boolean HANDSFREECLIENT_NREC_SUPPORTED = true;
+}
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
new file mode 100644
index 0000000..aaf7156
--- /dev/null
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
@@ -0,0 +1,737 @@
+/*
+ * Copyright (c) 2014 The Android Open Source Project
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.hfpclient;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothHeadsetClientCall;
+import android.bluetooth.IBluetoothHeadsetClient;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Message;
+import android.provider.Settings;
+import android.util.Log;
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.Utils;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides Bluetooth Headset Client (HF Role) profile, as a service in the
+ * Bluetooth application.
+ *
+ * @hide
+ */
+public class HeadsetClientService extends ProfileService {
+ private static final boolean DBG = false;
+ private static final String TAG = "HeadsetClientService";
+
+ private HeadsetClientStateMachine mStateMachine;
+ private static HeadsetClientService sHeadsetClientService;
+
+ @Override
+ protected String getName() {
+ return TAG;
+ }
+
+ @Override
+ public IProfileServiceBinder initBinder() {
+ return new BluetoothHeadsetClientBinder(this);
+ }
+
+ @Override
+ protected boolean start() {
+ mStateMachine = HeadsetClientStateMachine.make(this);
+ IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
+ filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
+ try {
+ registerReceiver(mBroadcastReceiver, filter);
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to register broadcat receiver", e);
+ }
+ setHeadsetClientService(this);
+ return true;
+ }
+
+ @Override
+ protected boolean stop() {
+ try {
+ unregisterReceiver(mBroadcastReceiver);
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to unregister broadcast receiver", e);
+ }
+ mStateMachine.doQuit();
+ return true;
+ }
+
+ @Override
+ protected boolean cleanup() {
+ if (mStateMachine != null) {
+ mStateMachine.cleanup();
+ }
+ clearHeadsetClientService();
+ return true;
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+ int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) {
+ int streamValue = intent
+ .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
+ int streamPrevValue = intent.getIntExtra(
+ AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
+
+ if (streamValue != -1 && streamValue != streamPrevValue) {
+ mStateMachine.sendMessage(mStateMachine.obtainMessage(
+ HeadsetClientStateMachine.SET_SPEAKER_VOLUME, streamValue, 0));
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * Handlers for incoming service calls
+ */
+ private static class BluetoothHeadsetClientBinder extends IBluetoothHeadsetClient.Stub
+ implements IProfileServiceBinder {
+ private HeadsetClientService mService;
+
+ public BluetoothHeadsetClientBinder(HeadsetClientService svc) {
+ mService = svc;
+ }
+
+ @Override
+ public boolean cleanup() {
+ mService = null;
+ return true;
+ }
+
+ private HeadsetClientService getService() {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG, "HeadsetClient call not allowed for non-active user");
+ return null;
+ }
+
+ if (mService != null && mService.isAvailable()) {
+ return mService;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.disconnect(device);
+ }
+
+ @Override
+ public List<BluetoothDevice> getConnectedDevices() {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return service.getConnectedDevices();
+ }
+
+ @Override
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return service.getDevicesMatchingConnectionStates(states);
+ }
+
+ @Override
+ public int getConnectionState(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ return service.getConnectionState(device);
+ }
+
+ @Override
+ public boolean setPriority(BluetoothDevice device, int priority) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.setPriority(device, priority);
+ }
+
+ @Override
+ public int getPriority(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return BluetoothProfile.PRIORITY_UNDEFINED;
+ }
+ return service.getPriority(device);
+ }
+
+ @Override
+ public boolean startVoiceRecognition(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.startVoiceRecognition(device);
+ }
+
+ @Override
+ public boolean stopVoiceRecognition(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.stopVoiceRecognition(device);
+ }
+
+ @Override
+ public boolean acceptIncomingConnect(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.acceptIncomingConnect(device);
+ }
+
+ @Override
+ public boolean rejectIncomingConnect(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.rejectIncomingConnect(device);
+ }
+
+ @Override
+ public int getAudioState(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
+ }
+ return service.getAudioState(device);
+ }
+
+ @Override
+ public boolean connectAudio() {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.connectAudio();
+ }
+
+ @Override
+ public boolean disconnectAudio() {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.disconnectAudio();
+ }
+
+ @Override
+ public boolean acceptCall(BluetoothDevice device, int flag) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.acceptCall(device, flag);
+ }
+
+ @Override
+ public boolean rejectCall(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.rejectCall(device);
+ }
+
+ @Override
+ public boolean holdCall(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.holdCall(device);
+ }
+
+ @Override
+ public boolean terminateCall(BluetoothDevice device, int index) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.terminateCall(device, index);
+ }
+
+ @Override
+ public boolean explicitCallTransfer(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.explicitCallTransfer(device);
+ }
+
+ @Override
+ public boolean enterPrivateMode(BluetoothDevice device, int index) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.enterPrivateMode(device, index);
+ }
+
+ @Override
+ public boolean redial(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.redial(device);
+ }
+
+ @Override
+ public boolean dial(BluetoothDevice device, String number) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.dial(device, number);
+ }
+
+ @Override
+ public boolean dialMemory(BluetoothDevice device, int location) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.dialMemory(device, location);
+ }
+
+ @Override
+ public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return null;
+ }
+ return service.getCurrentCalls(device);
+ }
+
+ @Override
+ public boolean sendDTMF(BluetoothDevice device, byte code) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.sendDTMF(device, code);
+ }
+
+ @Override
+ public boolean getLastVoiceTagNumber(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.getLastVoiceTagNumber(device);
+ }
+
+ @Override
+ public Bundle getCurrentAgEvents(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return null;
+ }
+ return service.getCurrentAgEvents(device);
+ }
+
+ @Override
+ public Bundle getCurrentAgFeatures(BluetoothDevice device) {
+ HeadsetClientService service = getService();
+ if (service == null) {
+ return null;
+ }
+ return service.getCurrentAgFeatures(device);
+ }
+ };
+
+ // API methods
+ public static synchronized HeadsetClientService getHeadsetClientService() {
+ if (sHeadsetClientService != null && sHeadsetClientService.isAvailable()) {
+ if (DBG) {
+ Log.d(TAG, "getHeadsetClientService(): returning " + sHeadsetClientService);
+ }
+ return sHeadsetClientService;
+ }
+ if (DBG) {
+ if (sHeadsetClientService == null) {
+ Log.d(TAG, "getHeadsetClientService(): service is NULL");
+ } else if (!(sHeadsetClientService.isAvailable())) {
+ Log.d(TAG, "getHeadsetClientService(): service is not available");
+ }
+ }
+ return null;
+ }
+
+ private static synchronized void setHeadsetClientService(HeadsetClientService instance) {
+ if (instance != null && instance.isAvailable()) {
+ if (DBG) {
+ Log.d(TAG, "setHeadsetClientService(): set to: " + sHeadsetClientService);
+ }
+ sHeadsetClientService = instance;
+ } else {
+ if (DBG) {
+ if (sHeadsetClientService == null) {
+ Log.d(TAG, "setHeadsetClientService(): service not available");
+ } else if (!sHeadsetClientService.isAvailable()) {
+ Log.d(TAG, "setHeadsetClientService(): service is cleaning up");
+ }
+ }
+ }
+ }
+
+ private static synchronized void clearHeadsetClientService() {
+ sHeadsetClientService = null;
+ }
+
+ public boolean connect(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH ADMIN permission");
+
+ if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
+ return false;
+ }
+
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState == BluetoothProfile.STATE_CONNECTED ||
+ connectionState == BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+
+ mStateMachine.sendMessage(HeadsetClientStateMachine.CONNECT, device);
+ return true;
+ }
+
+ boolean disconnect(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH ADMIN permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+
+ mStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT, device);
+ return true;
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mStateMachine.getConnectedDevices();
+ }
+
+ private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mStateMachine.getDevicesMatchingConnectionStates(states);
+ }
+
+ int getConnectionState(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mStateMachine.getConnectionState(device);
+ }
+
+ // TODO Should new setting for HeadsetClient priority be created?
+ public boolean setPriority(BluetoothDevice device, int priority) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
+ priority);
+ if (DBG) {
+ Log.d(TAG, "Saved priority " + device + " = " + priority);
+ }
+ return true;
+ }
+
+ public int getPriority(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
+ int priority = Settings.Global.getInt(getContentResolver(),
+ Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
+ BluetoothProfile.PRIORITY_UNDEFINED);
+ return priority;
+ }
+
+ boolean startVoiceRecognition(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+ mStateMachine.sendMessage(HeadsetClientStateMachine.VOICE_RECOGNITION_START);
+ return true;
+ }
+
+ boolean stopVoiceRecognition(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ // It seem that we really need to check the AudioOn state.
+ // But since we allow startVoiceRecognition in STATE_CONNECTED and
+ // STATE_CONNECTING state, we do these 2 in this method
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+ mStateMachine.sendMessage(HeadsetClientStateMachine.VOICE_RECOGNITION_STOP);
+ return true;
+ }
+
+ boolean acceptIncomingConnect(BluetoothDevice device) {
+ // TODO(BT) remove it if stack does access control
+ return false;
+ }
+
+ boolean rejectIncomingConnect(BluetoothDevice device) {
+ // TODO(BT) remove it if stack does access control
+ return false;
+ }
+
+ int getAudioState(BluetoothDevice device) {
+ return mStateMachine.getAudioState(device);
+ }
+
+ boolean connectAudio() {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+ if (!mStateMachine.isConnected()) {
+ return false;
+ }
+ if (mStateMachine.isAudioOn()) {
+ return false;
+ }
+ mStateMachine.sendMessage(HeadsetClientStateMachine.CONNECT_AUDIO);
+ return true;
+ }
+
+ boolean disconnectAudio() {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+ if (!mStateMachine.isAudioOn()) {
+ return false;
+ }
+ mStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT_AUDIO);
+ return true;
+ }
+
+ boolean holdCall(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+ Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.HOLD_CALL);
+ mStateMachine.sendMessage(msg);
+ return true;
+ }
+
+ boolean acceptCall(BluetoothDevice device, int flag) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+ Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.ACCEPT_CALL);
+ msg.arg1 = flag;
+ mStateMachine.sendMessage(msg);
+ return true;
+ }
+
+ boolean rejectCall(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+
+ Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.REJECT_CALL);
+ mStateMachine.sendMessage(msg);
+ return true;
+ }
+
+ boolean terminateCall(BluetoothDevice device, int index) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+
+ Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.TERMINATE_CALL);
+ msg.arg1 = index;
+ mStateMachine.sendMessage(msg);
+ return true;
+ }
+
+ boolean enterPrivateMode(BluetoothDevice device, int index) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+
+ Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.ENTER_PRIVATE_MODE);
+ msg.arg1 = index;
+ mStateMachine.sendMessage(msg);
+ return true;
+ }
+
+ boolean redial(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+
+ Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.REDIAL);
+ mStateMachine.sendMessage(msg);
+ return true;
+ }
+
+ boolean dial(BluetoothDevice device, String number) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+
+ Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.DIAL_NUMBER);
+ msg.obj = number;
+ mStateMachine.sendMessage(msg);
+ return true;
+ }
+
+ boolean dialMemory(BluetoothDevice device, int location) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+ Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.DIAL_MEMORY);
+ msg.arg1 = location;
+ mStateMachine.sendMessage(msg);
+ return true;
+ }
+
+ public boolean sendDTMF(BluetoothDevice device, byte code) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+ Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.SEND_DTMF);
+ msg.arg1 = code;
+ mStateMachine.sendMessage(msg);
+ return true;
+ }
+
+ public boolean getLastVoiceTagNumber(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+ Message msg = mStateMachine.obtainMessage(HeadsetClientStateMachine.LAST_VTAG_NUMBER);
+ mStateMachine.sendMessage(msg);
+ return true;
+ }
+
+ public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED) {
+ return null;
+ }
+ return mStateMachine.getCurrentCalls();
+ }
+
+ public boolean explicitCallTransfer(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+ connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+ Message msg = mStateMachine
+ .obtainMessage(HeadsetClientStateMachine.EXPLICIT_CALL_TRANSFER);
+ mStateMachine.sendMessage(msg);
+ return true;
+ }
+
+ public Bundle getCurrentAgEvents(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED) {
+ return null;
+ }
+ return mStateMachine.getCurrentAgEvents();
+ }
+
+ public Bundle getCurrentAgFeatures(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ int connectionState = mStateMachine.getConnectionState(device);
+ if (connectionState != BluetoothProfile.STATE_CONNECTED) {
+ return null;
+ }
+ return mStateMachine.getCurrentAgFeatures();
+ }
+}
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
new file mode 100644
index 0000000..d50602a
--- /dev/null
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@@ -0,0 +1,2695 @@
+/*
+ * Copyright (c) 2014 The Android Open Source Project
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Bluetooth Headset Client StateMachine
+ * (Disconnected)
+ * | ^ ^
+ * CONNECT | | | DISCONNECTED
+ * V | |
+ * (Connecting) |
+ * | |
+ * CONNECTED | | DISCONNECT
+ * V |
+ * (Connected)
+ * | ^
+ * CONNECT_AUDIO | | DISCONNECT_AUDIO
+ * V |
+ * (AudioOn)
+ */
+
+package com.android.bluetooth.hfpclient;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothHeadsetClientCall;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.util.Log;
+import android.util.Pair;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+
+import com.android.internal.util.IState;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.ProfileService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+
+final class HeadsetClientStateMachine extends StateMachine {
+ private static final String TAG = "HeadsetClientStateMachine";
+ private static final boolean DBG = false;
+
+ static final int NO_ACTION = 0;
+
+ // external actions
+ static final int CONNECT = 1;
+ static final int DISCONNECT = 2;
+ static final int CONNECT_AUDIO = 3;
+ static final int DISCONNECT_AUDIO = 4;
+ static final int VOICE_RECOGNITION_START = 5;
+ static final int VOICE_RECOGNITION_STOP = 6;
+ static final int SET_MIC_VOLUME = 7;
+ static final int SET_SPEAKER_VOLUME = 8;
+ static final int REDIAL = 9;
+ static final int DIAL_NUMBER = 10;
+ static final int DIAL_MEMORY = 11;
+ static final int ACCEPT_CALL = 12;
+ static final int REJECT_CALL = 13;
+ static final int HOLD_CALL = 14;
+ static final int TERMINATE_CALL = 15;
+ static final int ENTER_PRIVATE_MODE = 16;
+ static final int SEND_DTMF = 17;
+ static final int EXPLICIT_CALL_TRANSFER = 18;
+ static final int LAST_VTAG_NUMBER = 19;
+
+ // internal actions
+ static final int QUERY_CURRENT_CALLS = 50;
+ static final int QUERY_OPERATOR_NAME = 51;
+ static final int SUBSCRIBER_INFO = 52;
+ // special action to handle terminating specific call from multiparty call
+ static final int TERMINATE_SPECIFIC_CALL = 53;
+
+ private static final int STACK_EVENT = 100;
+
+ private final Disconnected mDisconnected;
+ private final Connecting mConnecting;
+ private final Connected mConnected;
+ private final AudioOn mAudioOn;
+
+ private final HeadsetClientService mService;
+
+ private Hashtable<Integer, BluetoothHeadsetClientCall> mCalls;
+ private Hashtable<Integer, BluetoothHeadsetClientCall> mCallsUpdate;
+ private boolean mQueryCallsSupported;
+
+ private int mIndicatorNetworkState;
+ private int mIndicatorNetworkType;
+ private int mIndicatorNetworkSignal;
+ private int mIndicatorBatteryLevel;
+
+ private int mIndicatorCall;
+ private int mIndicatorCallSetup;
+ private int mIndicatorCallHeld;
+ private boolean mVgsFromStack = false;
+ private boolean mVgmFromStack = false;
+ private Uri alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
+ private Ringtone mRingtone = null;
+
+ private String mOperatorName;
+ private String mSubscriberInfo;
+
+ private int mVoiceRecognitionActive;
+ private int mInBandRingtone;
+
+ // queue of send actions (pair action, action_data)
+ private Queue<Pair<Integer, Object>> mQueuedActions;
+
+ // last executed command, before action is complete e.g. waiting for some
+ // indicator
+ private Pair<Integer, Object> mPendingAction;
+
+ private final AudioManager mAudioManager;
+ private int mAudioState;
+ private boolean mAudioWbs;
+ private final BluetoothAdapter mAdapter;
+ private boolean mNativeAvailable;
+
+ // currently connected device
+ private BluetoothDevice mCurrentDevice = null;
+
+ // general peer features and call handling features
+ private int mPeerFeatures;
+ private int mChldFeatures;
+
+ static {
+ classInitNative();
+ }
+
+ private void clearPendingAction() {
+ mPendingAction = new Pair<Integer, Object>(NO_ACTION, 0);
+ }
+
+ private void addQueuedAction(int action) {
+ addQueuedAction(action, 0);
+ }
+
+ private void addQueuedAction(int action, Object data) {
+ mQueuedActions.add(new Pair<Integer, Object>(action, data));
+ }
+
+ private void addQueuedAction(int action, int data) {
+ mQueuedActions.add(new Pair<Integer, Object>(action, data));
+ }
+
+ private void addCall(int state, String number) {
+ Log.d(TAG, "addToCalls state:" + state + " number:" + number);
+
+ boolean outgoing = state == BluetoothHeadsetClientCall.CALL_STATE_DIALING ||
+ state == BluetoothHeadsetClientCall.CALL_STATE_ALERTING;
+
+ // new call always takes lowest possible id, starting with 1
+ Integer id = 1;
+ while (mCalls.containsKey(id)) {
+ id++;
+ }
+
+ BluetoothHeadsetClientCall c = new BluetoothHeadsetClientCall(id, state, number, false,
+ outgoing);
+ mCalls.put(id, c);
+
+ sendCallChangedIntent(c);
+ }
+
+ private void removeCalls(int... states) {
+ Log.d(TAG, "removeFromCalls states:" + Arrays.toString(states));
+
+ Iterator<Hashtable.Entry<Integer, BluetoothHeadsetClientCall>> it;
+
+ it = mCalls.entrySet().iterator();
+ while (it.hasNext()) {
+ BluetoothHeadsetClientCall c = it.next().getValue();
+
+ for (int s : states) {
+ if (c.getState() == s) {
+ it.remove();
+ setCallState(c, BluetoothHeadsetClientCall.CALL_STATE_TERMINATED);
+ break;
+ }
+ }
+ }
+ }
+
+ private void changeCallsState(int old_state, int new_state) {
+ Log.d(TAG, "changeStateFromCalls old:" + old_state + " new: " + new_state);
+
+ for (BluetoothHeadsetClientCall c : mCalls.values()) {
+ if (c.getState() == old_state) {
+ setCallState(c, new_state);
+ }
+ }
+ }
+
+ private BluetoothHeadsetClientCall getCall(int... states) {
+ Log.d(TAG, "getFromCallsWithStates states:" + Arrays.toString(states));
+ for (BluetoothHeadsetClientCall c : mCalls.values()) {
+ for (int s : states) {
+ if (c.getState() == s) {
+ return c;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private int callsInState(int state) {
+ int i = 0;
+ for (BluetoothHeadsetClientCall c : mCalls.values()) {
+ if (c.getState() == state) {
+ i++;
+ }
+ }
+
+ return i;
+ }
+
+ private void updateCallsMultiParty() {
+ boolean multi = callsInState(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) > 1;
+
+ for (BluetoothHeadsetClientCall c : mCalls.values()) {
+ if (c.getState() == BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) {
+ if (c.isMultiParty() == multi) {
+ continue;
+ }
+
+ c.setMultiParty(multi);
+ sendCallChangedIntent(c);
+ } else {
+ if (c.isMultiParty()) {
+ c.setMultiParty(false);
+ sendCallChangedIntent(c);
+ }
+ }
+ }
+ }
+
+ private void setCallState(BluetoothHeadsetClientCall c, int state) {
+ if (state == c.getState()) {
+ return;
+ }
+ //abandon focus here
+ if (state == BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) {
+ if (mAudioManager.getMode() != AudioManager.MODE_NORMAL) {
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ Log.d(TAG, "abandonAudioFocus ");
+ // abandon audio focus after the mode has been set back to normal
+ mAudioManager.abandonAudioFocusForCall();
+ }
+ }
+ c.setState(state);
+ sendCallChangedIntent(c);
+ }
+
+ private void sendCallChangedIntent(BluetoothHeadsetClientCall c) {
+ Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CALL_CHANGED);
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_CALL, c);
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+
+ private boolean waitForIndicators(int call, int callsetup, int callheld) {
+ // all indicators initial values received
+ if (mIndicatorCall != -1 && mIndicatorCallSetup != -1 &&
+ mIndicatorCallHeld != -1) {
+ return false;
+ }
+
+ if (call != -1) {
+ mIndicatorCall = call;
+ } else if (callsetup != -1) {
+ mIndicatorCallSetup = callsetup;
+ } else if (callheld != -1) {
+ mIndicatorCallHeld = callheld;
+ }
+
+ // still waiting for some indicators
+ if (mIndicatorCall == -1 || mIndicatorCallSetup == -1 ||
+ mIndicatorCallHeld == -1) {
+ return true;
+ }
+
+ // for start always query calls to define if it is supported
+ mQueryCallsSupported = queryCallsStart();
+
+ if (mQueryCallsSupported) {
+ return true;
+ }
+
+ // no support for querying calls
+
+ switch (mIndicatorCallSetup) {
+ case HeadsetClientHalConstants.CALLSETUP_INCOMING:
+ addCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING, "");
+ break;
+ case HeadsetClientHalConstants.CALLSETUP_OUTGOING:
+ addCall(BluetoothHeadsetClientCall.CALL_STATE_DIALING, "");
+ break;
+ case HeadsetClientHalConstants.CALLSETUP_ALERTING:
+ addCall(BluetoothHeadsetClientCall.CALL_STATE_ALERTING, "");
+ break;
+ case HeadsetClientHalConstants.CALLSETUP_NONE:
+ default:
+ break;
+ }
+
+ switch (mIndicatorCall) {
+ case HeadsetClientHalConstants.CALL_CALLS_IN_PROGRESS:
+ addCall(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE, "");
+ break;
+ case HeadsetClientHalConstants.CALL_NO_CALLS_IN_PROGRESS:
+ default:
+ break;
+ }
+
+ switch (mIndicatorCallHeld) {
+ case HeadsetClientHalConstants.CALLHELD_HOLD_AND_ACTIVE:
+ case HeadsetClientHalConstants.CALLHELD_HOLD:
+ addCall(BluetoothHeadsetClientCall.CALL_STATE_HELD, "");
+ break;
+ case HeadsetClientHalConstants.CALLHELD_NONE:
+ default:
+ break;
+ }
+
+ return true;
+ }
+
+ private void updateCallIndicator(int call) {
+ Log.d(TAG, "updateCallIndicator " + call);
+
+ if (waitForIndicators(call, -1, -1)) {
+ return;
+ }
+
+ if (mQueryCallsSupported) {
+ sendMessage(QUERY_CURRENT_CALLS);
+ return;
+ }
+
+ BluetoothHeadsetClientCall c = null;
+
+ switch (call) {
+ case HeadsetClientHalConstants.CALL_NO_CALLS_IN_PROGRESS:
+ removeCalls(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE,
+ BluetoothHeadsetClientCall.CALL_STATE_HELD,
+ BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD);
+
+ break;
+ case HeadsetClientHalConstants.CALL_CALLS_IN_PROGRESS:
+ if (mIndicatorCall == HeadsetClientHalConstants.CALL_CALLS_IN_PROGRESS) {
+ // WP7.8 is sending call=1 before setup=0 when rejecting
+ // waiting call
+ if (mIndicatorCallSetup != HeadsetClientHalConstants.CALLSETUP_NONE) {
+ c = getCall(BluetoothHeadsetClientCall.CALL_STATE_WAITING);
+ if (c != null) {
+ setCallState(c, BluetoothHeadsetClientCall.CALL_STATE_TERMINATED);
+ mCalls.remove(c.getId());
+ }
+ }
+
+ break;
+ }
+
+ // if there is only waiting call it is changed to incoming so
+ // don't
+ // handle it here
+ if (mIndicatorCallSetup != HeadsetClientHalConstants.CALLSETUP_NONE) {
+ c = getCall(BluetoothHeadsetClientCall.CALL_STATE_DIALING,
+ BluetoothHeadsetClientCall.CALL_STATE_ALERTING,
+ BluetoothHeadsetClientCall.CALL_STATE_INCOMING);
+ if (c != null) {
+ setCallState(c, BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ }
+ }
+
+ updateCallsMultiParty();
+ break;
+ default:
+ break;
+ }
+
+ mIndicatorCall = call;
+ }
+
+ private void updateCallSetupIndicator(int callsetup) {
+ Log.d(TAG, "updateCallSetupIndicator " + callsetup + " " + mPendingAction.first);
+
+ if (mRingtone != null && mRingtone.isPlaying()) {
+ Log.d(TAG,"stopping ring after no response");
+ mRingtone.stop();
+ }
+
+ if (waitForIndicators(-1, callsetup, -1)) {
+ return;
+ }
+
+ if (mQueryCallsSupported) {
+ sendMessage(QUERY_CURRENT_CALLS);
+ return;
+ }
+
+ switch (callsetup) {
+ case HeadsetClientHalConstants.CALLSETUP_NONE:
+ switch (mPendingAction.first) {
+ case ACCEPT_CALL:
+ switch ((Integer) mPendingAction.second) {
+ case HeadsetClientHalConstants.CALL_ACTION_ATA:
+ removeCalls(BluetoothHeadsetClientCall.CALL_STATE_DIALING,
+ BluetoothHeadsetClientCall.CALL_STATE_ALERTING);
+ clearPendingAction();
+ break;
+ case HeadsetClientHalConstants.CALL_ACTION_CHLD_1:
+ removeCalls(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ changeCallsState(BluetoothHeadsetClientCall.CALL_STATE_WAITING,
+ BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ clearPendingAction();
+ break;
+ case HeadsetClientHalConstants.CALL_ACTION_CHLD_2:
+ // no specific order for callsetup=0 and
+ // callheld=1
+ if (mIndicatorCallHeld ==
+ HeadsetClientHalConstants.CALLHELD_HOLD_AND_ACTIVE) {
+ clearPendingAction();
+ }
+ break;
+ case HeadsetClientHalConstants.CALL_ACTION_CHLD_3:
+ if (mIndicatorCallHeld ==
+ HeadsetClientHalConstants.CALLHELD_NONE) {
+ clearPendingAction();
+ }
+ break;
+ default:
+ Log.e(TAG, "Unexpected callsetup=0 while in action ACCEPT_CALL");
+ break;
+ }
+ break;
+ case REJECT_CALL:
+ switch ((Integer) mPendingAction.second) {
+ case HeadsetClientHalConstants.CALL_ACTION_CHUP:
+ removeCalls(BluetoothHeadsetClientCall.CALL_STATE_INCOMING);
+ clearPendingAction();
+ break;
+ case HeadsetClientHalConstants.CALL_ACTION_CHLD_0:
+ removeCalls(BluetoothHeadsetClientCall.CALL_STATE_WAITING);
+ clearPendingAction();
+ break;
+ default:
+ Log.e(TAG, "Unexpected callsetup=0 while in action REJECT_CALL");
+ break;
+ }
+ break;
+ case DIAL_NUMBER:
+ case DIAL_MEMORY:
+ case REDIAL:
+ case NO_ACTION:
+ case TERMINATE_CALL:
+ removeCalls(BluetoothHeadsetClientCall.CALL_STATE_INCOMING,
+ BluetoothHeadsetClientCall.CALL_STATE_DIALING,
+ BluetoothHeadsetClientCall.CALL_STATE_WAITING,
+ BluetoothHeadsetClientCall.CALL_STATE_ALERTING);
+ clearPendingAction();
+ break;
+ default:
+ Log.e(TAG, "Unexpected callsetup=0 while in action " +
+ mPendingAction.first);
+ break;
+ }
+ break;
+ case HeadsetClientHalConstants.CALLSETUP_ALERTING:
+ BluetoothHeadsetClientCall c =
+ getCall(BluetoothHeadsetClientCall.CALL_STATE_DIALING);
+ if (c == null) {
+ if (mPendingAction.first == DIAL_NUMBER) {
+ addCall(BluetoothHeadsetClientCall.CALL_STATE_ALERTING,
+ (String) mPendingAction.second);
+ } else {
+ addCall(BluetoothHeadsetClientCall.CALL_STATE_ALERTING, "");
+ }
+ } else {
+ setCallState(c, BluetoothHeadsetClientCall.CALL_STATE_ALERTING);
+ }
+
+ switch (mPendingAction.first) {
+ case DIAL_NUMBER:
+ case DIAL_MEMORY:
+ case REDIAL:
+ clearPendingAction();
+ break;
+ default:
+ break;
+ }
+ break;
+ case HeadsetClientHalConstants.CALLSETUP_OUTGOING:
+ if (mPendingAction.first == DIAL_NUMBER) {
+ addCall(BluetoothHeadsetClientCall.CALL_STATE_DIALING,
+ (String) mPendingAction.second);
+ } else {
+ addCall(BluetoothHeadsetClientCall.CALL_STATE_DIALING, "");
+ }
+ break;
+ case HeadsetClientHalConstants.CALLSETUP_INCOMING:
+ if (getCall(BluetoothHeadsetClientCall.CALL_STATE_WAITING) == null)
+ {
+ // will get number in clip if known
+ addCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING, "");
+ }
+ break;
+ default:
+ break;
+ }
+
+ updateCallsMultiParty();
+
+ mIndicatorCallSetup = callsetup;
+ }
+
+ private void updateCallHeldIndicator(int callheld) {
+ Log.d(TAG, "updateCallHeld " + callheld);
+
+ if (waitForIndicators(-1, -1, callheld)) {
+ return;
+ }
+
+ if (mQueryCallsSupported) {
+ sendMessage(QUERY_CURRENT_CALLS);
+ return;
+ }
+
+ switch (callheld) {
+ case HeadsetClientHalConstants.CALLHELD_NONE:
+ switch (mPendingAction.first) {
+ case REJECT_CALL:
+ removeCalls(BluetoothHeadsetClientCall.CALL_STATE_HELD);
+ clearPendingAction();
+ break;
+ case ACCEPT_CALL:
+ switch ((Integer) mPendingAction.second) {
+ case HeadsetClientHalConstants.CALL_ACTION_CHLD_1:
+ removeCalls(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ changeCallsState(BluetoothHeadsetClientCall.CALL_STATE_HELD,
+ BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ clearPendingAction();
+ break;
+ case HeadsetClientHalConstants.CALL_ACTION_CHLD_3:
+ changeCallsState(BluetoothHeadsetClientCall.CALL_STATE_HELD,
+ BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ clearPendingAction();
+ break;
+ default:
+ break;
+ }
+ break;
+ case NO_ACTION:
+ if (mIndicatorCall == HeadsetClientHalConstants.CALL_CALLS_IN_PROGRESS &&
+ mIndicatorCallHeld == HeadsetClientHalConstants.CALLHELD_HOLD) {
+ changeCallsState(BluetoothHeadsetClientCall.CALL_STATE_HELD,
+ BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ break;
+ }
+
+ removeCalls(BluetoothHeadsetClientCall.CALL_STATE_HELD);
+ break;
+ default:
+ Log.e(TAG, "Unexpected callheld=0 while in action " + mPendingAction.first);
+ break;
+ }
+ break;
+ case HeadsetClientHalConstants.CALLHELD_HOLD_AND_ACTIVE:
+ switch (mPendingAction.first) {
+ case ACCEPT_CALL:
+ if ((Integer) mPendingAction.second ==
+ HeadsetClientHalConstants.CALL_ACTION_CHLD_2) {
+ BluetoothHeadsetClientCall c =
+ getCall(BluetoothHeadsetClientCall.CALL_STATE_WAITING);
+ if (c != null) { // accept
+ changeCallsState(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE,
+ BluetoothHeadsetClientCall.CALL_STATE_HELD);
+ setCallState(c, BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ } else { // swap
+ for (BluetoothHeadsetClientCall cc : mCalls.values()) {
+ if (cc.getState() ==
+ BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) {
+ setCallState(cc,
+ BluetoothHeadsetClientCall.CALL_STATE_HELD);
+ } else if (cc.getState() ==
+ BluetoothHeadsetClientCall.CALL_STATE_HELD) {
+ setCallState(cc,
+ BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ }
+ }
+ }
+ clearPendingAction();
+ }
+ break;
+ case NO_ACTION:
+ BluetoothHeadsetClientCall c =
+ getCall(BluetoothHeadsetClientCall.CALL_STATE_WAITING);
+ if (c != null) { // accept
+ changeCallsState(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE,
+ BluetoothHeadsetClientCall.CALL_STATE_HELD);
+ setCallState(c, BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ break;
+ }
+
+ // swap
+ for (BluetoothHeadsetClientCall cc : mCalls.values()) {
+ if (cc.getState() == BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) {
+ setCallState(cc, BluetoothHeadsetClientCall.CALL_STATE_HELD);
+ } else if (cc.getState() == BluetoothHeadsetClientCall.CALL_STATE_HELD) {
+ setCallState(cc, BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ }
+ }
+ break;
+ case ENTER_PRIVATE_MODE:
+ for (BluetoothHeadsetClientCall cc : mCalls.values()) {
+ if (cc != (BluetoothHeadsetClientCall) mPendingAction.second) {
+ setCallState(cc, BluetoothHeadsetClientCall.CALL_STATE_HELD);
+ }
+ }
+ clearPendingAction();
+ break;
+ default:
+ Log.e(TAG, "Unexpected callheld=0 while in action " + mPendingAction.first);
+ break;
+ }
+ break;
+ case HeadsetClientHalConstants.CALLHELD_HOLD:
+ switch (mPendingAction.first) {
+ case DIAL_NUMBER:
+ case DIAL_MEMORY:
+ case REDIAL:
+ changeCallsState(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE,
+ BluetoothHeadsetClientCall.CALL_STATE_HELD);
+ break;
+ case REJECT_CALL:
+ switch ((Integer) mPendingAction.second) {
+ case HeadsetClientHalConstants.CALL_ACTION_CHLD_1:
+ removeCalls(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ changeCallsState(BluetoothHeadsetClientCall.CALL_STATE_HELD,
+ BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ clearPendingAction();
+ break;
+ case HeadsetClientHalConstants.CALL_ACTION_CHLD_3:
+ changeCallsState(BluetoothHeadsetClientCall.CALL_STATE_HELD,
+ BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ clearPendingAction();
+ break;
+ default:
+ break;
+ }
+ break;
+ case TERMINATE_CALL:
+ case NO_ACTION:
+ removeCalls(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ break;
+ default:
+ Log.e(TAG, "Unexpected callheld=0 while in action " + mPendingAction.first);
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ updateCallsMultiParty();
+
+ mIndicatorCallHeld = callheld;
+ }
+
+ private void updateRespAndHold(int resp_and_hold) {
+ Log.d(TAG, "updatRespAndHold " + resp_and_hold);
+
+ if (mQueryCallsSupported) {
+ sendMessage(QUERY_CURRENT_CALLS);
+ return;
+ }
+
+ BluetoothHeadsetClientCall c = null;
+
+ switch (resp_and_hold) {
+ case HeadsetClientHalConstants.RESP_AND_HOLD_HELD:
+ // might be active if it was resp-and-hold before SLC created
+ c = getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING,
+ BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ if (c != null) {
+ setCallState(c,
+ BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD);
+ } else {
+ addCall(BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD, "");
+ }
+ break;
+ case HeadsetClientHalConstants.RESP_AND_HOLD_ACCEPT:
+ c = getCall(BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD);
+ if (c != null) {
+ setCallState(c, BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ }
+ if (mPendingAction.first == ACCEPT_CALL &&
+ (Integer) mPendingAction.second ==
+ HeadsetClientHalConstants.CALL_ACTION_BTRH_1) {
+ clearPendingAction();
+ }
+ break;
+ case HeadsetClientHalConstants.RESP_AND_HOLD_REJECT:
+ removeCalls(BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void updateClip(String number) {
+ Log.d(TAG, "updateClip number: " + number);
+
+ BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING);
+
+ if (c == null) {
+ // MeeGo sends CLCC indicating waiting call followed by CLIP when call state changes
+ // from waiting to incoming in 3WC scenarios. Handle this call state transfer here.
+ BluetoothHeadsetClientCall cw = getCall(BluetoothHeadsetClientCall.CALL_STATE_WAITING);
+ if(cw != null) {
+ setCallState(cw, BluetoothHeadsetClientCall.CALL_STATE_INCOMING);
+ }
+ else {
+ addCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING, number);
+ }
+ } else {
+ c.setNumber(number);
+ sendCallChangedIntent(c);
+ }
+ }
+
+ private void addCallWaiting(String number) {
+ Log.d(TAG, "addCallWaiting number: " + number);
+
+ if (getCall(BluetoothHeadsetClientCall.CALL_STATE_WAITING) == null) {
+ addCall(BluetoothHeadsetClientCall.CALL_STATE_WAITING, number);
+ }
+ }
+
+ // use ECS
+ private boolean queryCallsStart() {
+ Log.d(TAG, "queryCallsStart");
+
+ // not supported
+ if (mQueryCallsSupported == false) {
+ return false;
+ }
+
+ clearPendingAction();
+
+ // already started
+ if (mCallsUpdate != null) {
+ return true;
+ }
+
+ if (queryCurrentCallsNative()) {
+ mCallsUpdate = new Hashtable<Integer, BluetoothHeadsetClientCall>();
+ addQueuedAction(QUERY_CURRENT_CALLS, 0);
+ return true;
+ }
+
+ Log.i(TAG, "updateCallsStart queryCurrentCallsNative failed");
+ mQueryCallsSupported = false;
+ mCallsUpdate = null;
+ return false;
+ }
+
+ private void queryCallsDone() {
+ Log.d(TAG, "queryCallsDone");
+ Iterator<Hashtable.Entry<Integer, BluetoothHeadsetClientCall>> it;
+
+ // check if any call was removed
+ it = mCalls.entrySet().iterator();
+ while (it.hasNext()) {
+ Hashtable.Entry<Integer, BluetoothHeadsetClientCall> entry = it.next();
+
+ if (mCallsUpdate.containsKey(entry.getKey())) {
+ continue;
+ }
+
+ Log.d(TAG, "updateCallsDone call removed id:" + entry.getValue().getId());
+ BluetoothHeadsetClientCall c = entry.getValue();
+
+ setCallState(c, BluetoothHeadsetClientCall.CALL_STATE_TERMINATED);
+ }
+
+ /* check if any calls changed or new call is present */
+ it = mCallsUpdate.entrySet().iterator();
+ while (it.hasNext()) {
+ Hashtable.Entry<Integer, BluetoothHeadsetClientCall> entry = it.next();
+
+ if (mCalls.containsKey(entry.getKey())) {
+ // avoid losing number if was not present in clcc
+ if (entry.getValue().getNumber().equals("")) {
+ entry.getValue().setNumber(mCalls.get(entry.getKey()).getNumber());
+ }
+
+ if (mCalls.get(entry.getKey()).equals(entry.getValue())) {
+ continue;
+ }
+
+ Log.d(TAG, "updateCallsDone call changed id:" + entry.getValue().getId());
+ sendCallChangedIntent(entry.getValue());
+ } else {
+ Log.d(TAG, "updateCallsDone new call id:" + entry.getValue().getId());
+ sendCallChangedIntent(entry.getValue());
+ }
+ }
+
+ mCalls = mCallsUpdate;
+ mCallsUpdate = null;
+
+ if (loopQueryCalls()) {
+ Log.d(TAG, "queryCallsDone ambigious calls, starting call query loop");
+ sendMessageDelayed(QUERY_CURRENT_CALLS, 1523);
+ }
+ }
+
+ private void queryCallsUpdate(int id, int state, String number, boolean multiParty,
+ boolean outgoing) {
+ Log.d(TAG, "queryCallsUpdate: " + id);
+
+ // should not happen
+ if (mCallsUpdate == null) {
+ return;
+ }
+
+ mCallsUpdate.put(id, new BluetoothHeadsetClientCall(id, state, number, multiParty,
+ outgoing));
+ }
+
+ // helper function for determining if query calls should be looped
+ private boolean loopQueryCalls() {
+ if (callsInState(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) > 1) {
+ return true;
+ }
+
+ // Workaround for Windows Phone 7.8 not sending callsetup=0 after
+ // rejecting incoming call in 3WC use case (when no active calls present).
+ // Fixes both, AG and HF rejecting the call.
+ BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING);
+ if (c != null && mIndicatorCallSetup == HeadsetClientHalConstants.CALLSETUP_NONE)
+ return true;
+
+ return false;
+ }
+
+ private void acceptCall(int flag, boolean retry) {
+ int action;
+
+ Log.d(TAG, "acceptCall: (" + flag + ")");
+
+ BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING,
+ BluetoothHeadsetClientCall.CALL_STATE_WAITING);
+ if (c == null) {
+ c = getCall(BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD,
+ BluetoothHeadsetClientCall.CALL_STATE_HELD);
+
+ if (c == null) {
+ return;
+ }
+ }
+
+ switch (c.getState()) {
+ case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
+ if (flag != BluetoothHeadsetClient.CALL_ACCEPT_NONE) {
+ return;
+ }
+
+ // Some NOKIA phones with Windows Phone 7.8 and MeeGo requires CHLD=1
+ // for accepting incoming call if it is the only call present after
+ // second active remote has disconnected (3WC scenario - call state
+ // changes from waiting to incoming). On the other hand some Android
+ // phones and iPhone requires ATA. Try to handle those gently by
+ // first issuing ATA. Failing means that AG is probably one of those
+ // phones that requires CHLD=1. Handle this case when we are retrying.
+ // Accepting incoming calls when there is held one and
+ // no active should also be handled by ATA.
+ action = HeadsetClientHalConstants.CALL_ACTION_ATA;
+
+ if (mCalls.size() == 1 && retry) {
+ action = HeadsetClientHalConstants.CALL_ACTION_CHLD_1;
+ }
+ break;
+ case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
+ if (callsInState(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) == 0) {
+ // if no active calls present only plain accept is allowed
+ if (flag != BluetoothHeadsetClient.CALL_ACCEPT_NONE) {
+ return;
+ }
+
+ // Some phones (WP7) require ATA instead of CHLD=2
+ // to accept waiting call if no active calls are present.
+ if (retry) {
+ action = HeadsetClientHalConstants.CALL_ACTION_ATA;
+ } else {
+ action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2;
+ }
+ break;
+ }
+
+ // if active calls are present action must be selected
+ if (flag == BluetoothHeadsetClient.CALL_ACCEPT_HOLD) {
+ action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2;
+ } else if (flag == BluetoothHeadsetClient.CALL_ACCEPT_TERMINATE) {
+ action = HeadsetClientHalConstants.CALL_ACTION_CHLD_1;
+ } else {
+ return;
+ }
+ break;
+ case BluetoothHeadsetClientCall.CALL_STATE_HELD:
+ if (flag == BluetoothHeadsetClient.CALL_ACCEPT_HOLD) {
+ action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2;
+ } else if (flag == BluetoothHeadsetClient.CALL_ACCEPT_TERMINATE) {
+ action = HeadsetClientHalConstants.CALL_ACTION_CHLD_1;
+ } else if (getCall(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) != null) {
+ action = HeadsetClientHalConstants.CALL_ACTION_CHLD_3;
+ } else {
+ action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2;
+ }
+ break;
+ case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
+ if (flag != BluetoothHeadsetClient.CALL_ACCEPT_NONE) {
+ return;
+ }
+ action = HeadsetClientHalConstants.CALL_ACTION_BTRH_1;
+ break;
+ case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
+ case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
+ case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
+ default:
+ return;
+ }
+
+ if (handleCallActionNative(action, 0)) {
+ addQueuedAction(ACCEPT_CALL, action);
+ } else {
+ Log.e(TAG, "ERROR: Couldn't accept a call, action:" + action);
+ }
+ }
+
+ private void rejectCall() {
+ int action;
+
+ Log.d(TAG, "rejectCall");
+ if ( mRingtone != null && mRingtone.isPlaying()) {
+ Log.d(TAG,"stopping ring after call reject");
+ mRingtone.stop();
+ }
+
+ BluetoothHeadsetClientCall c =
+ getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING,
+ BluetoothHeadsetClientCall.CALL_STATE_WAITING,
+ BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD,
+ BluetoothHeadsetClientCall.CALL_STATE_HELD);
+ if (c == null) {
+ return;
+ }
+
+ switch (c.getState()) {
+ case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
+ action = HeadsetClientHalConstants.CALL_ACTION_CHUP;
+ break;
+ case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
+ case BluetoothHeadsetClientCall.CALL_STATE_HELD:
+ action = HeadsetClientHalConstants.CALL_ACTION_CHLD_0;
+ break;
+ case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
+ action = HeadsetClientHalConstants.CALL_ACTION_BTRH_2;
+ break;
+ case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
+ case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
+ case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
+ default:
+ return;
+ }
+
+ if (handleCallActionNative(action, 0)) {
+ addQueuedAction(REJECT_CALL, action);
+ } else {
+ Log.e(TAG, "ERROR: Couldn't reject a call, action:" + action);
+ }
+ }
+
+ private void holdCall() {
+ int action;
+
+ Log.d(TAG, "holdCall");
+
+ BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING);
+ if (c != null) {
+ action = HeadsetClientHalConstants.CALL_ACTION_BTRH_0;
+ } else {
+ c = getCall(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+ if (c == null) {
+ return;
+ }
+
+ action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2;
+ }
+
+ if (handleCallActionNative(action, 0)) {
+ addQueuedAction(HOLD_CALL, action);
+ } else {
+ Log.e(TAG, "ERROR: Couldn't hold a call, action:" + action);
+ }
+ }
+
+ private void terminateCall(int idx) {
+ Log.d(TAG, "terminateCall: " + idx);
+
+ if (idx == 0) {
+ int action = HeadsetClientHalConstants.CALL_ACTION_CHUP;
+
+ BluetoothHeadsetClientCall c = getCall(
+ BluetoothHeadsetClientCall.CALL_STATE_DIALING,
+ BluetoothHeadsetClientCall.CALL_STATE_ALERTING);
+ if (c != null) {
+ if (handleCallActionNative(action, 0)) {
+ addQueuedAction(TERMINATE_CALL, action);
+ } else {
+ Log.e(TAG, "ERROR: Couldn't terminate outgoing call");
+ }
+ }
+
+ if (callsInState(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) > 0) {
+ if (handleCallActionNative(action, 0)) {
+ addQueuedAction(TERMINATE_CALL, action);
+ } else {
+ Log.e(TAG, "ERROR: Couldn't terminate active calls");
+ }
+ }
+ } else {
+ int action;
+ BluetoothHeadsetClientCall c = mCalls.get(idx);
+
+ if (c == null) {
+ return;
+ }
+
+ switch (c.getState()) {
+ case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
+ action = HeadsetClientHalConstants.CALL_ACTION_CHLD_1x;
+ break;
+ case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
+ case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
+ action = HeadsetClientHalConstants.CALL_ACTION_CHUP;
+ break;
+ default:
+ return;
+ }
+
+ if (handleCallActionNative(action, idx)) {
+ if (action == HeadsetClientHalConstants.CALL_ACTION_CHLD_1x) {
+ addQueuedAction(TERMINATE_SPECIFIC_CALL, c);
+ } else {
+ addQueuedAction(TERMINATE_CALL, action);
+ }
+ } else {
+ Log.e(TAG, "ERROR: Couldn't terminate a call, action:" + action + " id:" + idx);
+ }
+ }
+ }
+
+ private void enterPrivateMode(int idx) {
+ Log.d(TAG, "enterPrivateMode: " + idx);
+
+ BluetoothHeadsetClientCall c = mCalls.get(idx);
+
+ if (c == null) {
+ return;
+ }
+
+ if (c.getState() != BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) {
+ return;
+ }
+
+ if (!c.isMultiParty()) {
+ return;
+ }
+
+ if (handleCallActionNative(HeadsetClientHalConstants.CALL_ACTION_CHLD_2x, idx)) {
+ addQueuedAction(ENTER_PRIVATE_MODE, c);
+ } else {
+ Log.e(TAG, "ERROR: Couldn't enter private " + " id:" + idx);
+ }
+ }
+
+ private void explicitCallTransfer() {
+ Log.d(TAG, "explicitCallTransfer");
+
+ // can't transfer call if there is not enough call parties
+ if (mCalls.size() < 2) {
+ return;
+ }
+
+ if (handleCallActionNative(HeadsetClientHalConstants.CALL_ACTION_CHLD_4, -1)) {
+ addQueuedAction(EXPLICIT_CALL_TRANSFER);
+ } else {
+ Log.e(TAG, "ERROR: Couldn't transfer call");
+ }
+ }
+
+ public Bundle getCurrentAgFeatures()
+ {
+ Bundle b = new Bundle();
+ if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_3WAY) ==
+ HeadsetClientHalConstants.PEER_FEAT_3WAY) {
+ b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
+ }
+ if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_VREC) ==
+ HeadsetClientHalConstants.PEER_FEAT_VREC) {
+ b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION, true);
+ }
+ if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_VTAG) ==
+ HeadsetClientHalConstants.PEER_FEAT_VTAG) {
+ b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT, true);
+ }
+ if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT) ==
+ HeadsetClientHalConstants.PEER_FEAT_REJECT) {
+ b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
+ }
+ if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECC) ==
+ HeadsetClientHalConstants.PEER_FEAT_ECC) {
+ b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC, true);
+ }
+
+ // add individual CHLD support extras
+ if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC) ==
+ HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC) {
+ b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL, true);
+ }
+ if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL) ==
+ HeadsetClientHalConstants.CHLD_FEAT_REL) {
+ b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL, true);
+ }
+ if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL_ACC) ==
+ HeadsetClientHalConstants.CHLD_FEAT_REL_ACC) {
+ b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT, true);
+ }
+ if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE) ==
+ HeadsetClientHalConstants.CHLD_FEAT_MERGE) {
+ b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE, true);
+ }
+ if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) ==
+ HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) {
+ b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE_AND_DETACH, true);
+ }
+
+ return b;
+ }
+
+ private HeadsetClientStateMachine(HeadsetClientService context) {
+ super(TAG);
+ mService = context;
+
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
+ mAudioWbs = false;
+
+ if(alert == null) {
+ // alert is null, using backup
+ alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+ if(alert == null) {
+ // alert backup is null, using 2nd backup
+ alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+ }
+ }
+ if (alert != null) {
+ mRingtone = RingtoneManager.getRingtone(mService, alert);
+ } else {
+ Log.e(TAG,"alert is NULL no ringtone");
+ }
+ mIndicatorNetworkState = HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE;
+ mIndicatorNetworkType = HeadsetClientHalConstants.SERVICE_TYPE_HOME;
+ mIndicatorNetworkSignal = 0;
+ mIndicatorBatteryLevel = 0;
+
+ // all will be set on connected
+ mIndicatorCall = -1;
+ mIndicatorCallSetup = -1;
+ mIndicatorCallHeld = -1;
+
+ mOperatorName = null;
+ mSubscriberInfo = null;
+
+ mVoiceRecognitionActive = HeadsetClientHalConstants.VR_STATE_STOPPED;
+ mInBandRingtone = HeadsetClientHalConstants.IN_BAND_RING_NOT_PROVIDED;
+
+ mQueuedActions = new LinkedList<Pair<Integer, Object>>();
+ clearPendingAction();
+
+ mCalls = new Hashtable<Integer, BluetoothHeadsetClientCall>();
+ mCallsUpdate = null;
+ mQueryCallsSupported = true;
+
+ initializeNative();
+ mNativeAvailable = true;
+
+ mDisconnected = new Disconnected();
+ mConnecting = new Connecting();
+ mConnected = new Connected();
+ mAudioOn = new AudioOn();
+
+ addState(mDisconnected);
+ addState(mConnecting);
+ addState(mConnected);
+ addState(mAudioOn, mConnected);
+
+ setInitialState(mDisconnected);
+ }
+
+ static HeadsetClientStateMachine make(HeadsetClientService context) {
+ Log.d(TAG, "make");
+ HeadsetClientStateMachine hfcsm = new HeadsetClientStateMachine(context);
+ hfcsm.start();
+ return hfcsm;
+ }
+
+ public void doQuit() {
+ quitNow();
+ }
+
+ public void cleanup() {
+ if (mNativeAvailable) {
+ cleanupNative();
+ mNativeAvailable = false;
+ }
+ }
+
+ private class Disconnected extends State {
+ @Override
+ public void enter() {
+ Log.d(TAG, "Enter Disconnected: " + getCurrentMessage().what);
+
+ // cleanup
+ mIndicatorNetworkState = HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE;
+ mIndicatorNetworkType = HeadsetClientHalConstants.SERVICE_TYPE_HOME;
+ mIndicatorNetworkSignal = 0;
+ mIndicatorBatteryLevel = 0;
+
+ mAudioWbs = false;
+
+ // will be set on connect
+ mIndicatorCall = -1;
+ mIndicatorCallSetup = -1;
+ mIndicatorCallHeld = -1;
+
+ mOperatorName = null;
+ mSubscriberInfo = null;
+
+ mQueuedActions = new LinkedList<Pair<Integer, Object>>();
+ clearPendingAction();
+
+ mVoiceRecognitionActive = HeadsetClientHalConstants.VR_STATE_STOPPED;
+ mInBandRingtone = HeadsetClientHalConstants.IN_BAND_RING_NOT_PROVIDED;
+
+ mCalls = new Hashtable<Integer, BluetoothHeadsetClientCall>();
+ mCallsUpdate = null;
+ mQueryCallsSupported = true;
+
+ mPeerFeatures = 0;
+ mChldFeatures = 0;
+
+ removeMessages(QUERY_CURRENT_CALLS);
+ }
+
+ @Override
+ public synchronized boolean processMessage(Message message) {
+ Log.d(TAG, "Disconnected process message: " + message.what);
+
+ if (mCurrentDevice != null) {
+ Log.e(TAG, "ERROR: current device not null in Disconnected");
+ return NOT_HANDLED;
+ }
+
+ switch (message.what) {
+ case CONNECT:
+ BluetoothDevice device = (BluetoothDevice) message.obj;
+
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTED);
+
+ if (!connectNative(getByteAddress(device))) {
+ broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ break;
+ }
+
+ mCurrentDevice = device;
+ transitionTo(mConnecting);
+ break;
+ case DISCONNECT:
+ // ignore
+ break;
+ case STACK_EVENT:
+ StackEvent event = (StackEvent) message.obj;
+ if (DBG) {
+ Log.d(TAG, "Stack event type: " + event.type);
+ }
+ switch (event.type) {
+ case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ Log.d(TAG, "Disconnected: Connection " + event.device
+ + " state changed:" + event.valueInt);
+ processConnectionEvent(event.valueInt, event.device);
+ break;
+ default:
+ Log.e(TAG, "Disconnected: Unexpected stack event: " + event.type);
+ break;
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ // in Disconnected state
+ private void processConnectionEvent(int state, BluetoothDevice device)
+ {
+ switch (state) {
+ case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED:
+ Log.w(TAG, "HFPClient Connecting from Disconnected state");
+ if (okToConnect(device)) {
+ Log.i(TAG, "Incoming AG accepted");
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTED);
+ mCurrentDevice = device;
+ transitionTo(mConnecting);
+ } else {
+ Log.i(TAG, "Incoming AG rejected. priority=" + mService.getPriority(device)
+ +
+ " bondState=" + device.getBondState());
+ // reject the connection and stay in Disconnected state
+ // itself
+ disconnectNative(getByteAddress(device));
+ // the other profile connection should be initiated
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if (adapterService != null) {
+ adapterService.connectOtherProfile(device,
+ AdapterService.PROFILE_CONN_REJECTED);
+ }
+ }
+ break;
+ case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTING:
+ case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
+ case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTING:
+ default:
+ Log.i(TAG, "ignoring state: " + state);
+ break;
+ }
+ }
+
+ @Override
+ public void exit() {
+ Log.d(TAG, "Exit Disconnected: " + getCurrentMessage().what);
+ }
+ }
+
+ private class Connecting extends State {
+ @Override
+ public void enter() {
+ Log.d(TAG, "Enter Connecting: " + getCurrentMessage().what);
+ }
+
+ @Override
+ public synchronized boolean processMessage(Message message) {
+ Log.d(TAG, "Connecting process message: " + message.what);
+
+ boolean retValue = HANDLED;
+ switch (message.what) {
+ case CONNECT:
+ case CONNECT_AUDIO:
+ case DISCONNECT:
+ deferMessage(message);
+ break;
+ case STACK_EVENT:
+ StackEvent event = (StackEvent) message.obj;
+ if (DBG) {
+ Log.d(TAG, "Connecting: event type: " + event.type);
+ }
+ switch (event.type) {
+ case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ Log.d(TAG, "Connecting: Connection " + event.device + " state changed:"
+ + event.valueInt);
+ processConnectionEvent(event.valueInt, event.valueInt2,
+ event.valueInt3, event.device);
+ break;
+ case EVENT_TYPE_AUDIO_STATE_CHANGED:
+ case EVENT_TYPE_VR_STATE_CHANGED:
+ case EVENT_TYPE_NETWORK_STATE:
+ case EVENT_TYPE_ROAMING_STATE:
+ case EVENT_TYPE_NETWORK_SIGNAL:
+ case EVENT_TYPE_BATTERY_LEVEL:
+ case EVENT_TYPE_CALL:
+ case EVENT_TYPE_CALLSETUP:
+ case EVENT_TYPE_CALLHELD:
+ case EVENT_TYPE_RESP_AND_HOLD:
+ case EVENT_TYPE_CLIP:
+ case EVENT_TYPE_CALL_WAITING:
+ case EVENT_TYPE_VOLUME_CHANGED:
+ case EVENT_TYPE_IN_BAND_RING:
+ deferMessage(message);
+ break;
+ case EVENT_TYPE_CMD_RESULT:
+ case EVENT_TYPE_SUBSCRIBER_INFO:
+ case EVENT_TYPE_CURRENT_CALLS:
+ case EVENT_TYPE_OPERATOR_NAME:
+ default:
+ Log.e(TAG, "Connecting: ignoring stack event: " + event.type);
+ break;
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return retValue;
+ }
+
+ // in Connecting state
+ private void processConnectionEvent(int state, int peer_feat, int chld_feat, BluetoothDevice device) {
+ switch (state) {
+ case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
+ broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ mCurrentDevice = null;
+ transitionTo(mDisconnected);
+ break;
+ case HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED:
+ Log.w(TAG, "HFPClient Connected from Connecting state");
+
+ mPeerFeatures = peer_feat;
+ mChldFeatures = chld_feat;
+
+ broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ // Send AT+NREC to remote if supported by audio
+ if (HeadsetClientHalConstants.HANDSFREECLIENT_NREC_SUPPORTED) {
+ sendATCmdNative(HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_NREC,
+ 1 , 0, null);
+ }
+ transitionTo(mConnected);
+
+ // TODO get max stream volume and scale 0-15
+ sendMessage(obtainMessage(HeadsetClientStateMachine.SET_SPEAKER_VOLUME,
+ mAudioManager.getStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO), 0));
+ sendMessage(obtainMessage(HeadsetClientStateMachine.SET_MIC_VOLUME,
+ mAudioManager.isMicrophoneMute() ? 0 : 15, 0));
+
+ // query subscriber info
+ sendMessage(HeadsetClientStateMachine.SUBSCRIBER_INFO);
+ break;
+ case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED:
+ if (!mCurrentDevice.equals(device)) {
+ Log.w(TAG, "incoming connection event, device: " + device);
+
+ broadcastConnectionState(mCurrentDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTED);
+
+ mCurrentDevice = device;
+ }
+ break;
+ case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTING:
+ /* outgoing connecting started */
+ Log.d(TAG, "outgoing connection started, ignore");
+ break;
+ case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTING:
+ default:
+ Log.e(TAG, "Incorrect state: " + state);
+ break;
+ }
+ }
+
+ @Override
+ public void exit() {
+ Log.d(TAG, "Exit Connecting: " + getCurrentMessage().what);
+ }
+ }
+
+ private class Connected extends State {
+ @Override
+ public void enter() {
+ Log.d(TAG, "Enter Connected: " + getCurrentMessage().what);
+
+ mAudioWbs = false;
+ }
+
+ @Override
+ public synchronized boolean processMessage(Message message) {
+ Log.d(TAG, "Connected process message: " + message.what);
+ if (DBG) {
+ if (mCurrentDevice == null) {
+ Log.d(TAG, "ERROR: mCurrentDevice is null in Connected");
+ return NOT_HANDLED;
+ }
+ }
+
+ switch (message.what) {
+ case CONNECT:
+ BluetoothDevice device = (BluetoothDevice) message.obj;
+ if (mCurrentDevice.equals(device)) {
+ // already connected to this device, do nothing
+ break;
+ }
+
+ if (!disconnectNative(getByteAddress(mCurrentDevice))) {
+ // if succeed this will be handled from disconnected
+ // state
+ broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTED);
+ broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
+ break;
+ }
+
+ // will be handled when entered disconnected
+ deferMessage(message);
+ break;
+ case DISCONNECT:
+ BluetoothDevice dev = (BluetoothDevice) message.obj;
+ if (!mCurrentDevice.equals(dev)) {
+ break;
+ }
+ broadcastConnectionState(dev, BluetoothProfile.STATE_DISCONNECTING,
+ BluetoothProfile.STATE_CONNECTED);
+ if (!disconnectNative(getByteAddress(dev))) {
+ // disconnecting failed
+ broadcastConnectionState(dev, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ break;
+ }
+ break;
+ case CONNECT_AUDIO:
+ // TODO: handle audio connection failure
+ if (!connectAudioNative(getByteAddress(mCurrentDevice))) {
+ Log.e(TAG, "ERROR: Couldn't connect Audio.");
+ }
+ break;
+ case DISCONNECT_AUDIO:
+ // TODO: handle audio disconnection failure
+ if (!disconnectAudioNative(getByteAddress(mCurrentDevice))) {
+ Log.e(TAG, "ERROR: Couldn't connect Audio.");
+ }
+ break;
+ case VOICE_RECOGNITION_START:
+ if (mVoiceRecognitionActive == HeadsetClientHalConstants.VR_STATE_STOPPED) {
+ if (startVoiceRecognitionNative()) {
+ addQueuedAction(VOICE_RECOGNITION_START);
+ } else {
+ Log.e(TAG, "ERROR: Couldn't start voice recognition");
+ }
+ }
+ break;
+ case VOICE_RECOGNITION_STOP:
+ if (mVoiceRecognitionActive == HeadsetClientHalConstants.VR_STATE_STARTED) {
+ if (stopVoiceRecognitionNative()) {
+ addQueuedAction(VOICE_RECOGNITION_STOP);
+ } else {
+ Log.e(TAG, "ERROR: Couldn't stop voice recognition");
+ }
+ }
+ break;
+ case SET_MIC_VOLUME:
+ if (mVgmFromStack) {
+ mVgmFromStack = false;
+ break;
+ }
+ if (setVolumeNative(HeadsetClientHalConstants.VOLUME_TYPE_MIC, message.arg1)) {
+ addQueuedAction(SET_MIC_VOLUME);
+ }
+ break;
+ case SET_SPEAKER_VOLUME:
+ Log.d(TAG,"Volume is set to " + message.arg1);
+ mAudioManager.setParameters("hfp_volume=" + message.arg1);
+ if (mVgsFromStack) {
+ mVgsFromStack = false;
+ break;
+ }
+ if (setVolumeNative(HeadsetClientHalConstants.VOLUME_TYPE_SPK, message.arg1)) {
+ addQueuedAction(SET_SPEAKER_VOLUME);
+ }
+ break;
+ case REDIAL:
+ if (dialNative(null)) {
+ addQueuedAction(REDIAL);
+ } else {
+ Log.e(TAG, "ERROR: Cannot redial");
+ }
+ break;
+ case DIAL_NUMBER:
+ if (dialNative((String) message.obj)) {
+ addQueuedAction(DIAL_NUMBER, message.obj);
+ } else {
+ Log.e(TAG, "ERROR: Cannot dial with a given number:" + (String) message.obj);
+ }
+ break;
+ case DIAL_MEMORY:
+ if (dialMemoryNative(message.arg1)) {
+ addQueuedAction(DIAL_MEMORY);
+ } else {
+ Log.e(TAG, "ERROR: Cannot dial with a given location:" + message.arg1);
+ }
+ break;
+ case ACCEPT_CALL:
+ acceptCall(message.arg1, false);
+ break;
+ case REJECT_CALL:
+ rejectCall();
+ break;
+ case HOLD_CALL:
+ holdCall();
+ break;
+ case TERMINATE_CALL:
+ terminateCall(message.arg1);
+ break;
+ case ENTER_PRIVATE_MODE:
+ enterPrivateMode(message.arg1);
+ break;
+ case EXPLICIT_CALL_TRANSFER:
+ explicitCallTransfer();
+ break;
+ case SEND_DTMF:
+ if (sendDtmfNative((byte) message.arg1)) {
+ addQueuedAction(SEND_DTMF);
+ } else {
+ Log.e(TAG, "ERROR: Couldn't send DTMF");
+ }
+ break;
+ case SUBSCRIBER_INFO:
+ if (retrieveSubscriberInfoNative()) {
+ addQueuedAction(SUBSCRIBER_INFO);
+ } else {
+ Log.e(TAG, "ERROR: Couldn't retrieve subscriber info");
+ }
+ break;
+ case LAST_VTAG_NUMBER:
+ if (requestLastVoiceTagNumberNative()) {
+ addQueuedAction(LAST_VTAG_NUMBER);
+ } else {
+ Log.e(TAG, "ERROR: Couldn't get last VTAG number");
+ }
+ break;
+ case QUERY_CURRENT_CALLS:
+ queryCallsStart();
+ break;
+ case STACK_EVENT:
+ Intent intent = null;
+ StackEvent event = (StackEvent) message.obj;
+ if (DBG) {
+ Log.d(TAG, "Connected: event type: " + event.type);
+ }
+
+ switch (event.type) {
+ case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ Log.d(TAG, "Connected: Connection state changed: " + event.device
+ + ": " + event.valueInt);
+ processConnectionEvent(event.valueInt, event.device);
+ break;
+ case EVENT_TYPE_AUDIO_STATE_CHANGED:
+ Log.d(TAG, "Connected: Audio state changed: " + event.device + ": "
+ + event.valueInt);
+ processAudioEvent(event.valueInt, event.device);
+ break;
+ case EVENT_TYPE_NETWORK_STATE:
+ Log.d(TAG, "Connected: Network state: " + event.valueInt);
+
+ mIndicatorNetworkState = event.valueInt;
+
+ intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_STATUS,
+ event.valueInt);
+
+ if (mIndicatorNetworkState ==
+ HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
+ mOperatorName = null;
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_OPERATOR_NAME,
+ mOperatorName);
+ }
+
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+
+ if (mIndicatorNetworkState ==
+ HeadsetClientHalConstants.NETWORK_STATE_AVAILABLE) {
+ if (queryCurrentOperatorNameNative()) {
+ addQueuedAction(QUERY_OPERATOR_NAME);
+ } else {
+ Log.e(TAG, "ERROR: Couldn't querry operator name");
+ }
+ }
+ break;
+ case EVENT_TYPE_ROAMING_STATE:
+ Log.d(TAG, "Connected: Roaming state: " + event.valueInt);
+
+ mIndicatorNetworkType = event.valueInt;
+
+ intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_ROAMING,
+ event.valueInt);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ break;
+ case EVENT_TYPE_NETWORK_SIGNAL:
+ Log.d(TAG, "Connected: Signal level: " + event.valueInt);
+
+ mIndicatorNetworkSignal = event.valueInt;
+
+ intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH,
+ event.valueInt);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ break;
+ case EVENT_TYPE_BATTERY_LEVEL:
+ Log.d(TAG, "Connected: Battery level: " + event.valueInt);
+
+ mIndicatorBatteryLevel = event.valueInt;
+
+ intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL,
+ event.valueInt);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ break;
+ case EVENT_TYPE_OPERATOR_NAME:
+ Log.d(TAG, "Connected: Operator name: " + event.valueString);
+
+ mOperatorName = event.valueString;
+
+ intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_OPERATOR_NAME,
+ event.valueString);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ break;
+ case EVENT_TYPE_VR_STATE_CHANGED:
+ Log.d(TAG, "Connected: Voice recognition state: " + event.valueInt);
+
+ if (mVoiceRecognitionActive != event.valueInt) {
+ mVoiceRecognitionActive = event.valueInt;
+
+ intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION,
+ mVoiceRecognitionActive);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+ break;
+ case EVENT_TYPE_CALL:
+ updateCallIndicator(event.valueInt);
+ break;
+ case EVENT_TYPE_CALLSETUP:
+ updateCallSetupIndicator(event.valueInt);
+ break;
+ case EVENT_TYPE_CALLHELD:
+ updateCallHeldIndicator(event.valueInt);
+ break;
+ case EVENT_TYPE_RESP_AND_HOLD:
+ updateRespAndHold(event.valueInt);
+ break;
+ case EVENT_TYPE_CLIP:
+ updateClip(event.valueString);
+ break;
+ case EVENT_TYPE_CALL_WAITING:
+ addCallWaiting(event.valueString);
+ break;
+ case EVENT_TYPE_IN_BAND_RING:
+ if (mInBandRingtone != event.valueInt) {
+ mInBandRingtone = event.valueInt;
+ intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING,
+ mInBandRingtone);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+ break;
+ case EVENT_TYPE_CURRENT_CALLS:
+ queryCallsUpdate(
+ event.valueInt,
+ event.valueInt3,
+ event.valueString,
+ event.valueInt4 ==
+ HeadsetClientHalConstants.CALL_MPTY_TYPE_MULTI,
+ event.valueInt2 ==
+ HeadsetClientHalConstants.CALL_DIRECTION_OUTGOING);
+ break;
+ case EVENT_TYPE_VOLUME_CHANGED:
+ if (event.valueInt == HeadsetClientHalConstants.VOLUME_TYPE_SPK) {
+ mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO,
+ event.valueInt2, AudioManager.FLAG_SHOW_UI);
+ mVgsFromStack = true;
+ } else if (event.valueInt == HeadsetClientHalConstants.VOLUME_TYPE_MIC) {
+ mAudioManager.setMicrophoneMute(event.valueInt2 == 0);
+ mVgmFromStack = true;
+ }
+ break;
+ case EVENT_TYPE_CMD_RESULT:
+ Pair<Integer, Object> queuedAction = mQueuedActions.poll();
+
+ // should not happen but...
+ if (queuedAction == null || queuedAction.first == NO_ACTION) {
+ clearPendingAction();
+ break;
+ }
+
+ Log.d(TAG, "Connected: command result: " + event.valueInt
+ + " queuedAction: " + queuedAction.first);
+
+ switch (queuedAction.first) {
+ case VOICE_RECOGNITION_STOP:
+ case VOICE_RECOGNITION_START:
+ if (event.valueInt == HeadsetClientHalConstants.CMD_COMPLETE_OK) {
+ if (queuedAction.first == VOICE_RECOGNITION_STOP) {
+ mVoiceRecognitionActive =
+ HeadsetClientHalConstants.VR_STATE_STOPPED;
+ } else {
+ mVoiceRecognitionActive =
+ HeadsetClientHalConstants.VR_STATE_STARTED;
+ }
+ }
+ intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
+ intent.putExtra(
+ BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION,
+ mVoiceRecognitionActive);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ break;
+ case QUERY_CURRENT_CALLS:
+ queryCallsDone();
+ break;
+ case ACCEPT_CALL:
+ if (event.valueInt == BluetoothHeadsetClient.ACTION_RESULT_OK) {
+ mPendingAction = queuedAction;
+ } else {
+ if (callsInState(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) == 0) {
+ if(getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING) != null &&
+ (Integer) mPendingAction.second == HeadsetClientHalConstants.CALL_ACTION_ATA) {
+ acceptCall(BluetoothHeadsetClient.CALL_ACCEPT_NONE, true);
+ break;
+ } else if(getCall(BluetoothHeadsetClientCall.CALL_STATE_WAITING) != null &&
+ (Integer) mPendingAction.second == HeadsetClientHalConstants.CALL_ACTION_CHLD_2) {
+ acceptCall(BluetoothHeadsetClient.CALL_ACCEPT_NONE, true);
+ break;
+ }
+ }
+ sendActionResultIntent(event);
+ }
+ break;
+ case REJECT_CALL:
+ case HOLD_CALL:
+ case TERMINATE_CALL:
+ case ENTER_PRIVATE_MODE:
+ case DIAL_NUMBER:
+ case DIAL_MEMORY:
+ case REDIAL:
+ if (event.valueInt == BluetoothHeadsetClient.ACTION_RESULT_OK) {
+ mPendingAction = queuedAction;
+ } else {
+ sendActionResultIntent(event);
+ }
+ break;
+ case TERMINATE_SPECIFIC_CALL:
+ // if terminating specific succeed no other
+ // event is send
+ if (event.valueInt == BluetoothHeadsetClient.ACTION_RESULT_OK) {
+ BluetoothHeadsetClientCall c =
+ (BluetoothHeadsetClientCall) queuedAction.second;
+ setCallState(c,
+ BluetoothHeadsetClientCall.CALL_STATE_TERMINATED);
+ mCalls.remove(c.getId());
+ } else {
+ sendActionResultIntent(event);
+ }
+ break;
+ case LAST_VTAG_NUMBER:
+ if (event.valueInt != BluetoothHeadsetClient.ACTION_RESULT_OK) {
+ sendActionResultIntent(event);
+ }
+ break;
+ case SET_MIC_VOLUME:
+ case SET_SPEAKER_VOLUME:
+ case SUBSCRIBER_INFO:
+ case QUERY_OPERATOR_NAME:
+ break;
+ default:
+ sendActionResultIntent(event);
+ break;
+ }
+
+ break;
+ case EVENT_TYPE_SUBSCRIBER_INFO:
+ /* TODO should we handle type as well? */
+ mSubscriberInfo = event.valueString;
+ intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_SUBSCRIBER_INFO,
+ mSubscriberInfo);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ break;
+ case EVENT_TYPE_LAST_VOICE_TAG_NUMBER:
+ intent = new Intent(BluetoothHeadsetClient.ACTION_LAST_VTAG);
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_NUMBER,
+ event.valueString);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ break;
+ case EVENT_TYPE_RING_INDICATION:
+ Log.e(TAG, "start ringing");
+ if (mRingtone != null && mRingtone.isPlaying()) {
+ Log.d(TAG,"ring already playing");
+ break;
+ }
+ int newAudioMode = AudioManager.MODE_RINGTONE;
+ int currMode = mAudioManager.getMode();
+ if (currMode != newAudioMode) {
+ // request audio focus before setting the new mode
+ mAudioManager.requestAudioFocusForCall(AudioManager.MODE_RINGTONE,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ Log.d(TAG, "setAudioMode Setting audio mode from "
+ + currMode + " to " + newAudioMode);
+ mAudioManager.setMode(newAudioMode);
+ }
+ if (mRingtone != null) {
+ mRingtone.play();
+ }
+ break;
+ default:
+ Log.e(TAG, "Unknown stack event: " + event.type);
+ break;
+ }
+
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ private void sendActionResultIntent(StackEvent event) {
+ Intent intent = new Intent(BluetoothHeadsetClient.ACTION_RESULT);
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_RESULT_CODE, event.valueInt);
+ if (event.valueInt == BluetoothHeadsetClient.ACTION_RESULT_ERROR_CME) {
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_CME_CODE, event.valueInt2);
+ }
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+
+ // in Connected state
+ private void processConnectionEvent(int state, BluetoothDevice device) {
+ switch (state) {
+ case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
+ Log.d(TAG, "Connected disconnects.");
+ // AG disconnects
+ if (mCurrentDevice.equals(device)) {
+ broadcastConnectionState(mCurrentDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED);
+ mCurrentDevice = null;
+ transitionTo(mDisconnected);
+ } else {
+ Log.e(TAG, "Disconnected from unknown device: " + device);
+ }
+ break;
+ default:
+ Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
+ break;
+ }
+ }
+
+ // in Connected state
+ private void processAudioEvent(int state, BluetoothDevice device) {
+ // message from old device
+ if (!mCurrentDevice.equals(device)) {
+ Log.e(TAG, "Audio changed on disconnected device: " + device);
+ return;
+ }
+
+ switch (state) {
+ case HeadsetClientHalConstants.AUDIO_STATE_CONNECTED_MSBC:
+ mAudioWbs = true;
+ // fall through
+ case HeadsetClientHalConstants.AUDIO_STATE_CONNECTED:
+ mAudioState = BluetoothHeadsetClient.STATE_AUDIO_CONNECTED;
+ // request audio focus for call
+ if (mRingtone != null && mRingtone.isPlaying()) {
+ Log.d(TAG,"stopping ring and request focus for call");
+ mRingtone.stop();
+ }
+ int newAudioMode = AudioManager.MODE_IN_CALL;
+ int currMode = mAudioManager.getMode();
+ if (currMode != newAudioMode) {
+ // request audio focus before setting the new mode
+ mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ Log.d(TAG, "setAudioMode Setting audio mode from "
+ + currMode + " to " + newAudioMode);
+ mAudioManager.setMode(newAudioMode);
+ }
+ Log.d(TAG,"hfp_enable=true");
+ Log.d(TAG,"mAudioWbs is " + mAudioWbs);
+ if (mAudioWbs) {
+ Log.d(TAG,"Setting sampling rate as 16000");
+ mAudioManager.setParameters("hfp_set_sampling_rate=16000");
+ }
+ else {
+ Log.d(TAG,"Setting sampling rate as 8000");
+ mAudioManager.setParameters("hfp_set_sampling_rate=8000");
+ }
+ mAudioManager.setParameters("hfp_enable=true");
+ broadcastAudioState(device, BluetoothHeadsetClient.STATE_AUDIO_CONNECTED,
+ BluetoothHeadsetClient.STATE_AUDIO_CONNECTING);
+ transitionTo(mAudioOn);
+ break;
+ case HeadsetClientHalConstants.AUDIO_STATE_CONNECTING:
+ mAudioState = BluetoothHeadsetClient.STATE_AUDIO_CONNECTING;
+ broadcastAudioState(device, BluetoothHeadsetClient.STATE_AUDIO_CONNECTING,
+ BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED);
+ break;
+ case HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED:
+ if (mAudioState == BluetoothHeadsetClient.STATE_AUDIO_CONNECTING) {
+ mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
+ broadcastAudioState(device,
+ BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED,
+ BluetoothHeadsetClient.STATE_AUDIO_CONNECTING);
+ }
+ break;
+ default:
+ Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
+ break;
+ }
+ }
+
+ @Override
+ public void exit() {
+ Log.d(TAG, "Exit Connected: " + getCurrentMessage().what);
+ }
+ }
+
+ private class AudioOn extends State {
+ @Override
+ public void enter() {
+ Log.d(TAG, "Enter AudioOn: " + getCurrentMessage().what);
+
+ mAudioManager.setStreamSolo(AudioManager.STREAM_BLUETOOTH_SCO, true);
+ }
+
+ @Override
+ public synchronized boolean processMessage(Message message) {
+ Log.d(TAG, "AudioOn process message: " + message.what);
+ if (DBG) {
+ if (mCurrentDevice == null) {
+ Log.d(TAG, "ERROR: mCurrentDevice is null in Connected");
+ return NOT_HANDLED;
+ }
+ }
+
+ switch (message.what) {
+ case DISCONNECT:
+ BluetoothDevice device = (BluetoothDevice) message.obj;
+ if (!mCurrentDevice.equals(device)) {
+ break;
+ }
+ deferMessage(message);
+ /*
+ * fall through - disconnect audio first then expect
+ * deferred DISCONNECT message in Connected state
+ */
+ case DISCONNECT_AUDIO:
+ /*
+ * just disconnect audio and wait for
+ * EVENT_TYPE_AUDIO_STATE_CHANGED, that triggers State
+ * Machines state changing
+ */
+ if (disconnectAudioNative(getByteAddress(mCurrentDevice))) {
+ mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
+ //abandon audio focus
+ if (mAudioManager.getMode() != AudioManager.MODE_NORMAL) {
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ Log.d(TAG, "abandonAudioFocus");
+ // abandon audio focus after the mode has been set back to normal
+ mAudioManager.abandonAudioFocusForCall();
+ }
+ Log.d(TAG,"hfp_enable=false");
+ mAudioManager.setParameters("hfp_enable=false");
+ broadcastAudioState(mCurrentDevice,
+ BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED,
+ BluetoothHeadsetClient.STATE_AUDIO_CONNECTED);
+ }
+ break;
+ case STACK_EVENT:
+ StackEvent event = (StackEvent) message.obj;
+ if (DBG) {
+ Log.d(TAG, "AudioOn: event type: " + event.type);
+ }
+ switch (event.type) {
+ case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ Log.d(TAG, "AudioOn connection state changed" + event.device + ": "
+ + event.valueInt);
+ processConnectionEvent(event.valueInt, event.device);
+ break;
+ case EVENT_TYPE_AUDIO_STATE_CHANGED:
+ Log.d(TAG, "AudioOn audio state changed" + event.device + ": "
+ + event.valueInt);
+ processAudioEvent(event.valueInt, event.device);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ // in AudioOn state. Can AG disconnect RFCOMM prior to SCO? Handle this
+ private void processConnectionEvent(int state, BluetoothDevice device) {
+ switch (state) {
+ case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
+ if (mCurrentDevice.equals(device)) {
+ processAudioEvent(HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED,
+ device);
+ broadcastConnectionState(mCurrentDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED);
+ mCurrentDevice = null;
+ transitionTo(mDisconnected);
+ } else {
+ Log.e(TAG, "Disconnected from unknown device: " + device);
+ }
+ break;
+ default:
+ Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
+ break;
+ }
+ }
+
+ // in AudioOn state
+ private void processAudioEvent(int state, BluetoothDevice device) {
+ if (!mCurrentDevice.equals(device)) {
+ Log.e(TAG, "Audio changed on disconnected device: " + device);
+ return;
+ }
+
+ switch (state) {
+ case HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED:
+ if (mAudioState != BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED) {
+ mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
+ //abandon audio focus for call
+ if (mAudioManager.getMode() != AudioManager.MODE_NORMAL) {
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ Log.d(TAG, "abandonAudioFocus");
+ // abandon audio focus after the mode has been set back to normal
+ mAudioManager.abandonAudioFocusForCall();
+ }
+ Log.d(TAG,"hfp_enable=false");
+ mAudioManager.setParameters("hfp_enable=false");
+ broadcastAudioState(device,
+ BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED,
+ BluetoothHeadsetClient.STATE_AUDIO_CONNECTED);
+ }
+
+ transitionTo(mConnected);
+ break;
+ default:
+ Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
+ break;
+ }
+ }
+
+ @Override
+ public void exit() {
+ Log.d(TAG, "Exit AudioOn: " + getCurrentMessage().what);
+
+ mAudioManager.setStreamSolo(AudioManager.STREAM_BLUETOOTH_SCO, false);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public synchronized int getConnectionState(BluetoothDevice device) {
+ if (mCurrentDevice == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ if (!mCurrentDevice.equals(device)) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ IState currentState = getCurrentState();
+ if (currentState == mConnecting) {
+ return BluetoothProfile.STATE_CONNECTING;
+ }
+
+ if (currentState == mConnected || currentState == mAudioOn) {
+ return BluetoothProfile.STATE_CONNECTED;
+ }
+
+ Log.e(TAG, "Bad currentState: " + currentState);
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) {
+ Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
+
+ if (newState == BluetoothHeadsetClient.STATE_AUDIO_CONNECTED) {
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_AUDIO_WBS, mAudioWbs);
+ }
+
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ Log.d(TAG, "Audio state " + device + ": " + prevState + "->" + newState);
+ }
+
+ // This method does not check for error condition (newState == prevState)
+ private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
+ Log.d(TAG, "Connection state " + device + ": " + prevState + "->" + newState);
+ /*
+ * Notifying the connection state change of the profile before sending
+ * the intent for connection state change, as it was causing a race
+ * condition, with the UI not being updated with the correct connection
+ * state.
+ */
+ mService.notifyProfileConnectionStateChanged(device, BluetoothProfile.HEADSET_CLIENT,
+ newState, prevState);
+ Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+
+ // add feature extras when connected
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_3WAY) ==
+ HeadsetClientHalConstants.PEER_FEAT_3WAY) {
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
+ }
+ if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_VREC) ==
+ HeadsetClientHalConstants.PEER_FEAT_VREC) {
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION, true);
+ }
+ if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_VTAG) ==
+ HeadsetClientHalConstants.PEER_FEAT_VTAG) {
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT, true);
+ }
+ if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT) ==
+ HeadsetClientHalConstants.PEER_FEAT_REJECT) {
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
+ }
+ if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECC) ==
+ HeadsetClientHalConstants.PEER_FEAT_ECC) {
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC, true);
+ }
+
+ // add individual CHLD support extras
+ if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC) ==
+ HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC) {
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL, true);
+ }
+ if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL) ==
+ HeadsetClientHalConstants.CHLD_FEAT_REL) {
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL, true);
+ }
+ if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL_ACC) ==
+ HeadsetClientHalConstants.CHLD_FEAT_REL_ACC) {
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT, true);
+ }
+ if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE) ==
+ HeadsetClientHalConstants.CHLD_FEAT_MERGE) {
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE, true);
+ }
+ if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) ==
+ HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) {
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE_AND_DETACH, true);
+ }
+ }
+
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+
+ boolean isConnected() {
+ IState currentState = getCurrentState();
+ return (currentState == mConnected || currentState == mAudioOn);
+ }
+
+ List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
+ Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+ int connectionState;
+ synchronized (this) {
+ for (BluetoothDevice device : bondedDevices) {
+ ParcelUuid[] featureUuids = device.getUuids();
+ if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.Handsfree_AG)) {
+ continue;
+ }
+ connectionState = getConnectionState(device);
+ for (int state : states) {
+ if (connectionState == state) {
+ deviceList.add(device);
+ }
+ }
+ }
+ }
+ return deviceList;
+ }
+
+ boolean okToConnect(BluetoothDevice device) {
+ int priority = mService.getPriority(device);
+ boolean ret = false;
+ // check priority and accept or reject the connection. if priority is
+ // undefined
+ // it is likely that our SDP has not completed and peer is initiating
+ // the
+ // connection. Allow this connection, provided the device is bonded
+ if ((BluetoothProfile.PRIORITY_OFF < priority) ||
+ ((BluetoothProfile.PRIORITY_UNDEFINED == priority) &&
+ (device.getBondState() != BluetoothDevice.BOND_NONE))) {
+ ret = true;
+ }
+ return ret;
+ }
+
+ boolean isAudioOn() {
+ return (getCurrentState() == mAudioOn);
+ }
+
+ synchronized int getAudioState(BluetoothDevice device) {
+ if (mCurrentDevice == null || !mCurrentDevice.equals(device)) {
+ return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
+ }
+ return mAudioState;
+ }
+
+ /**
+ * @hide
+ */
+ List<BluetoothDevice> getConnectedDevices() {
+ List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+ synchronized (this) {
+ if (isConnected()) {
+ devices.add(mCurrentDevice);
+ }
+ }
+ return devices;
+ }
+
+ private BluetoothDevice getDevice(byte[] address) {
+ return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
+ }
+
+ private void onConnectionStateChanged(int state, int peer_feat, int chld_feat, byte[] address) {
+ StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt = state;
+ event.valueInt2 = peer_feat;
+ event.valueInt3 = chld_feat;
+ event.device = getDevice(address);
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onAudioStateChanged(int state, byte[] address) {
+ StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
+ event.valueInt = state;
+ event.device = getDevice(address);
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onVrStateChanged(int state) {
+ StackEvent event = new StackEvent(EVENT_TYPE_VR_STATE_CHANGED);
+ event.valueInt = state;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onNetworkState(int state) {
+ StackEvent event = new StackEvent(EVENT_TYPE_NETWORK_STATE);
+ event.valueInt = state;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onNetworkRoaming(int state) {
+ StackEvent event = new StackEvent(EVENT_TYPE_ROAMING_STATE);
+ event.valueInt = state;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onNetworkSignal(int signal) {
+ StackEvent event = new StackEvent(EVENT_TYPE_NETWORK_SIGNAL);
+ event.valueInt = signal;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onBatteryLevel(int level) {
+ StackEvent event = new StackEvent(EVENT_TYPE_BATTERY_LEVEL);
+ event.valueInt = level;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onCurrentOperator(String name) {
+ StackEvent event = new StackEvent(EVENT_TYPE_OPERATOR_NAME);
+ event.valueString = name;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onCall(int call) {
+ StackEvent event = new StackEvent(EVENT_TYPE_CALL);
+ event.valueInt = call;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onCallSetup(int callsetup) {
+ StackEvent event = new StackEvent(EVENT_TYPE_CALLSETUP);
+ event.valueInt = callsetup;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onCallHeld(int callheld) {
+ StackEvent event = new StackEvent(EVENT_TYPE_CALLHELD);
+ event.valueInt = callheld;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onRespAndHold(int resp_and_hold) {
+ StackEvent event = new StackEvent(EVENT_TYPE_RESP_AND_HOLD);
+ event.valueInt = resp_and_hold;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onClip(String number) {
+ StackEvent event = new StackEvent(EVENT_TYPE_CLIP);
+ event.valueString = number;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onCallWaiting(String number) {
+ StackEvent event = new StackEvent(EVENT_TYPE_CALL_WAITING);
+ event.valueString = number;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onCurrentCalls(int index, int dir, int state, int mparty, String number) {
+ StackEvent event = new StackEvent(EVENT_TYPE_CURRENT_CALLS);
+ event.valueInt = index;
+ event.valueInt2 = dir;
+ event.valueInt3 = state;
+ event.valueInt4 = mparty;
+ event.valueString = number;
+ Log.d(TAG, "incoming " + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onVolumeChange(int type, int volume) {
+ StackEvent event = new StackEvent(EVENT_TYPE_VOLUME_CHANGED);
+ event.valueInt = type;
+ event.valueInt2 = volume;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onCmdResult(int type, int cme) {
+ StackEvent event = new StackEvent(EVENT_TYPE_CMD_RESULT);
+ event.valueInt = type;
+ event.valueInt2 = cme;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onSubscriberInfo(String number, int type) {
+ StackEvent event = new StackEvent(EVENT_TYPE_SUBSCRIBER_INFO);
+ event.valueInt = type;
+ event.valueString = number;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onInBandRing(int in_band) {
+ StackEvent event = new StackEvent(EVENT_TYPE_IN_BAND_RING);
+ event.valueInt = in_band;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private void onLastVoiceTagNumber(String number) {
+ StackEvent event = new StackEvent(EVENT_TYPE_LAST_VOICE_TAG_NUMBER);
+ event.valueString = number;
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+ private void onRingIndication() {
+ StackEvent event = new StackEvent(EVENT_TYPE_RING_INDICATION);
+ Log.d(TAG, "incoming" + event);
+ sendMessage(STACK_EVENT, event);
+ }
+
+ private String getCurrentDeviceName() {
+ String defaultName = "<unknown>";
+ if (mCurrentDevice == null) {
+ return defaultName;
+ }
+ String deviceName = mCurrentDevice.getName();
+ if (deviceName == null) {
+ return defaultName;
+ }
+ return deviceName;
+ }
+
+ private byte[] getByteAddress(BluetoothDevice device) {
+ return Utils.getBytesFromAddress(device.getAddress());
+ }
+
+ // Event types for STACK_EVENT message
+ final private static int EVENT_TYPE_NONE = 0;
+ final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+ final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
+ final private static int EVENT_TYPE_VR_STATE_CHANGED = 3;
+ final private static int EVENT_TYPE_NETWORK_STATE = 4;
+ final private static int EVENT_TYPE_ROAMING_STATE = 5;
+ final private static int EVENT_TYPE_NETWORK_SIGNAL = 6;
+ final private static int EVENT_TYPE_BATTERY_LEVEL = 7;
+ final private static int EVENT_TYPE_OPERATOR_NAME = 8;
+ final private static int EVENT_TYPE_CALL = 9;
+ final private static int EVENT_TYPE_CALLSETUP = 10;
+ final private static int EVENT_TYPE_CALLHELD = 11;
+ final private static int EVENT_TYPE_CLIP = 12;
+ final private static int EVENT_TYPE_CALL_WAITING = 13;
+ final private static int EVENT_TYPE_CURRENT_CALLS = 14;
+ final private static int EVENT_TYPE_VOLUME_CHANGED = 15;
+ final private static int EVENT_TYPE_CMD_RESULT = 16;
+ final private static int EVENT_TYPE_SUBSCRIBER_INFO = 17;
+ final private static int EVENT_TYPE_RESP_AND_HOLD = 18;
+ final private static int EVENT_TYPE_IN_BAND_RING = 19;
+ final private static int EVENT_TYPE_LAST_VOICE_TAG_NUMBER = 20;
+ final private static int EVENT_TYPE_RING_INDICATION= 21;
+
+ // for debugging only
+ private final String EVENT_TYPE_NAMES[] =
+ {
+ "EVENT_TYPE_NONE",
+ "EVENT_TYPE_CONNECTION_STATE_CHANGED",
+ "EVENT_TYPE_AUDIO_STATE_CHANGED",
+ "EVENT_TYPE_VR_STATE_CHANGED",
+ "EVENT_TYPE_NETWORK_STATE",
+ "EVENT_TYPE_ROAMING_STATE",
+ "EVENT_TYPE_NETWORK_SIGNAL",
+ "EVENT_TYPE_BATTERY_LEVEL",
+ "EVENT_TYPE_OPERATOR_NAME",
+ "EVENT_TYPE_CALL",
+ "EVENT_TYPE_CALLSETUP",
+ "EVENT_TYPE_CALLHELD",
+ "EVENT_TYPE_CLIP",
+ "EVENT_TYPE_CALL_WAITING",
+ "EVENT_TYPE_CURRENT_CALLS",
+ "EVENT_TYPE_VOLUME_CHANGED",
+ "EVENT_TYPE_CMD_RESULT",
+ "EVENT_TYPE_SUBSCRIBER_INFO",
+ "EVENT_TYPE_RESP_AND_HOLD",
+ "EVENT_TYPE_IN_BAND_RING",
+ "EVENT_TYPE_LAST_VOICE_TAG_NUMBER",
+ "EVENT_TYPE_RING_INDICATION",
+ };
+
+ private class StackEvent {
+ int type = EVENT_TYPE_NONE;
+ int valueInt = 0;
+ int valueInt2 = 0;
+ int valueInt3 = 0;
+ int valueInt4 = 0;
+ String valueString = null;
+ BluetoothDevice device = null;
+
+ private StackEvent(int type) {
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ // event dump
+ StringBuilder result = new StringBuilder();
+ result.append("StackEvent {type:" + EVENT_TYPE_NAMES[type]);
+ result.append(", value1:" + valueInt);
+ result.append(", value2:" + valueInt2);
+ result.append(", value3:" + valueInt3);
+ result.append(", value4:" + valueInt4);
+ result.append(", string: \"" + valueString + "\"");
+ result.append(", device:" + device + "}");
+ return result.toString();
+ }
+ }
+
+ private native static void classInitNative();
+
+ private native void initializeNative();
+
+ private native void cleanupNative();
+
+ private native boolean connectNative(byte[] address);
+
+ private native boolean disconnectNative(byte[] address);
+
+ private native boolean connectAudioNative(byte[] address);
+
+ private native boolean disconnectAudioNative(byte[] address);
+
+ private native boolean startVoiceRecognitionNative();
+
+ private native boolean stopVoiceRecognitionNative();
+
+ private native boolean setVolumeNative(int volumeType, int volume);
+
+ private native boolean dialNative(String number);
+
+ private native boolean dialMemoryNative(int location);
+
+ private native boolean handleCallActionNative(int action, int index);
+
+ private native boolean queryCurrentCallsNative();
+
+ private native boolean queryCurrentOperatorNameNative();
+
+ private native boolean retrieveSubscriberInfoNative();
+
+ private native boolean sendDtmfNative(byte code);
+
+ private native boolean requestLastVoiceTagNumberNative();
+
+ private native boolean sendATCmdNative(int ATCmd, int val1,
+ int val2, String arg);
+
+ public List<BluetoothHeadsetClientCall> getCurrentCalls() {
+ return new ArrayList<BluetoothHeadsetClientCall>(mCalls.values());
+ }
+
+ public Bundle getCurrentAgEvents() {
+ Bundle b = new Bundle();
+ b.putInt(BluetoothHeadsetClient.EXTRA_NETWORK_STATUS, mIndicatorNetworkState);
+ b.putInt(BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH, mIndicatorNetworkSignal);
+ b.putInt(BluetoothHeadsetClient.EXTRA_NETWORK_ROAMING, mIndicatorNetworkType);
+ b.putInt(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL, mIndicatorBatteryLevel);
+ b.putString(BluetoothHeadsetClient.EXTRA_OPERATOR_NAME, mOperatorName);
+ b.putInt(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, mVoiceRecognitionActive);
+ b.putInt(BluetoothHeadsetClient.EXTRA_IN_BAND_RING, mInBandRingtone);
+ b.putString(BluetoothHeadsetClient.EXTRA_SUBSCRIBER_INFO, mSubscriberInfo);
+ return b;
+ }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppParams.java b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
index ffdd2f1..cb607c7 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAppParams.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
@@ -87,6 +87,9 @@
public static final int CHARSET_NATIVE = 0;
public static final int CHARSET_UTF8 = 1;
+ /* Default values for omitted application parameters */
+ public static final long PARAMETER_MASK_ALL_ENABLED = 0xFFFF; // TODO: Update when bit 16-31 will be used.
+
private int maxListCount = INVALID_VALUE_PARAMETER;
private int startOffset = INVALID_VALUE_PARAMETER;
private int filterMessageType = INVALID_VALUE_PARAMETER;
diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java
index 55ccaa8..054b6f3 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -1175,6 +1175,15 @@
BluetoothMapMessageListing bmList = new BluetoothMapMessageListing();
BluetoothMapMessageListingElement e = null;
+ /* We overwrite the parameter mask here if it is 0 or not present, as this
+ * should cause all parameters to be included in the message list. */
+ if(ap.getParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
+ ap.getParameterMask() == 0) {
+ ap.setParameterMask(BluetoothMapAppParams.PARAMETER_MASK_ALL_ENABLED);
+ if (V) Log.w(TAG, "msgListing(): appParameterMask is zero or not present, " +
+ "changing to: " + ap.getParameterMask());
+ }
+
/* Cache some info used throughout filtering */
FilterInfo fi = new FilterInfo();
setFilterInfo(fi);
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
index 9f70759..246f804 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
@@ -253,7 +253,7 @@
if(sent != null)
xmlMsgElement.attribute("", "sent", sent);
if(protect != null)
- xmlMsgElement.attribute("", "protect", protect);
+ xmlMsgElement.attribute("", "protected", protect);
xmlMsgElement.endTag("", "msg");
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index 1dfc7cf..c60ce07 100644
--- a/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -239,7 +239,7 @@
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
}
try {
- if(folderName == null || folderName.equals("")) {
+ if(folderName == null || folderName.trim().isEmpty()) {
folderName = mCurrentFolder.getName();
}
folderName = folderName.toLowerCase();
@@ -344,7 +344,7 @@
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
- if (folderName == null || folderName == "") {
+ if (folderName == null || folderName.trim().isEmpty()) {
if(backup == false)
mCurrentFolder = mCurrentFolder.getRoot();
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppManager.java b/src/com/android/bluetooth/opp/BluetoothOppManager.java
index dd8efe0..69f8e8e 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppManager.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppManager.java
@@ -250,11 +250,13 @@
synchronized (BluetoothOppManager.this) {
mMultipleFlag = false;
mMimeTypeOfSendingFile = mimeType;
- mUriOfSendingFile = uriString;
mIsHandoverInitiated = isHandover;
Uri uri = Uri.parse(uriString);
- BluetoothOppUtility.putSendFileInfo(uri,
- BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType));
+ BluetoothOppSendFileInfo sendFileInfo =
+ BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType);
+ uri = BluetoothOppUtility.generateUri(uri, sendFileInfo);
+ BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo);
+ mUriOfSendingFile = uri.toString();
storeApplicationData();
}
}
@@ -263,11 +265,14 @@
synchronized (BluetoothOppManager.this) {
mMultipleFlag = true;
mMimeTypeOfSendingFiles = mimeType;
- mUrisOfSendingFiles = uris;
+ mUrisOfSendingFiles = new ArrayList<Uri>();
mIsHandoverInitiated = isHandover;
for (Uri uri : uris) {
- BluetoothOppUtility.putSendFileInfo(uri,
- BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType));
+ BluetoothOppSendFileInfo sendFileInfo =
+ BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType);
+ uri = BluetoothOppUtility.generateUri(uri, sendFileInfo);
+ mUrisOfSendingFiles.add(uri);
+ BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo);
}
storeApplicationData();
}
@@ -426,15 +431,18 @@
Long ts = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
Uri fileUri = mUris.get(i);
+
+ ContentValues values = new ContentValues();
+ values.put(BluetoothShare.URI, fileUri.toString());
+
ContentResolver contentResolver = mContext.getContentResolver();
+ fileUri = BluetoothOppUtility.originalUri(fileUri);
String contentType = contentResolver.getType(fileUri);
if (V) Log.v(TAG, "Got mimetype: " + contentType + " Got uri: " + fileUri);
if (TextUtils.isEmpty(contentType)) {
contentType = mTypeOfMultipleFiles;
}
- ContentValues values = new ContentValues();
- values.put(BluetoothShare.URI, fileUri.toString());
values.put(BluetoothShare.MIMETYPE, contentType);
values.put(BluetoothShare.DESTINATION, mRemoteDevice.getAddress());
values.put(BluetoothShare.TIMESTAMP, ts);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java b/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
index 993dc0b..c394634 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
@@ -371,6 +371,12 @@
.cancel(mTransInfo.mID);
// retry the failed transfer
+ Uri uri = BluetoothOppUtility.originalUri(Uri.parse(mTransInfo.mFileUri));
+ BluetoothOppSendFileInfo sendFileInfo =
+ BluetoothOppSendFileInfo.generateFileInfo(this, uri, mTransInfo.mFileType);
+ uri = BluetoothOppUtility.generateUri(uri, sendFileInfo);
+ BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo);
+ mTransInfo.mFileUri = uri.toString();
BluetoothOppUtility.retryTransfer(this, mTransInfo);
BluetoothDevice remoteDevice = mAdapter.getRemoteDevice(mTransInfo.mDestAddr);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java b/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
index abe343f..7319b5c 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
@@ -76,6 +76,8 @@
private boolean mShowAllIncoming;
+ private boolean mContextMenu = false;
+
/** Class to handle Notification Manager updates */
private BluetoothOppNotification mNotifier;
@@ -136,6 +138,7 @@
}
mNotifier = new BluetoothOppNotification(this);
+ mContextMenu = false;
}
@Override
@@ -188,6 +191,7 @@
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (mTransferCursor != null) {
+ mContextMenu = true;
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
mTransferCursor.moveToPosition(info.position);
mContextMenuPosition = info.position;
@@ -263,9 +267,13 @@
*/
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Open the selected item
- mTransferCursor.moveToPosition(position);
- openCompleteTransfer();
- updateNotificationWhenBtDisabled();
+ if (V) Log.v(TAG, "onItemClick: ContextMenu = " + mContextMenu);
+ if (!mContextMenu) {
+ mTransferCursor.moveToPosition(position);
+ openCompleteTransfer();
+ updateNotificationWhenBtDisabled();
+ }
+ mContextMenu = false;
}
/**
diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
index 24b7606..1a42133 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -97,7 +97,7 @@
info.mFileUri = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.URI));
if (info.mFileUri != null) {
- Uri u = Uri.parse(info.mFileUri);
+ Uri u = originalUri(Uri.parse(info.mFileUri));
info.mFileType = context.getContentResolver().getType(u);
} else {
Uri u = Uri.parse(info.mFileName);
@@ -308,6 +308,26 @@
transInfo.mDeviceName);
}
+ static Uri originalUri(Uri uri) {
+ String mUri = uri.toString();
+ int atIndex = mUri.lastIndexOf("@");
+ if (atIndex != -1) {
+ mUri = mUri.substring(0, atIndex);
+ uri = Uri.parse(mUri);
+ }
+ if (V) Log.v(TAG, "originalUri: " + uri);
+ return uri;
+ }
+
+ static Uri generateUri(Uri uri, BluetoothOppSendFileInfo sendFileInfo) {
+ String fileInfo = sendFileInfo.toString();
+ int atIndex = fileInfo.lastIndexOf("@");
+ fileInfo = fileInfo.substring(atIndex);
+ uri = Uri.parse(uri + fileInfo);
+ if (V) Log.v(TAG, "generateUri: " + uri);
+ return uri;
+ }
+
static void putSendFileInfo(Uri uri, BluetoothOppSendFileInfo sendFileInfo) {
if (D) Log.d(TAG, "putSendFileInfo: uri=" + uri + " sendFileInfo=" + sendFileInfo);
sSendFileMap.put(uri, sendFileInfo);
diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java
index 682fe2d..0646f16 100755
--- a/src/com/android/bluetooth/pan/PanService.java
+++ b/src/com/android/bluetooth/pan/PanService.java
@@ -51,7 +51,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
/**
* Provides Bluetooth Pan Device profile, as a service in
@@ -76,6 +77,8 @@
private static final int MESSAGE_DISCONNECT = 2;
private static final int MESSAGE_CONNECT_STATE_CHANGED = 11;
private boolean mTetherOn = false;
+ private static final String PAN_PREFERENCE_FILE = "PANMGR";
+ private static final String PAN_TETHER_SETTING = "TETHERSTATE";
AsyncChannel mTetherAc;
@@ -108,6 +111,10 @@
Context.CONNECTIVITY_SERVICE);
cm.supplyMessenger(ConnectivityManager.TYPE_BLUETOOTH, new Messenger(mHandler));
+ // Set mTetherOn based on the last saved tethering preference while starting the Pan service
+ SharedPreferences tetherSetting = getSharedPreferences(PAN_PREFERENCE_FILE, 0);
+ mTetherOn = tetherSetting.getBoolean(PAN_TETHER_SETTING, false);
+
return true;
}
@@ -259,7 +266,6 @@
return service.isPanUOn();
}
public boolean isTetheringOn() {
- // TODO(BT) have a variable marking the on/off state
PanService service = getService();
if (service == null) return false;
return service.isTetheringOn();
@@ -319,7 +325,6 @@
return (getPanLocalRoleNative() & BluetoothPan.LOCAL_PANU_ROLE) != 0;
}
boolean isTetheringOn() {
- // TODO(BT) have a variable marking the on/off state
return mTetherOn;
}
@@ -327,6 +332,14 @@
if(DBG) Log.d(TAG, "setBluetoothTethering: " + value +", mTetherOn: " + mTetherOn);
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
if(mTetherOn != value) {
+
+ SharedPreferences tetherSetting = getSharedPreferences(PAN_PREFERENCE_FILE, 0);
+ SharedPreferences.Editor editor = tetherSetting.edit();
+
+ editor.putBoolean(PAN_TETHER_SETTING, value);
+
+ // Commit the edit!
+ editor.commit();
//drop any existing panu or pan-nap connection when changing the tethering state
mTetherOn = value;
List<BluetoothDevice> DevList = getConnectedDevices();
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
index ce020d5..c2911c4 100755
--- a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
@@ -610,6 +610,8 @@
if (D) Log.d(TAG, "currentValue=" + currentValue);
if (currentValue.startsWith(compareValue)) {
itemsFound++;
+ if (currentValue.contains(","))
+ currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
writeVCardEntry(pos, currentValue,result);
}
}
@@ -624,8 +626,10 @@
for (int pos = listStartOffset; pos < listSize &&
itemsFound < requestSize; pos++) {
currentValue = nameList.get(pos);
- if (D) Log.d(TAG, "currentValue=" + currentValue);
- if (searchValue == null || currentValue.startsWith(compareValue)) {
+ if (currentValue.contains(","))
+ currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
+
+ if (searchValue == null || (currentValue.toLowerCase()).equals(compareValue.toLowerCase())) {
itemsFound++;
writeVCardEntry(pos, currentValue,result);
}
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
index 1aae276..89719c4 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
@@ -133,6 +133,7 @@
boolean isSet = (c != null && c.getCount() > 0);
if (c != null) {
c.close();
+ c = null;
}
return isSet;
}
@@ -147,6 +148,7 @@
}
if (c != null) {
c.close();
+ c = null;
}
return ownerName;
}
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
index 3568299..cb36326 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
@@ -35,6 +35,7 @@
import android.content.ContentResolver;
import android.content.Context;
+import android.database.CursorWindowAllocationException;
import android.database.Cursor;
import android.net.Uri;
import android.provider.CallLog;
@@ -168,9 +169,12 @@
if (contactCursor != null) {
size = contactCursor.getCount() + 1; // always has the 0.vcf
}
+ } catch (CursorWindowAllocationException e) {
+ Log.e(TAG, "CursorWindowAllocationException while getting Contacts size");
} finally {
if (contactCursor != null) {
contactCursor.close();
+ contactCursor = null;
}
}
return size;
@@ -187,9 +191,12 @@
if (callCursor != null) {
size = callCursor.getCount();
}
+ } catch (CursorWindowAllocationException e) {
+ Log.e(TAG, "CursorWindowAllocationException while getting CallHistory size");
} finally {
if (callCursor != null) {
callCursor.close();
+ callCursor = null;
}
}
return size;
@@ -227,9 +234,12 @@
list.add(name);
}
}
+ } catch (CursorWindowAllocationException e) {
+ Log.e(TAG, "CursorWindowAllocationException while loading CallHistory");
} finally {
if (callCursor != null) {
callCursor.close();
+ callCursor = null;
}
}
return list;
@@ -264,15 +274,19 @@
for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
.moveToNext()) {
String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX);
+ long id = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
if (TextUtils.isEmpty(name)) {
name = mContext.getString(android.R.string.unknownName);
}
- nameList.add(name);
+ nameList.add(name + "," + id);
}
}
+ } catch (CursorWindowAllocationException e) {
+ Log.e(TAG, "CursorWindowAllocationException while getting Phonebook name list");
} finally {
if (contactCursor != null) {
contactCursor.close();
+ contactCursor = null;
}
}
return nameList;
@@ -304,12 +318,15 @@
name = mContext.getString(android.R.string.unknownName);
}
if (V) Log.v(TAG, "got name " + name + " by number " + phoneNumber + " @" + id);
- nameList.add(name);
+ nameList.add(name + "," + id);
}
}
+ } catch (CursorWindowAllocationException e) {
+ Log.e(TAG, "CursorWindowAllocationException while getting contact names");
} finally {
if (contactCursor != null) {
contactCursor.close();
+ contactCursor = null;
}
}
return nameList;
@@ -348,9 +365,12 @@
}
if (V) Log.v(TAG, "Call log query endPointId = " + endPointId);
}
+ } catch (CursorWindowAllocationException e) {
+ Log.e(TAG, "CursorWindowAllocationException while composing calllog vcards");
} finally {
if (callsCursor != null) {
callsCursor.close();
+ callsCursor = null;
}
}
@@ -402,9 +422,12 @@
}
if (V) Log.v(TAG, "Query endPointId = " + endPointId);
}
+ } catch (CursorWindowAllocationException e) {
+ Log.e(TAG, "CursorWindowAllocationException while composing phonebook vcards");
} finally {
if (contactCursor != null) {
contactCursor.close();
+ contactCursor = null;
}
}
@@ -440,9 +463,12 @@
contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
if (V) Log.v(TAG, "Query startPointId = " + contactId);
}
+ } catch (CursorWindowAllocationException e) {
+ Log.e(TAG, "CursorWindowAllocationException while composing phonebook one vcard order by index");
} finally {
if (contactCursor != null) {
contactCursor.close();
+ contactCursor = null;
}
}
} else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
@@ -454,9 +480,12 @@
contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
if (V) Log.v(TAG, "Query startPointId = " + contactId);
}
+ } catch (CursorWindowAllocationException e) {
+ Log.e(TAG, "CursorWindowAllocationException while composing phonebook one vcard order by alphabetical");
} finally {
if (contactCursor != null) {
contactCursor.close();
+ contactCursor = null;
}
}
} else {
diff --git a/tests/Android.mk b/tests/Android.mk
index ce9be11..22f117a 100755
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -16,4 +16,3 @@
LOCAL_INSTRUMENTATION_FOR := Bluetooth
include $(BUILD_PACKAGE)
-
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index c022b22..3ab9f79 100755
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -51,7 +51,7 @@
<uses-permission android:name="android.permission.ACCESS_BLUETOOTH_SHARE" />
<uses-permission android:name="com.android.permission.WHITELIST_BLUETOOTH_DEVICE" />
<path-permission
- android:path="/btopp"
+ android:pathPrefix="/btopp"
android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" />
</application>
<!--