Merge "Making Bluetooth OPP file transfer intent generic"
diff --git a/Android.mk b/Android.mk
index 4c24df9..0ef513e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -13,7 +13,8 @@
 LOCAL_JAVA_LIBRARIES := javax.obex telephony-common mms-common
 LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard
 
-LOCAL_REQUIRED_MODULES := libbluetooth_jni bluetooth.default
+LOCAL_REQUIRED_MODULES := bluetooth.default
+LOCAL_MULTILIB := 32
 
 LOCAL_PROGUARD_ENABLED := disabled
 
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 95f5d1b..aa18d33 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -31,6 +31,7 @@
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
     <uses-permission android:name="android.permission.NFC_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" />
@@ -258,6 +259,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>
@@ -280,5 +297,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 b90926e..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 \
@@ -23,6 +26,8 @@
     liblog \
     libhardware
 
+LOCAL_MULTILIB := 32
+
 #LOCAL_CFLAGS += -O0 -g
 
 LOCAL_MODULE := libbluetooth_jni
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 28de5ea..72c3c1c 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -41,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;
 
@@ -439,7 +443,7 @@
 
     ALOGV("%s: status:%d packet_count:%d ", __FUNCTION__, status, packet_count);
 }
-bt_callbacks_t sBluetoothCallbacks = {
+static bt_callbacks_t sBluetoothCallbacks = {
     sizeof(sBluetoothCallbacks),
     adapter_state_change_callback,
     adapter_properties_callback,
@@ -456,6 +460,116 @@
     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) {
     int err;
     hw_module_t* module;
@@ -487,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, "");
 
@@ -511,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");
@@ -539,6 +667,7 @@
     ALOGI("%s: return from cleanup",__FUNCTION__);
 
     env->DeleteGlobalRef(sJniCallbacksObj);
+    env->DeleteGlobalRef(sJniAdapterServiceObj);
     return JNI_TRUE;
 }
 
@@ -651,6 +780,22 @@
     return result;
 }
 
+static jboolean isConnectedNative(JNIEnv* env, jobject obj, jbyteArray address) {
+    ALOGV("%s:",__FUNCTION__);
+    if (!sBluetoothInterface) return JNI_FALSE;
+
+    jbyte *addr = env->GetByteArrayElements(address, NULL);
+    if (addr == NULL) {
+        jniThrowIOException(env, EINVAL);
+        return JNI_FALSE;
+    }
+
+    int ret = sBluetoothInterface->get_connection_state((bt_bdaddr_t *)addr);
+    env->ReleaseByteArrayElements(address, addr, 0);
+
+    return (ret != 0 ? JNI_TRUE : JNI_FALSE);
+}
+
 static jboolean pinReplyNative(JNIEnv *env, jobject obj, jbyteArray address, jboolean accept,
                                jint len, jbyteArray pinArray) {
     ALOGV("%s:",__FUNCTION__);
@@ -876,6 +1021,8 @@
 
     if (!sBluetoothSocketInterface) return -1;
 
+    ALOGV("%s: SOCK FLAG = %x", __FUNCTION__, flag);
+
     service_name = env->GetStringUTFChars(name_str, NULL);
 
     uuid = env->GetByteArrayElements(uuidObj, NULL);
@@ -883,7 +1030,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);
@@ -936,13 +1082,15 @@
     {"createBondNative", "([B)Z", (void*) createBondNative},
     {"removeBondNative", "([B)Z", (void*) removeBondNative},
     {"cancelBondNative", "([B)Z", (void*) cancelBondNative},
+    {"isConnectedNative", "([B)Z", (void*) isConnectedNative},
     {"pinReplyNative", "([BZI[B)Z", (void*) pinReplyNative},
     {"sspReplyNative", "([BIZI)Z", (void*) sspReplyNative},
     {"getRemoteServicesNative", "([B)Z", (void*) getRemoteServicesNative},
     {"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)
@@ -980,13 +1128,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 1ffac4e..1898357 100644
--- a/jni/com_android_bluetooth_gatt.cpp
+++ b/jni/com_android_bluetooth_gatt.cpp
@@ -159,6 +159,12 @@
 static jmethodID method_onRegisterForNotifications;
 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
@@ -430,6 +436,50 @@
     checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
 }
 
+void btgattc_configure_mtu_cb(int conn_id, int status, int mtu)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConfigureMTU,
+                                 conn_id, status, mtu);
+    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,
@@ -448,7 +498,13 @@
     btgattc_write_descriptor_cb,
     btgattc_execute_write_cb,
     btgattc_remote_rssi_cb,
-    btgattc_advertise_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
 };
 
 
@@ -663,6 +719,13 @@
     method_onGetIncludedService = env->GetMethodID(clazz, "onGetIncludedService", "(IIIIJJIIJJ)V");
     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
 
@@ -679,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!");
 }
@@ -767,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,
@@ -1089,6 +1151,139 @@
     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)
+{
+    if (!sGattIf) return;
+    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
@@ -1109,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;
 
@@ -1117,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,
@@ -1279,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},
@@ -1287,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},
@@ -1303,10 +1510,11 @@
     {"gattClientRegisterForNotificationsNative", "(ILjava/lang/String;IIJJIJJZ)V", (void *) gattClientRegisterForNotificationsNative},
     {"gattClientReadRemoteRssiNative", "(ILjava/lang/String;)V", (void *) gattClientReadRemoteRssiNative},
     {"gattAdvertiseNative", "(IZ)V", (void *) gattAdvertiseNative},
+    {"gattClientConfigureMTUNative", "(II)V", (void *) gattClientConfigureMTUNative},
 
     {"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},
@@ -1320,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 31727d2..ed3314a 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -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-el/strings.xml b/res/values-el/strings.xml
index fea6d62..014756c 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -38,16 +38,16 @@
     <string name="incoming_file_confirm_ok" msgid="281462442932231475">"Αποδοχή"</string>
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</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">"Κοινοποίηση μέσω Bluetooth: Εισερχόμενο αρχείο"</string>
+    <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"Κοινή χρήση μέσω Bluetooth: Εισερχόμενο αρχείο"</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">"Κοινοποίηση μέσω Bluetooth: Λήψη του <xliff:g id="FILE">%1$s</xliff:g>"</string>
-    <string name="notification_received" msgid="3324588019186687985">"Κοινοποίηση μέσω Bluetooth: Ελήφθη το <xliff:g id="FILE">%1$s</xliff:g>"</string>
-    <string name="notification_received_fail" msgid="3619350997285714746">"Κοινοποίηση μέσω Bluetooth: Το αρχείο <xliff:g id="FILE">%1$s</xliff:g> δεν ελήφθη"</string>
-    <string name="notification_sending" msgid="3035748958534983833">"Κοινοποίηση μέσω Bluetooth: Αποστολή του <xliff:g id="FILE">%1$s</xliff:g>"</string>
-    <string name="notification_sent" msgid="9218710861333027778">"Κοινοποίηση μέσω Bluetooth: Εστάλη το <xliff:g id="FILE">%1$s</xliff:g>"</string>
+    <string name="notification_receiving" msgid="4674648179652543984">"Κοινή χρήση μέσω Bluetooth: Λήψη του <xliff:g id="FILE">%1$s</xliff:g>"</string>
+    <string name="notification_received" msgid="3324588019186687985">"Κοινή χρήση μέσω Bluetooth: Ελήφθη το <xliff:g id="FILE">%1$s</xliff:g>"</string>
+    <string name="notification_received_fail" msgid="3619350997285714746">"Κοινή χρήση μέσω Bluetooth: Το αρχείο <xliff:g id="FILE">%1$s</xliff:g> δεν ελήφθη"</string>
+    <string name="notification_sending" msgid="3035748958534983833">"Κοινή χρήση μέσω Bluetooth: Αποστολή του <xliff:g id="FILE">%1$s</xliff:g>"</string>
+    <string name="notification_sent" msgid="9218710861333027778">"Κοινή χρήση μέσω Bluetooth: Εστάλη το <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">"Κοινοποίηση μέσω Bluetooth: Το αρχείο <xliff:g id="FILE">%1$s</xliff:g> δεν εστάλη"</string>
+    <string name="notification_sent_fail" msgid="6696082233774569445">"Κοινή χρήση μέσω Bluetooth: Το αρχείο <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">"Κοινοποίηση Bluetooth: Απεσταλμένα αρχεία"</string>
-    <string name="inbound_noti_title" msgid="4143352641953027595">"Κοινοποίηση Bluetooth: Ληφθέντα αρχεία"</string>
+    <string name="outbound_noti_title" msgid="8051906709452260849">"Κοινή χρήση Bluetooth: Απεσταλμένα αρχεία"</string>
+    <string name="inbound_noti_title" msgid="4143352641953027595">"Κοινή χρήση Bluetooth: Ληφθέντα αρχεία"</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-el/test_strings.xml b/res/values-el/test_strings.xml
index 2e3b68c..789278f 100644
--- a/res/values-el/test_strings.xml
+++ b/res/values-el/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">"Hello World, TestActivity"</string>
-    <string name="app_name" msgid="1203877025577761792">"Κοινοποίηση μέσω Bluetooth"</string>
+    <string name="app_name" msgid="1203877025577761792">"Κοινή χρήση μέσω Bluetooth"</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-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-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
index 83a8fb2..4fea7eb 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -30,13 +30,13 @@
     <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>
     <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"បោះបង់"</string>
     <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_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_caption" msgid="6671081128475981157">"តើ​អ្នក​ចង់​ទទួល​ឯកសារ​នេះ​ឬ​?"</string>
@@ -59,19 +59,19 @@
     <string name="download_fail_line1" msgid="3846450148862894552">"មិន​​បាន​ទទួល​​​ឯកសារ"</string>
     <string name="download_fail_line2" msgid="8950394574689971071">"ឯកសារ៖ <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="download_fail_line3" msgid="3451040656154861722">"មូលហេតុ៖ <xliff:g id="REASON">%1$s</xliff:g>"</string>
-    <string name="download_fail_ok" msgid="1521733664438320300">"យល់​ព្រម"</string>
+    <string name="download_fail_ok" msgid="1521733664438320300">"យល់​ព្រម​"</string>
     <string name="download_succ_line5" msgid="4509944688281573595">"បាន​ទទួល​​ឯកសារ"</string>
     <string name="download_succ_ok" msgid="7053688246357050216">"បើក"</string>
     <string name="upload_line1" msgid="2055952074059709052">"ទៅ៖ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="upload_line3" msgid="4920689672457037437">"ប្រភេទ​ឯកសារ៖ <xliff:g id="TYPE">%1$s</xliff:g> (<xliff:g id="SIZE">%2$s</xliff:g>)"</string>
     <string name="upload_line5" msgid="7759322537674229752">"កំពុង​ផ្ញើ​ឯកសារ…"</string>
     <string name="upload_succ_line5" msgid="5687317197463383601">"បាន​ផ្ញើ​ឯកសារ"</string>
-    <string name="upload_succ_ok" msgid="7705428476405478828">"យល់​ព្រម"</string>
+    <string name="upload_succ_ok" msgid="7705428476405478828">"យល់​ព្រម​"</string>
     <string name="upload_fail_line1" msgid="7899394672421491701">"មិន​បាន​ផ្ញើ​ឯកសារ​ទៅ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ។"</string>
     <string name="upload_fail_line1_2" msgid="2108129204050841798">"ឯកសារ៖ <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="upload_fail_ok" msgid="5807702461606714296">"ព្យាយាម​ម្ដង​ទៀត"</string>
-    <string name="upload_fail_cancel" msgid="9118496285835687125">"បិទ"</string>
-    <string name="bt_error_btn_ok" msgid="5965151173011534240">"យល់​ព្រម"</string>
+    <string name="upload_fail_cancel" msgid="9118496285835687125">"បិទ​"</string>
+    <string name="bt_error_btn_ok" msgid="5965151173011534240">"យល់​ព្រម​"</string>
     <string name="unknown_file" msgid="6092727753965095366">"មិន​ស្គាល់​ឯកសារ"</string>
     <string name="unknown_file_desc" msgid="480434281415453287">"គ្មាន​កម្មវិធី​​សម្រាប់​គ្រប់គ្រង​ប្រភេទ​ឯកសារ​នេះ​ទេ។ \n"</string>
     <string name="not_exist_file" msgid="3489434189599716133">"គ្មាន​ឯកសារ"</string>
diff --git a/res/values-km-rKH/test_strings.xml b/res/values-km-rKH/test_strings.xml
index f35bc4a..c9d1e40 100644
--- a/res/values-km-rKH/test_strings.xml
+++ b/res/values-km-rKH/test_strings.xml
@@ -7,7 +7,7 @@
     <string name="update_record" msgid="2480425402384910635">"បញ្ជាក់​កំណត់ត្រា"</string>
     <string name="ack_record" msgid="6716152390978472184">"កំណត់ត្រា​ការ​ទទួល​ស្គាល់"</string>
     <string name="deleteAll_record" msgid="4383349788485210582">"លុប​កំណត់​ត្រា​ទាំងអស់"</string>
-    <string name="ok_button" msgid="6519033415223065454">"យល់​ព្រម"</string>
+    <string name="ok_button" msgid="6519033415223065454">"យល់​ព្រម​"</string>
     <string name="delete_record" msgid="4645040331967533724">"លុប​កំណត់​ត្រា"</string>
     <string name="start_server" msgid="9034821924409165795">"ចាប់ផ្ដើម​​ម៉ាស៊ីន​មេ TCP"</string>
     <string name="notify_server" msgid="4369106744022969655">"ជូន​ដំណឹង​ម៉ាស៊ីន​មេ TCP"</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 88%
rename from src/com/android/bluetooth/a2dp/Avrcp.java
rename to src/com/android/bluetooth/avrcp/Avrcp.java
index 2db403e..3aeb866 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;
@@ -28,6 +29,8 @@
 import android.media.IRemoteControlDisplay;
 import android.media.MediaMetadataRetriever;
 import android.media.RemoteControlClient;
+import android.media.RemoteController;
+import android.media.RemoteController.MetadataEditor;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -40,12 +43,14 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.util.Log;
+
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.Utils;
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
@@ -55,15 +60,15 @@
  * support Bluetooth AVRCP profile.
  * support metadata, play status and event notification
  */
-final class Avrcp {
-    private static final boolean DEBUG = true;
+public final class Avrcp {
+    private static final boolean DEBUG = false;
     private static final String TAG = "Avrcp";
 
     private Context mContext;
     private final AudioManager mAudioManager;
     private AvrcpMessageHandler mHandler;
-    private IRemoteControlDisplayWeak mRemoteControlDisplay;
-    private int mClientGeneration;
+    private RemoteController mRemoteController;
+    private RemoteControllerWeak mRemoteControllerCb;
     private Metadata mMetadata;
     private int mTransportControlFlags;
     private int mCurrentPlayState;
@@ -88,10 +93,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;
@@ -122,7 +123,6 @@
     private static final int MSG_UPDATE_STATE = 100;
     private static final int MSG_SET_METADATA = 101;
     private static final int MSG_SET_TRANSPORT_CONTROLS = 102;
-    private static final int MSG_SET_ARTWORK = 103;
     private static final int MSG_SET_GENERATION_ID = 104;
 
     private static final int BUTTON_TIMEOUT_TIME = 2000;
@@ -173,13 +173,13 @@
         thread.start();
         Looper looper = thread.getLooper();
         mHandler = new AvrcpMessageHandler(looper);
-        mRemoteControlDisplay = new IRemoteControlDisplayWeak(mHandler);
-        mAudioManager.registerRemoteControlDisplay(mRemoteControlDisplay);
-        mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(
-                      mRemoteControlDisplay, true);
+        mRemoteControllerCb = new RemoteControllerWeak(mHandler);
+        mRemoteController = new RemoteController(mContext, mRemoteControllerCb);
+        mAudioManager.registerRemoteController(mRemoteController);
+        mRemoteController.setSynchronizationMode(RemoteController.POSITION_SYNCHRONIZATION_CHECK);
     }
 
-    static Avrcp make(Context context) {
+    public static Avrcp make(Context context) {
         if (DEBUG) Log.v(TAG, "make");
         Avrcp ar = new Avrcp(context);
         ar.start();
@@ -192,73 +192,65 @@
         if (looper != null) {
             looper.quit();
         }
-        mAudioManager.unregisterRemoteControlDisplay(mRemoteControlDisplay);
+        mAudioManager.unregisterRemoteController(mRemoteController);
     }
 
     public void cleanup() {
         cleanupNative();
     }
 
-    private static class IRemoteControlDisplayWeak extends IRemoteControlDisplay.Stub {
-        private WeakReference<Handler> mLocalHandler;
-        IRemoteControlDisplayWeak(Handler handler) {
+    private static class RemoteControllerWeak implements RemoteController.OnClientUpdateListener {
+        private final WeakReference<Handler> mLocalHandler;
+
+        public RemoteControllerWeak(Handler handler) {
             mLocalHandler = new WeakReference<Handler>(handler);
         }
 
         @Override
-        public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
+        public void onClientChange(boolean clearing) {
+            Handler handler = mLocalHandler.get();
+            if (handler != null) {
+                handler.obtainMessage(MSG_SET_GENERATION_ID,
+                        0, (clearing ? 1 : 0), null).sendToTarget();
+            }
+        }
+
+        @Override
+        public void onClientPlaybackStateUpdate(int state) {
+            // Should never be called with the existing code, but just in case
+            Handler handler = mLocalHandler.get();
+            if (handler != null) {
+                handler.obtainMessage(MSG_UPDATE_STATE, 0, state,
+                        new Long(RemoteControlClient.PLAYBACK_POSITION_INVALID)).sendToTarget();
+            }
+        }
+
+        @Override
+        public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
                 long currentPosMs, float speed) {
             Handler handler = mLocalHandler.get();
             if (handler != null) {
-                handler.obtainMessage(MSG_UPDATE_STATE, generationId, state,
-                                      new Long(currentPosMs)).sendToTarget();
+                handler.obtainMessage(MSG_UPDATE_STATE, 0, state,
+                        new Long(currentPosMs)).sendToTarget();
             }
         }
 
         @Override
-        public void setMetadata(int generationId, Bundle metadata) {
+        public void onClientTransportControlUpdate(int transportControlFlags) {
             Handler handler = mLocalHandler.get();
             if (handler != null) {
-                handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget();
-            }
-        }
-
-        @Override
-        public void setTransportControlInfo(int generationId, int flags, int posCapabilities) {
-            Handler handler = mLocalHandler.get();
-            if (handler != null) {
-                handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags)
+                handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, 0, transportControlFlags)
                         .sendToTarget();
             }
         }
 
         @Override
-        public void setArtwork(int generationId, Bitmap bitmap) {
-        }
-
-        @Override
-        public void setAllMetadata(int generationId, Bundle metadata, Bitmap bitmap) {
+        public void onClientMetadataUpdate(MetadataEditor metadataEditor) {
             Handler handler = mLocalHandler.get();
             if (handler != null) {
-                handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget();
-                handler.obtainMessage(MSG_SET_ARTWORK, generationId, 0, bitmap).sendToTarget();
+                handler.obtainMessage(MSG_SET_METADATA, 0, 0, metadataEditor).sendToTarget();
             }
         }
-
-        @Override
-        public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent,
-                boolean clearing) throws RemoteException {
-            Handler handler = mLocalHandler.get();
-            if (handler != null) {
-                handler.obtainMessage(MSG_SET_GENERATION_ID,
-                    clientGeneration, (clearing ? 1 : 0), mediaIntent).sendToTarget();
-            }
-        }
-
-        @Override
-        public void setEnabled(boolean enabled) {
-            // no-op: this RemoteControlDisplay is not subject to being disabled.
-        }
     }
 
     /** Handles Avrcp messages. */
@@ -271,27 +263,19 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
             case MSG_UPDATE_STATE:
-                if (mClientGeneration == msg.arg1) {
-                    updatePlayPauseState(msg.arg2, ((Long)msg.obj).longValue());
-                }
+                    updatePlayPauseState(msg.arg2, ((Long) msg.obj).longValue());
                 break;
 
             case MSG_SET_METADATA:
-                if (mClientGeneration == msg.arg1) updateMetadata((Bundle) msg.obj);
+                    updateMetadata((MetadataEditor) msg.obj);
                 break;
 
             case MSG_SET_TRANSPORT_CONTROLS:
-                if (mClientGeneration == msg.arg1) updateTransportControls(msg.arg2);
-                break;
-
-            case MSG_SET_ARTWORK:
-                if (mClientGeneration == msg.arg1) {
-                }
+                    updateTransportControls(msg.arg2);
                 break;
 
             case MSG_SET_GENERATION_ID:
                 if (DEBUG) Log.v(TAG, "New genId = " + msg.arg1 + ", clearing = " + msg.arg2);
-                mClientGeneration = msg.arg1;
                 break;
 
             case MESSAGE_GET_RC_FEATURES:
@@ -337,8 +321,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 +336,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(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");
                 }
@@ -534,19 +521,11 @@
         }
     }
 
-    private String getMdString(Bundle data, int id) {
-        return data.getString(Integer.toString(id));
-    }
-
-    private long getMdLong(Bundle data, int id) {
-        return data.getLong(Integer.toString(id));
-    }
-
-    private void updateMetadata(Bundle data) {
+    private void updateMetadata(MetadataEditor data) {
         String oldMetadata = mMetadata.toString();
-        mMetadata.artist = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST);
-        mMetadata.trackTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_TITLE);
-        mMetadata.albumTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUM);
+        mMetadata.artist = data.getString(MediaMetadataRetriever.METADATA_KEY_ARTIST, null);
+        mMetadata.trackTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_TITLE, null);
+        mMetadata.albumTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_ALBUM, null);
         if (!oldMetadata.equals(mMetadata.toString())) {
             mTrackNumber++;
             if (mTrackChangedNT == NOTIFICATION_TYPE_INTERIM) {
@@ -570,7 +549,8 @@
         }
         if (DEBUG) Log.v(TAG, "mMetadata=" + mMetadata.toString());
 
-        mSongLengthMs = getMdLong(data, MediaMetadataRetriever.METADATA_KEY_DURATION);
+        mSongLengthMs = data.getLong(MediaMetadataRetriever.METADATA_KEY_DURATION,
+                RemoteControlClient.PLAYBACK_POSITION_INVALID);
         if (DEBUG) Log.v(TAG, "duration=" + mSongLengthMs);
     }
 
@@ -591,7 +571,7 @@
         for (i = 0; i < numAttr; ++i) {
             attrList.add(attrs[i]);
         }
-        Message msg = mHandler.obtainMessage(MESSAGE_GET_ELEM_ATTRS, (int)numAttr, 0, attrList);
+        Message msg = mHandler.obtainMessage(MESSAGE_GET_ELEM_ATTRS, numAttr, 0, attrList);
         mHandler.sendMessage(msg);
     }
 
@@ -633,10 +613,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;
         }
@@ -656,8 +636,7 @@
         long currentPosMs = getPlayPosition();
         if (currentPosMs == -1L) return;
         long newPosMs = Math.max(0L, currentPosMs + amount);
-        mAudioManager.setRemoteControlClientPlaybackPosition(mClientGeneration,
-                newPosMs);
+        mRemoteController.seekTo(newPosMs);
     }
 
     private int getSkipMultiplier() {
@@ -790,7 +769,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
@@ -813,7 +791,7 @@
 
     private int convertToAudioStreamVolume(int volume) {
         // Rescale volume to match AudioSystem's volume
-        return (int) Math.ceil((double) volume*mAudioStreamMax/AVRCP_MAX_VOL);
+        return (int) Math.round((double) volume*mAudioStreamMax/AVRCP_MAX_VOL);
     }
 
     private int convertToAvrcpVolume(int volume) {
@@ -873,4 +851,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 af8e416..a1a26a5
--- a/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -31,6 +31,7 @@
 
 import java.util.HashMap;
 import java.util.ArrayList;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 class AdapterProperties {
     private static final boolean DBG = true;
@@ -44,7 +45,7 @@
     private int mScanMode;
     private int mDiscoverableTimeout;
     private ParcelUuid[] mUuids;
-    private ArrayList<BluetoothDevice> mBondedDevices = new ArrayList<BluetoothDevice>();
+    private CopyOnWriteArrayList<BluetoothDevice> mBondedDevices = new CopyOnWriteArrayList<BluetoothDevice>();
 
     private int mProfilesConnecting, mProfilesConnected, mProfilesDisconnecting;
     private HashMap<Integer, Pair<Integer, Integer>> mProfileConnectionState;
@@ -57,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
@@ -209,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() {
@@ -219,7 +261,7 @@
 
             try {
                 bondedDeviceList = mBondedDevices.toArray(bondedDeviceList);
-                debugLog("getBondedDevices: length="+bondedDeviceList.length);
+                infoLog("getBondedDevices: length="+bondedDeviceList.length);
                 return bondedDeviceList;
             } catch(ArrayStoreException ee) {
                 errorLog("Error retrieving bonded device array");
@@ -487,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);
                 }
@@ -547,7 +607,7 @@
     }
 
     private void infoLog(String msg) {
-        if (DBG) Log.i(TAG, msg);
+        if (VDBG) Log.i(TAG, msg);
     }
 
     private void debugLog(String msg) {
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..b2f679f
--- 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;
@@ -95,14 +103,14 @@
     private static AdapterService sAdapterService;
     public static synchronized AdapterService getAdapterService(){
         if (sAdapterService != null && !sAdapterService.mCleaningUp) {
-            if (DBG) Log.d(TAG, "getAdapterService(): returning " + sAdapterService);
+            Log.d(TAG, "getAdapterService() - returning " + sAdapterService);
             return sAdapterService;
         }
         if (DBG)  {
             if (sAdapterService == null) {
-                Log.d(TAG, "getAdapterService(): service not available");
+                Log.d(TAG, "getAdapterService() - Service not available");
             } else if (sAdapterService.mCleaningUp) {
-                Log.d(TAG,"getAdapterService(): service is cleaning up");
+                Log.d(TAG,"getAdapterService() - Service is cleaning up");
             }
         }
         return null;
@@ -110,14 +118,14 @@
 
     private static synchronized void setAdapterService(AdapterService instance) {
         if (instance != null && !instance.mCleaningUp) {
-            if (DBG) Log.d(TAG, "setAdapterService(): set to: " + sAdapterService);
+            if (DBG) Log.d(TAG, "setAdapterService() - set to: " + sAdapterService);
             sAdapterService = instance;
         } else {
             if (DBG)  {
                 if (sAdapterService == null) {
-                    Log.d(TAG, "setAdapterService(): service not available");
+                    Log.d(TAG, "setAdapterService() - Service not available");
                 } else if (sAdapterService.mCleaningUp) {
-                    Log.d(TAG,"setAdapterService(): service is cleaning up");
+                    Log.d(TAG,"setAdapterService() - Service is cleaning up");
                 }
             }
         }
@@ -140,12 +148,18 @@
     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) {
             synchronized (AdapterService.class) {
                 sRefCount++;
-                Log.d(TAG, "REFCOUNT: CREATED. INSTANCE_COUNT" + sRefCount);
+                debugLog("AdapterService() - REFCOUNT: CREATED. INSTANCE_COUNT" + sRefCount);
             }
         }
     }
@@ -161,10 +175,51 @@
         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)){
-            if (DBG) debugLog( "Profile connected. Schedule missing profile connection if any");
+             (newState == BluetoothProfile.STATE_CONNECTED)){
+            debugLog( "Profile connected. Schedule missing profile connection if any");
             connectOtherProfile(device, PROFILE_CONN_CONNECTED);
             setProfileAutoConnectionPriority(device, profileId);
         }
@@ -173,7 +228,7 @@
             try {
                 binder.sendConnectionStateChange(device, profileId, newState,prevState);
             } catch (RemoteException re) {
-                Log.e(TAG, "",re);
+                errorLog("" + re);
             }
         }
     }
@@ -197,7 +252,8 @@
                 doUpdate=true;
             }
         }
-        if (DBG) Log.d(TAG,"onProfileServiceStateChange: serviceName=" + serviceName + ", state = " + state +", doUpdate = " + doUpdate);
+        debugLog("onProfileServiceStateChange() serviceName=" + serviceName
+            + ", state=" + state +", doUpdate=" + doUpdate);
 
         if (!doUpdate) {
             return;
@@ -211,36 +267,36 @@
         if (isTurningOff) {
             //Process stop or disable pending
             //Check if all services are stopped if so, do cleanup
-            //if (DBG) Log.d(TAG,"Checking if all profiles are stopped...");
             synchronized (mProfileServicesState) {
                 Iterator<Map.Entry<String,Integer>> i = mProfileServicesState.entrySet().iterator();
                 while (i.hasNext()) {
                     Map.Entry<String,Integer> entry = i.next();
                     if (BluetoothAdapter.STATE_OFF != entry.getValue()) {
-                        Log.d(TAG, "Profile still running: " + entry.getKey());
+                        debugLog("onProfileServiceStateChange() - Profile still running: "
+                            + entry.getKey());
                         return;
                     }
                 }
             }
-            if (DBG) Log.d(TAG, "All profile services stopped...");
+            debugLog("onProfileServiceStateChange() - All profile services stopped...");
             //Send message to state machine
             mProfilesStarted=false;
             mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.STOPPED));
         } else if (isTurningOn) {
             //Process start pending
             //Check if all services are started if so, update state
-            //if (DBG) Log.d(TAG,"Checking if all profiles are running...");
             synchronized (mProfileServicesState) {
                 Iterator<Map.Entry<String,Integer>> i = mProfileServicesState.entrySet().iterator();
                 while (i.hasNext()) {
                     Map.Entry<String,Integer> entry = i.next();
                     if (BluetoothAdapter.STATE_ON != entry.getValue()) {
-                        Log.d(TAG, "Profile still not running:" + entry.getKey());
+                        debugLog("onProfileServiceStateChange() - Profile still not running:"
+                            + entry.getKey());
                         return;
                     }
                 }
             }
-            if (DBG) Log.d(TAG, "All profile services started.");
+            debugLog("onProfileServiceStateChange() - All profile services started.");
             mProfilesStarted=true;
             //Send message to state machine
             mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.STARTED));
@@ -250,7 +306,7 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        if (DBG) debugLog("onCreate");
+        debugLog("onCreate()");
         mBinder = new AdapterServiceBinder(this);
         mAdapterProperties = new AdapterProperties(this);
         mAdapterStateMachine =  AdapterState.make(this, mAdapterProperties);
@@ -261,26 +317,29 @@
         //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
     public IBinder onBind(Intent intent) {
-        if (DBG) debugLog("onBind");
+        debugLog("onBind()");
         return mBinder;
     }
     public boolean onUnbind(Intent intent) {
-        if (DBG) debugLog("onUnbind, calling cleanup");
+        debugLog("onUnbind() - calling cleanup");
         cleanup();
         return super.onUnbind(intent);
     }
 
     public void onDestroy() {
-        debugLog("****onDestroy()********");
+        debugLog("onDestroy()");
     }
 
     void processStart() {
-        if (DBG) debugLog("processStart()");
+        debugLog("processStart()");
         Class[] supportedProfileServices = Config.getSupportedProfiles();
         //Initialize data objects
         for (int i=0; i < supportedProfileServices.length;i++) {
@@ -289,7 +348,7 @@
         mRemoteDevices = new RemoteDevices(this);
         mAdapterProperties.init(mRemoteDevices);
 
-        if (DBG) {debugLog("processStart(): Make Bond State Machine");}
+        debugLog("processStart() - Make Bond State Machine");
         mBondStateMachine = BondStateMachine.make(this, mAdapterProperties, mRemoteDevices);
 
         mJniCallbacks.init(mBondStateMachine,mRemoteDevices);
@@ -302,7 +361,7 @@
             //Startup all profile services
             setProfileServiceState(supportedProfileServices,BluetoothAdapter.STATE_ON);
         }else {
-            if (DBG) {debugLog("processStart(): Profile Services alreay started");}
+            debugLog("processStart() - Profile Services alreay started");
             mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.STARTED));
         }
     }
@@ -316,21 +375,20 @@
         if (mProfilesStarted && supportedProfileServices.length>0) {
             setProfileServiceState(supportedProfileServices,BluetoothAdapter.STATE_OFF);
             return true;
-        } else {
-            if (DBG) {debugLog("stopProfileServices(): No profiles services to stop or already stopped.");}
-            return false;
         }
+        debugLog("stopProfileServices() - No profiles services to stop or already stopped.");
+        return false;
     }
 
      void updateAdapterState(int prevState, int newState){
         if (mCallbacks !=null) {
             int n=mCallbacks.beginBroadcast();
-            Log.d(TAG,"Broadcasting updateAdapterState() to " + n + " receivers.");
+            debugLog("updateAdapterState() - Broadcasting state to " + n + " receivers.");
             for (int i=0; i <n;i++) {
                 try {
                     mCallbacks.getBroadcastItem(i).onBluetoothStateChange(prevState,newState);
                 }  catch (RemoteException e) {
-                    Log.e(TAG, "Unable to call onBluetoothStateChange() on callback #" + i, e);
+                    debugLog("updateAdapterState() - Callback #" + i + " failed ("  + e + ")");
                 }
             }
             mCallbacks.finishBroadcast();
@@ -338,14 +396,26 @@
     }
 
     void cleanup () {
-        if (DBG)debugLog("cleanup()");
+        debugLog("cleanup()");
         if (mCleaningUp) {
-            Log.w(TAG,"*************service already starting to cleanup... Ignoring cleanup request.........");
+            errorLog("cleanup() - Service already starting to cleanup, ignoring request...");
             return;
         }
 
         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();
@@ -361,9 +431,8 @@
         }
 
         if (mNativeAvailable) {
-            Log.d(TAG, "Cleaning up adapter native....");
+            debugLog("cleanup() - Cleaning up adapter native");
             cleanupNative();
-            Log.d(TAG, "Done cleaning up adapter native....");
             mNativeAvailable=false;
         }
 
@@ -390,35 +459,44 @@
             mCallbacks.kill();
         }
 
-        if (DBG)debugLog("cleanup() done");
-
-        if (DBG) debugLog("bluetooth process exit normally after clean up...");
+        debugLog("cleanup() - Bluetooth process exited normally.");
         System.exit(0);
     }
 
     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() {
         @Override
         public void handleMessage(Message msg) {
-            if (DBG) debugLog("Message: " + msg.what);
+            debugLog("handleMessage() - Message: " + msg.what);
 
             switch (msg.what) {
                 case MESSAGE_PROFILE_SERVICE_STATE_CHANGED: {
-                    if(DBG) debugLog("MESSAGE_PROFILE_SERVICE_STATE_CHANGED");
+                    debugLog("handleMessage() - MESSAGE_PROFILE_SERVICE_STATE_CHANGED");
                     processProfileServiceStateChanged((String) msg.obj, msg.arg1);
                 }
                     break;
                 case MESSAGE_PROFILE_CONNECTION_STATE_CHANGED: {
-                    if (DBG) debugLog( "MESSAGE_PROFILE_CONNECTION_STATE_CHANGED");
+                    debugLog( "handleMessage() - MESSAGE_PROFILE_CONNECTION_STATE_CHANGED");
                     processProfileStateChanged((BluetoothDevice) msg.obj, msg.arg1,msg.arg2, msg.getData().getInt("prevState",BluetoothAdapter.ERROR));
                 }
                     break;
+                case MESSAGE_PROFILE_INIT_PRIORITIES: {
+                    debugLog( "handleMessage() - 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");
+                    debugLog( "handleMessage() - MESSAGE_CONNECT_OTHER_PROFILES");
                     processConnectOtherProfiles((BluetoothDevice) msg.obj,msg.arg1);
                 }
                     break;
@@ -429,7 +507,7 @@
     @SuppressWarnings("rawtypes")
     private void setProfileServiceState(Class[] services, int state) {
         if (state != BluetoothAdapter.STATE_ON && state != BluetoothAdapter.STATE_OFF) {
-            Log.w(TAG,"setProfileServiceState(): invalid state...Leaving...");
+            debugLog("setProfileServiceState() - Invalid state, leaving...");
             return;
         }
 
@@ -444,15 +522,16 @@
             String serviceName = services[i].getName();
             Integer serviceState = mProfileServicesState.get(serviceName);
             if(serviceState != null && serviceState != expectedCurrentState) {
-                Log.w(TAG, "Unable to " + (state == BluetoothAdapter.STATE_OFF? "start" : "stop" ) +" service " +
-                        serviceName+". Invalid state: " + serviceState);
+                debugLog("setProfileServiceState() - Unable to " 
+                    + (state == BluetoothAdapter.STATE_OFF ? "start" : "stop" )
+                    + " service " + serviceName
+                    + ". Invalid state: " + serviceState);
                 continue;
             }
 
-            if (DBG) {
-                Log.w(TAG, (state == BluetoothAdapter.STATE_OFF? "Stopping" : "Starting" ) +" service " +
-                        serviceName);
-            }
+            debugLog("setProfileServiceState() - "
+                + (state == BluetoothAdapter.STATE_OFF ? "Stopping" : "Starting")
+                + " service " + serviceName);
 
             mProfileServicesState.put(serviceName,pendingState);
             Intent intent = new Intent(this,services[i]);
@@ -516,9 +595,9 @@
         public boolean enable() {
             if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
                 (!Utils.checkCaller())) {
-                Log.w(TAG,"enable(): not allowed for non-active user and non system user");
+                Log.w(TAG, "enable() - Not allowed for non-active user and non system user");
                 return false;
-	    }
+            }
 
             AdapterService service = getService();
             if (service == null) return false;
@@ -528,9 +607,9 @@
         public boolean enableNoAutoConnect() {
             if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
                 (!Utils.checkCaller())) {
-                Log.w(TAG,"enableNoAuto(): not allowed for non-active user and non system user");
+                Log.w(TAG, "enableNoAuto() - Not allowed for non-active user and non system user");
                 return false;
-	    }
+            }
 
             AdapterService service = getService();
             if (service == null) return false;
@@ -540,9 +619,9 @@
         public boolean disable() {
             if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
                 (!Utils.checkCaller())) {
-                Log.w(TAG,"disable(): not allowed for non-active user and non system user");
+                Log.w(TAG, "disable() - Not allowed for non-active user and non system user");
                 return false;
-	    }
+            }
 
             AdapterService service = getService();
             if (service == null) return false;
@@ -552,9 +631,9 @@
         public String getAddress() {
             if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
                 (!Utils.checkCaller())) {
-                Log.w(TAG,"getAddress(): not allowed for non-active user and non system user");
+                Log.w(TAG, "getAddress() - Not allowed for non-active user and non system user");
                 return null;
-	    }
+            }
 
             AdapterService service = getService();
             if (service == null) return null;
@@ -563,7 +642,7 @@
 
         public ParcelUuid[] getUuids() {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"getUuids(): not allowed for non-active user");
+                Log.w(TAG, "getUuids() - Not allowed for non-active user");
                 return new ParcelUuid[0];
             }
 
@@ -575,9 +654,9 @@
         public String getName() {
             if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
                 (!Utils.checkCaller())) {
-                Log.w(TAG,"getName(): not allowed for non-active user and non system user");
+                Log.w(TAG, "getName() - Not allowed for non-active user and non system user");
                 return null;
-	    }
+            }
 
             AdapterService service = getService();
             if (service == null) return null;
@@ -586,7 +665,7 @@
 
         public boolean setName(String name) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"setName(): not allowed for non-active user");
+                Log.w(TAG, "setName() - Not allowed for non-active user");
                 return false;
             }
 
@@ -597,7 +676,7 @@
 
         public int getScanMode() {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"getScanMode(): not allowed for non-active user");
+                Log.w(TAG, "getScanMode() - Not allowed for non-active user");
                 return BluetoothAdapter.SCAN_MODE_NONE;
             }
 
@@ -608,7 +687,7 @@
 
         public boolean setScanMode(int mode, int duration) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"setScanMode(): not allowed for non-active user");
+                Log.w(TAG, "setScanMode() - Not allowed for non-active user");
                 return false;
             }
 
@@ -619,7 +698,7 @@
 
         public int getDiscoverableTimeout() {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"getDiscoverableTimeout(): not allowed for non-active user");
+                Log.w(TAG, "getDiscoverableTimeout() - Not allowed for non-active user");
                 return 0;
             }
 
@@ -630,7 +709,7 @@
 
         public boolean setDiscoverableTimeout(int timeout) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"setDiscoverableTimeout(): not allowed for non-active user");
+                Log.w(TAG, "setDiscoverableTimeout() - Not allowed for non-active user");
                 return false;
             }
 
@@ -641,7 +720,7 @@
 
         public boolean startDiscovery() {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"startDiscovery(): not allowed for non-active user");
+                Log.w(TAG, "startDiscovery() - Not allowed for non-active user");
                 return false;
             }
 
@@ -652,7 +731,7 @@
 
         public boolean cancelDiscovery() {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"cancelDiscovery(): not allowed for non-active user");
+                Log.w(TAG, "cancelDiscovery() - Not allowed for non-active user");
                 return false;
             }
 
@@ -662,7 +741,7 @@
         }
         public boolean isDiscovering() {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"isDiscovering(): not allowed for non-active user");
+                Log.w(TAG, "isDiscovering() - Not allowed for non-active user");
                 return false;
             }
 
@@ -687,7 +766,7 @@
 
         public int getProfileConnectionState(int profile) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"getProfileConnectionState: not allowed for non-active user");
+                Log.w(TAG, "getProfileConnectionState- Not allowed for non-active user");
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
 
@@ -698,7 +777,7 @@
 
         public boolean createBond(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"createBond(): not allowed for non-active user");
+                Log.w(TAG, "createBond() - Not allowed for non-active user");
                 return false;
             }
 
@@ -709,7 +788,7 @@
 
         public boolean cancelBondProcess(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"cancelBondProcess(): not allowed for non-active user");
+                Log.w(TAG, "cancelBondProcess() - Not allowed for non-active user");
                 return false;
             }
 
@@ -720,7 +799,7 @@
 
         public boolean removeBond(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"removeBond(): not allowed for non-active user");
+                Log.w(TAG, "removeBond() - Not allowed for non-active user");
                 return false;
             }
 
@@ -736,9 +815,17 @@
             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");
+                Log.w(TAG, "getRemoteName() - Not allowed for non-active user");
                 return null;
             }
 
@@ -749,7 +836,7 @@
 
         public int getRemoteType(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"getRemoteType(): not allowed for non-active user");
+                Log.w(TAG, "getRemoteType() - Not allowed for non-active user");
                 return BluetoothDevice.DEVICE_TYPE_UNKNOWN;
             }
 
@@ -760,7 +847,7 @@
 
         public String getRemoteAlias(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"getRemoteAlias(): not allowed for non-active user");
+                Log.w(TAG, "getRemoteAlias() - Not allowed for non-active user");
                 return null;
             }
 
@@ -771,7 +858,7 @@
 
         public boolean setRemoteAlias(BluetoothDevice device, String name) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"setRemoteAlias(): not allowed for non-active user");
+                Log.w(TAG, "setRemoteAlias() - Not allowed for non-active user");
                 return false;
             }
 
@@ -782,7 +869,7 @@
 
         public int getRemoteClass(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"getRemoteClass(): not allowed for non-active user");
+                Log.w(TAG, "getRemoteClass() - Not allowed for non-active user");
                 return 0;
             }
 
@@ -793,7 +880,7 @@
 
         public ParcelUuid[] getRemoteUuids(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"getRemoteUuids(): not allowed for non-active user");
+                Log.w(TAG, "getRemoteUuids() - Not allowed for non-active user");
                 return new ParcelUuid[0];
             }
 
@@ -804,7 +891,7 @@
 
         public boolean fetchRemoteUuids(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"fetchRemoteUuids(): not allowed for non-active user");
+                Log.w(TAG, "fetchRemoteUuids() - Not allowed for non-active user");
                 return false;
             }
 
@@ -815,7 +902,7 @@
 
         public boolean setPin(BluetoothDevice device, boolean accept, int len, byte[] pinCode) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"setPin(): not allowed for non-active user");
+                Log.w(TAG, "setPin() - Not allowed for non-active user");
                 return false;
             }
 
@@ -826,7 +913,7 @@
 
         public boolean setPasskey(BluetoothDevice device, boolean accept, int len, byte[] passkey) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"setPasskey(): not allowed for non-active user");
+                Log.w(TAG, "setPasskey() - Not allowed for non-active user");
                 return false;
             }
 
@@ -837,7 +924,7 @@
 
         public boolean setPairingConfirmation(BluetoothDevice device, boolean accept) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"setPairingConfirmation(): not allowed for non-active user");
+                Log.w(TAG, "setPairingConfirmation() - Not allowed for non-active user");
                 return false;
             }
 
@@ -856,7 +943,7 @@
         public ParcelFileDescriptor connectSocket(BluetoothDevice device, int type,
                                                   ParcelUuid uuid, int port, int flag) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"connectSocket(): not allowed for non-active user");
+                Log.w(TAG, "connectSocket() - Not allowed for non-active user");
                 return null;
             }
 
@@ -868,7 +955,7 @@
         public ParcelFileDescriptor createSocketChannel(int type, String serviceName,
                                                         ParcelUuid uuid, int port, int flag) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"createSocketChannel(): not allowed for non-active user");
+                Log.w(TAG, "createSocketChannel() - Not allowed for non-active user");
                 return null;
             }
 
@@ -880,7 +967,7 @@
         public boolean configHciSnoopLog(boolean enable) {
             if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
                 (!Utils.checkCaller())) {
-                Log.w(TAG,"configHciSnoopLog(): not allowed for non-active user");
+                Log.w(TAG, "configHciSnoopLog() - Not allowed for non-active user");
                 return false;
             }
 
@@ -917,7 +1004,7 @@
             return  BluetoothAdapter.STATE_OFF;
         }
         else {
-            if (DBG) debugLog("getState(): mAdapterProperties: " + mAdapterProperties);
+            debugLog("getState() - mAdapterProperties: " + mAdapterProperties);
             return mAdapterProperties.getState();
         }
     }
@@ -933,7 +1020,7 @@
      public synchronized boolean enable(boolean quietMode) {
          enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                         "Need BLUETOOTH ADMIN permission");
-         if (DBG)debugLog("Enable called with quiet mode status =  " + mQuietmode);
+         debugLog("enable() - Enable called with quiet mode status =  " + mQuietmode);
          mQuietmode  = quietMode;
          Message m =
                  mAdapterStateMachine.obtainMessage(AdapterState.USER_TURN_ON);
@@ -945,7 +1032,7 @@
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                        "Need BLUETOOTH ADMIN permission");
 
-        if (DBG) debugLog("disable() called...");
+        debugLog("disable() called...");
         Message m =
                 mAdapterStateMachine.obtainMessage(AdapterState.USER_TURN_OFF);
         mAdapterStateMachine.sendMessage(m);
@@ -973,7 +1060,7 @@
         try {
             return mAdapterProperties.getName();
         } catch (Throwable t) {
-            Log.d(TAG, "Unexpected exception while calling getName()",t);
+            debugLog("getName() - Unexpected exception (" + t + ")");
         }
         return null;
     }
@@ -1034,13 +1121,11 @@
 
      BluetoothDevice[] getBondedDevices() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        debugLog("Get Bonded Devices being called");
         return mAdapterProperties.getBondedDevices();
     }
 
      int getAdapterConnectionState() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
         return mAdapterProperties.getConnectionState();
     }
 
@@ -1069,22 +1154,22 @@
     }
 
       public boolean isQuietModeEnabled() {
-          if (DBG) debugLog("Quiet mode Enabled = " + mQuietmode);
+          debugLog("isQuetModeEnabled() - Enabled = " + mQuietmode);
           return mQuietmode;
      }
 
      public void autoConnect(){
         if (getState() != BluetoothAdapter.STATE_ON){
-             errorLog("BT is not ON. Exiting autoConnect");
+             errorLog("autoConnect() - BT is not ON. Exiting autoConnect");
              return;
          }
          if (isQuietModeEnabled() == false) {
-            if (DBG) debugLog( "Initiate auto connection on BT on...");
+             debugLog( "autoConnect() - Initiate auto connection on BT on...");
              autoConnectHeadset();
              autoConnectA2dp();
          }
          else {
-             if (DBG) debugLog( "BT is in Quiet mode. Not initiating  auto connections");
+             debugLog( "autoConnect() - BT is in quiet mode. Not initiating auto connections");
          }
     }
 
@@ -1097,9 +1182,9 @@
         }
         for (BluetoothDevice device : bondedDevices) {
             if (hsService.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT ){
-                Log.d(TAG,"Auto Connecting Headset Profile with device " + device.toString());
+                debugLog("autoConnectHeadset() - Connecting HFP with " + device.toString());
                 hsService.connect(device);
-                }
+            }
         }
     }
 
@@ -1111,9 +1196,9 @@
         }
         for (BluetoothDevice device : bondedDevices) {
             if (a2dpSservice.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT ){
-                Log.d(TAG,"Auto Connecting A2DP Profile with device " + device.toString());
+                debugLog("autoConnectA2dp() - Connecting A2DP with " + device.toString());
                 a2dpSservice.connect(device);
-                }
+            }
         }
     }
 
@@ -1133,6 +1218,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 +1245,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 +1267,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 +1311,12 @@
         return deviceProp.getBondState();
     }
 
+    boolean isConnected(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        byte[] addr = Utils.getBytesFromAddress(device.getAddress());
+        return isConnectedNative(addr);
+    }
+
      String getRemoteName(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
@@ -1358,6 +1451,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,14 +1502,84 @@
         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);
+        if (DBG) Log.d(TAG +"(" +hashCode()+")", msg);
     }
 
     private void errorLog(String msg) {
         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();
@@ -1409,6 +1597,8 @@
     /*package*/ native boolean removeBondNative(byte[] address);
     /*package*/ native boolean cancelBondNative(byte[] address);
 
+    /*package*/ native boolean isConnectedNative(byte[] address);
+
     private native boolean startDiscoveryNative();
     private native boolean cancelDiscoveryNative();
 
@@ -1426,12 +1616,14 @@
 
     /*package*/ native boolean configHciSnoopLogNative(boolean enable);
 
+    private native void alarmFiredNative();
+
     protected void finalize() {
         cleanup();
         if (TRACE_REF) {
             synchronized (AdapterService.class) {
                 sRefCount--;
-                Log.d(TAG, "REFCOUNT: FINALIZED. INSTANCE_COUNT= " + sRefCount);
+                debugLog("finalize() - REFCOUNT: FINALIZED. INSTANCE_COUNT= " + sRefCount);
             }
         }
     }
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 f054df6..0e6c8cb 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;
 
@@ -210,12 +212,12 @@
         }
     }
 
-
     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
@@ -228,6 +230,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin);
         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
                     BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
     }
 
@@ -354,6 +357,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, getDevice(address));
         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
                 BluetoothDevice.PAIRING_VARIANT_PIN);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
         return;
     }
@@ -396,6 +400,7 @@
             intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, passkey);
         }
         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, variant);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
     }
 
@@ -407,6 +412,10 @@
             return;
         }
 
+        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);
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 71000a5..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) {
@@ -415,6 +460,12 @@
             service.readRemoteRssi(clientIf, address);
         }
 
+        public void configureMTU(int clientIf, String address, int mtu) {
+            GattService service = getService();
+            if (service == null) return;
+            service.configureMTU(clientIf, address, mtu);
+        }
+
         public void registerServer(ParcelUuid uuid, IBluetoothGattServerCallback callback) {
             GattService service = getService();
             if (service == null) return;
@@ -427,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) {
@@ -516,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;
@@ -567,10 +634,8 @@
 
         @Override
         public void removeAdvManufacturerCodeAndData(int manufacturerCode) throws RemoteException {
-            GattService service = getService();
-            if (service == null) return;
-            service.removeAdvManufacturerCodeAndData(manufacturerCode);
         }
+
     };
 
     /**************************************************************************
@@ -600,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 {
@@ -623,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);
@@ -920,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) {
@@ -968,6 +1078,78 @@
         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);
+
+        if (DBG) Log.d(TAG, "onConfigureMTU() address=" + address + ", status="
+            + status + ", mtu=" + mtu);
+
+        ClientMap.App app = mClientMap.getByConnId(connId);
+        if (app != null) {
+            app.callback.onConfigureMTU(address, mtu, status);
+        }
+    }
+
+    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
      *************************************************************************/
@@ -1026,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");
 
@@ -1039,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) {
@@ -1048,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);
         }
     }
 
@@ -1076,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) {
@@ -1147,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.
@@ -1225,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");
 
@@ -1384,6 +1709,18 @@
         gattClientReadRemoteRssiNative(clientIf, address);
     }
 
+    void configureMTU(int clientIf, String address, int mtu) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        if (DBG) Log.d(TAG, "configureMTU() - address=" + address + " mtu=" + mtu);
+        Integer connId = mClientMap.connIdByAddress(clientIf, address);
+        if (connId != null) {
+            gattClientConfigureMTUNative(connId, mtu);
+        } else {
+            Log.e(TAG, "configureMTU() - No connection for " + address + "...");
+        }
+    }
+
     /**************************************************************************
      * Callback functions - SERVER
      *************************************************************************/
@@ -1625,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) {
@@ -1746,6 +2083,7 @@
         }
     }
 
+
     /**************************************************************************
      * Private functions
      *************************************************************************/
@@ -1762,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;
@@ -1777,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() {
@@ -1859,7 +2201,8 @@
                 }
             }
         } else {
-            gattServerStartServiceNative(serverIf, srvcHandle, (byte)2 /*BREDR/LE*/);
+            gattServerStartServiceNative(serverIf, srvcHandle,
+                (byte)BluetoothDevice.TRANSPORT_BREDR | BluetoothDevice.TRANSPORT_LE);
             finished = true;
         }
 
@@ -1987,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);
@@ -2051,6 +2394,8 @@
 
     private native void gattAdvertiseNative(int client_if, boolean start);
 
+    private native void gattClientConfigureMTUNative(int conn_id, int mtu);
+
     private native void gattSetAdvDataNative(int serverIf, boolean setScanRsp, boolean inclName,
             boolean inclTxPower, int minInterval, int maxInterval,
             int appearance, byte[] manufacturerData, byte[] serviceData, byte[] serviceUuid);
@@ -2061,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..2f8821a 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
@@ -161,13 +163,17 @@
 
     void sendDeviceStateChanged()
     {
+        // When out of service, send signal strength as 0. Some devices don't
+        // use the service indicator, but only the signal indicator
+        int signal = mService == HeadsetHalConstants.NETWORK_STATE_AVAILABLE ? mSignal : 0;
+
         Log.d(TAG, "sendDeviceStateChanged. mService="+ mService +
-                   " mSignal="+mSignal +" mRoam="+mRoam +
+                   " mSignal=" + signal +" mRoam="+ mRoam +
                    " mBatteryCharge=" + mBatteryCharge);
         HeadsetStateMachine sm = mStateMachine;
         if (sm != null) {
             sm.sendMessage(HeadsetStateMachine.DEVICE_STATE_CHANGED,
-                new HeadsetDeviceState(mService, mRoam, mSignal, mBatteryCharge));
+                new HeadsetDeviceState(mService, mRoam, signal, mBatteryCharge));
         }
     }
 
@@ -327,10 +333,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/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index 58528f6..7ada0c4 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -894,8 +894,13 @@
     private void writeMmsDataPart(long handle, MimePart part, int count) throws IOException{
         ContentValues values = new ContentValues();
         values.put("mid", handle);
-        if(part.contentType != null)
+        if(part.contentType != null){
+            //Remove last char if ';' from contentType
+            if(part.contentType.charAt(part.contentType.length() - 1) == ';') {
+               part.contentType = part.contentType.substring(0,part.contentType.length() -1);
+            }
             values.put("ct", part.contentType);
+        }
         if(part.contentId != null)
             values.put("cid", part.contentId);
         if(part.contentLocation != null)
diff --git a/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index 6640955..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();
         }
@@ -456,7 +456,7 @@
         HeaderSet replyHeaders = new HeaderSet();
         BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
         BluetoothMapMessageListing outList;
-        if(folderName == null) {
+        if(folderName == null || folderName.length() == 0 ) {
             folderName = mCurrentFolder.getName();
         }
         if(appParams == null){
diff --git a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
index e63d67c..39116a0 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
@@ -51,6 +51,11 @@
 import android.util.Log;
 import android.provider.Settings;
 
+import android.util.Patterns;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.Locale;
+
 /**
  * This class is designed to act as the entry point of handling the share intent
  * via BT from other APPs. and also make "Bluetooth" available in sharing method
@@ -61,6 +66,10 @@
     private static final boolean D = Constants.DEBUG;
     private static final boolean V = Constants.VERBOSE;
 
+    // Regex that matches characters that have special meaning in HTML. '<', '>', '&' and
+    // multiple continuous spaces.
+    private static final Pattern PLAIN_TEXT_TO_ESCAPE = Pattern.compile("[<>&]| {2,}|\r?\n");
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -250,11 +259,58 @@
             String fileName = getString(R.string.bluetooth_share_file_name) + ".html";
             context.deleteFile(fileName);
 
-            String uri = shareContent.toString();
-            String content = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html;"
-                + " charset=UTF-8\"/></head><body>" + "<a href=\"" + uri + "\">" + uri + "</a></p>"
-                + "</body></html>";
-            byte[] byteBuff = content.getBytes();
+            /*
+             * Convert the plain text to HTML
+             */
+            StringBuffer sb = new StringBuffer("<html><head><meta http-equiv=\"Content-Type\""
+                    + " content=\"text/html; charset=UTF-8\"/></head><body>");
+            // Escape any inadvertent HTML in the text message
+            String text = escapeCharacterToDisplay(shareContent.toString());
+
+            // Regex that matches Web URL protocol part as case insensitive.
+            Pattern webUrlProtocol = Pattern.compile("(?i)(http|https)://");
+
+            Pattern pattern = Pattern.compile("("
+                    + Patterns.WEB_URL.pattern() + ")|("
+                    + Patterns.EMAIL_ADDRESS.pattern() + ")|("
+                    + Patterns.PHONE.pattern() + ")");
+            // Find any embedded URL's and linkify
+            Matcher m = pattern.matcher(text);
+            while (m.find()) {
+                String matchStr = m.group();
+                String link = null;
+
+                // Find any embedded URL's and linkify
+                if (Patterns.WEB_URL.matcher(matchStr).matches()) {
+                    Matcher proto = webUrlProtocol.matcher(matchStr);
+                    if (proto.find()) {
+                        // This is work around to force URL protocol part be lower case,
+                        // because WebView could follow only lower case protocol link.
+                        link = proto.group().toLowerCase(Locale.US) +
+                                matchStr.substring(proto.end());
+                    } else {
+                        // Patterns.WEB_URL matches URL without protocol part,
+                        // so added default protocol to link.
+                        link = "http://" + matchStr;
+                    }
+
+                // Find any embedded email address
+                } else if (Patterns.EMAIL_ADDRESS.matcher(matchStr).matches()) {
+                    link = "mailto:" + matchStr;
+
+                // Find any embedded phone numbers and linkify
+                } else if (Patterns.PHONE.matcher(matchStr).matches()) {
+                    link = "tel:" + matchStr;
+                }
+                if (link != null) {
+                    String href = String.format("<a href=\"%s\">%s</a>", link, matchStr);
+                    m.appendReplacement(sb, href);
+                }
+            }
+            m.appendTail(sb);
+            sb.append("</body></html>");
+
+            byte[] byteBuff = sb.toString().getBytes();
 
             outStream = context.openFileOutput(fileName, Context.MODE_PRIVATE);
             if (outStream != null) {
@@ -283,4 +339,44 @@
         }
         return fileUri;
     }
+
+    /**
+     * Escape some special character as HTML escape sequence.
+     *
+     * @param text Text to be displayed using WebView.
+     * @return Text correctly escaped.
+     */
+    private static String escapeCharacterToDisplay(String text) {
+        Pattern pattern = PLAIN_TEXT_TO_ESCAPE;
+        Matcher match = pattern.matcher(text);
+
+        if (match.find()) {
+            StringBuilder out = new StringBuilder();
+            int end = 0;
+            do {
+                int start = match.start();
+                out.append(text.substring(end, start));
+                end = match.end();
+                int c = text.codePointAt(start);
+                if (c == ' ') {
+                    // Escape successive spaces into series of "&nbsp;".
+                    for (int i = 1, n = end - start; i < n; ++i) {
+                        out.append("&nbsp;");
+                    }
+                    out.append(' ');
+                } else if (c == '\r' || c == '\n') {
+                    out.append("<br>");
+                } else if (c == '<') {
+                    out.append("&lt;");
+                } else if (c == '>') {
+                    out.append("&gt;");
+                } else if (c == '&') {
+                    out.append("&amp;");
+                }
+            } while (match.find());
+            out.append(text.substring(end));
+            text = out.toString();
+        }
+        return text;
+    }
 }
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)
-